Share

[[{“value”:”

Introduction

This is a follow-up to my earlier post on deploying SAP HANA 2.0 on AWS with Terraform and Ansible. That covered a single-node installation.

SAP HANA System Replication (HSR) paid my mortgage for about 12 / 13 years, but I don’t get to play with it much anymore, which is a shame. This post automates the whole setup — two EC2 instances in separate availability zones, HANA installed on both, HSR configured end to end with no manual steps.

I might follow it up with a blog about HA, but I don’t believe that Pacemaker / STONITH is fit for purpose. On nearly every project that I’ve worked on that had Pacemaker, we ended up tearing it out in favour of manual HSR with manual failover. We’ll see.

Everything here was built and tested live. The gotchas section reflects what actually broke, not theoretical edge cases.

Note: Same sandbox sizing as the previous post — r5.xlarge, 100GB root volume. Not for production. Run proper sizing before you go anywhere near a productive landscape.


Architecture

Local machine
    ├── Terraform  → VPC, two subnets (AZ-a, AZ-b), IGW, Route 53 private zone
    │               two EC2 instances, IAM role for S3 access
    │
    └── Ansible    → hana-install.yml  (runs on both nodes in parallel)
                     hsr-setup.yml    (primary first, then secondary, then verify)

AWS VPC (10.0.0.0/16)
    ├── Subnet 10.0.1.0/24 (us-east-2a)  →  hana-primary   (r5.xlarge)
    └── Subnet 10.0.2.0/24 (us-east-2b)  →  hana-secondary (r5.xlarge)

Replication: SYNCMEM, logreplay mode

The security group allows all inbound traffic within the VPC (10.0.0.0/16) so HSR can communicate freely between nodes. HANA System Replication uses ports in the 4000x and 5432x ranges depending on instance number — rather than enumerate them, open the VPC CIDR internally and restrict SSH at the perimeter.


Project Layout

ansible_hsr/
├── terraform/
│   ├── providers.tf
│   ├── variables.tf
│   ├── main.tf
│   └── outputs.tf
├── ansible/
│   ├── inventory.ini
│   ├── hana-install.yml
│   ├── hsr-setup.yml
│   └── templates/
│       └── hana_install.cfg.j2
└── generate_inventory.sh

Step 1 — Terraform

What it provisions

  • VPC with DNS support and hostnames enabled (required for HANA internal resolution)
  • Two subnets in separate AZs for HA placement
  • Internet gateway and route table
  • Route 53 private hosted zone (hana.internal) with A records for both nodes
  • IAM role + policy granting S3 read access to the HANA media bucket — no credentials on the instances
  • Security group: SSH from anywhere, all internal VPC traffic, all egress
  • Two EC2 instances: r5.xlarge, SUSE SLES for SAP 15 SP5 PAYG AMI, 100GB gp3

variables.tf

variable "aws_region" {
  default = "us-east-2"
}

variable "key_pair_name" {
  default = "<your-key-pair-name>"
}

variable "hana_media_bucket" {
  default = "<your-s3-bucket-name>"
}

variable "private_zone_name" {
  default = "hana.internal"
}

main.tf (abbreviated — key sections)

locals {
  sles_sap_ami = "ami-099afc29551c4b139"  # SUSE SLES for SAP 15 SP5 PAYG, us-east-2
}

resource "aws_vpc" "hana" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
}

resource "aws_subnet" "primary" {
  vpc_id            = aws_vpc.hana.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${var.aws_region}a"
}

resource "aws_subnet" "secondary" {
  vpc_id            = aws_vpc.hana.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "${var.aws_region}b"
}

resource "aws_security_group" "hana" {
  vpc_id = aws_vpc.hana.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "All internal VPC traffic (HSR)"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["10.0.0.0/16"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "hana_primary" {
  ami                         = local.sles_sap_ami
  instance_type               = "r5.xlarge"
  key_name                    = var.key_pair_name
  subnet_id                   = aws_subnet.primary.id
  vpc_security_group_ids      = [aws_security_group.hana.id]
  iam_instance_profile        = aws_iam_instance_profile.hana_s3.name
  associate_public_ip_address = true

  root_block_device {
    volume_type = "gp3"
    volume_size = 100
  }

  tags = { Name = "hana-primary" }
}

outputs.tf

output "primary_public_ip"    { value = aws_instance.hana_primary.public_ip }
output "secondary_public_ip"  { value = aws_instance.hana_secondary.public_ip }
output "primary_private_ip"   { value = aws_instance.hana_primary.private_ip }
output "secondary_private_ip" { value = aws_instance.hana_secondary.private_ip }

Run

cd terraform
terraform init
terraform apply -auto-approve

Step 2 — Generate the Ansible Inventory

After terraform apply, run generate_inventory.sh from the repo root. It reads the Terraform outputs, validates the IPs, constructs the primary’s private hostname, and writes ansible/inventory.ini.

#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR/terraform"

PRIMARY_PUBLIC=$(terraform output -raw primary_public_ip 2>&1)
SECONDARY_PUBLIC=$(terraform output -raw secondary_public_ip 2>&1)
PRIMARY_PRIVATE=$(terraform output -raw primary_private_ip 2>&1)

if [[ ! "$PRIMARY_PUBLIC" =~ ^[0-9]+.[0-9]+.[0-9]+.[0-9]+$ || 
      ! "$SECONDARY_PUBLIC" =~ ^[0-9]+.[0-9]+.[0-9]+.[0-9]+$ ]]; then
  echo "ERROR: No valid IPs from Terraform — run terraform apply first"
  exit 1
fi

PRIMARY_HOSTNAME="ip-$(echo "$PRIMARY_PRIVATE" | tr '.' '-')"

cat > "$SCRIPT_DIR/ansible/inventory.ini" <<EOF
[primary]
hana-primary ansible_host=${PRIMARY_PUBLIC} ansible_user=ec2-user ansible_ssh_private_key_file=~/.ssh/HANA_DEP.pem

[secondary]
hana-secondary ansible_host=${SECONDARY_PUBLIC} ansible_user=ec2-user ansible_ssh_private_key_file=~/.ssh/HANA_DEP.pem

[hana:children]
primary
secondary

[hana:vars]
ansible_ssh_common_args='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=30 -o ServerAliveCountMax=20'
primary_private_hostname=${PRIMARY_HOSTNAME}
EOF

The primary_private_hostname variable is critical — see the gotchas section.

./generate_inventory.sh

Step 3 — Install HANA on Both Nodes

hana-install.yml (Appendix A) runs against [hana] (both nodes in parallel). It handles all prerequisites — packages, kernel parameters, swap, ulimits, THP, saptune — downloads the HANA media from S3, and runs hdblcm in silent/batch mode.

Key points:

  • The IAM instance profile gives each node S3 read access — AWS CLI uses it automatically, no credentials needed on the instance
  • hdblcm is run with async: 3600, poll: 30 — installation takes 20–40 minutes and will time out a normal SSH connection
  • A stat check prevents hdblcm re-running if HANA is already installed
  • The backup directory /hana/backup is chowned to hdbadm:sapsys after hdblcm runs — hdbadm doesn’t exist until HANA installs
cd ansible
ansible-playbook -i inventory.ini hana-install.yml

Step 4 — Configure HSR

hsr-setup.yml (Appendix B) is three plays:

  1. Primary — take backups, enable HSR, copy PKI files to control node
  2. Secondary — stop HANA, push PKI files, register with primary, start HANA
  3. Primary — verify replication is ACTIVE
ansible-playbook -i inventory.ini hsr-setup.yml

The full playbook is in the appendix. The gotchas below explain every decision in it.


Gotchas

1. HANA MDC requires backups of BOTH databases before sr_enable

HANA 2.0 runs as Multi-Database Container (MDC) by default. There is a SYSTEMDB and one or more tenant databases (in this case HDB). hdbnsutil -sr_enable will refuse to run unless both have a complete data backup:

Backup has not yet been executed for database 'HDB'
Primary needs initial data backup for system replication!

A SYSTEMDB backup alone is not enough. The playbook runs both explicitly:

- name: Run SYSTEMDB backup
  shell: |
    su - hdbadm -c "
      hdbsql -i 00 -d SYSTEMDB -u SYSTEM -p 'Hana1234!' 
      "BACKUP DATA USING FILE ('systemdb_backup')"
    "
  changed_when: true

- name: Run tenant database backup
  shell: |
    su - hdbadm -c "
      hdbsql -i 00 -d HDB -u SYSTEM -p 'Hana1234!' 
      "BACKUP DATA USING FILE ('tenant_backup')"
    "
  changed_when: true

Then waits for each to appear in the backup catalog before proceeding:

- name: Wait for SYSTEMDB backup in catalog
  shell: |
    su - hdbadm -c "
      hdbsql -i 00 -d SYSTEMDB -u SYSTEM -p 'Hana1234!' 
      "SELECT COUNT(*) FROM SYS.M_BACKUP_CATALOG
        WHERE ENTRY_TYPE_NAME='complete data backup'
        AND STATE_NAME='successful'"
    "
  register: systemdb_backup_catalog
  until: (systemdb_backup_catalog.stdout | regex_search('[1-9][0-9]*')) is not none
  retries: 20
  delay: 15
  changed_when: false

Gotcha: regex_search() in Ansible returns a string (the match) or None — not a boolean. Using it bare in until: raises a “Conditional result was derived from value of type str” error. The is not none cast makes it a proper boolean.

2. PKI files 

HANA HSR uses PKI/SSFS files to authenticate replication between nodes. These must be copied from the primary to the secondary so both nodes trust each other. The files are:

/usr/sap/HDB/SYS/global/security/rsecssfs/data/SSFS_HDB.DAT
/usr/sap/HDB/SYS/global/security/rsecssfs/key/SSFS_HDB.KEY

The playbook copies them to /tmp on the primary, fetches them to the control node with Ansible’s fetch module, then pushes them to the secondary with copy:

- name: Copy PKI files to tmp
  shell: |
    cp /usr/sap/HDB/SYS/global/security/rsecssfs/data/SSFS_HDB.DAT /tmp/SSFS_HDB.DAT
    cp /usr/sap/HDB/SYS/global/security/rsecssfs/key/SSFS_HDB.KEY /tmp/SSFS_HDB.KEY
    chmod 644 /tmp/SSFS_HDB.DAT /tmp/SSFS_HDB.KEY
  changed_when: true

- name: Fetch PKI DAT to control node
  fetch:
    src: "/tmp/SSFS_HDB.DAT"
    dest: "/tmp/SSFS_HDB.DAT"
    flat: true

On the secondary:

- name: Push PKI DAT file to secondary
  copy:
    src: "/tmp/SSFS_HDB.DAT"
    dest: "/usr/sap/HDB/SYS/global/security/rsecssfs/data/SSFS_HDB.DAT"
    owner: hdbadm
    group: sapsys
    mode: "0600"

3. Use the EC2 private hostname, not Route 53

When registering the secondary with the primary, hdbnsutil -sr_register --remoteHost= must match the hostname that HANA itself registered under. HANA on AWS registers using the EC2 internal hostname, which is always derived from the private IP:

10.0.1.154  →  ip-10-0-1-154

Route 53 names like hana-primary.hana.internal will not match and the registration will fail with an SSL/PKI error that looks like a certificate problem but is actually a hostname mismatch.

generate_inventory.sh constructs the correct hostname from the Terraform private IP output and writes it to [hana:vars] in inventory.ini:

PRIMARY_HOSTNAME="ip-$(echo "$PRIMARY_PRIVATE" | tr '.' '-')"

The secondary play reads it as {{ primary_private_hostname }} — no hardcoding.

4. hdbadm doesn’t exist until HANA installs

The first time hana-install.yml runs, hdbadm and the sapsys group don’t exist yet — they’re created by hdblcm. Any task that references them (like setting ownership on /hana/backup) must run after the hdblcm task, not before.

5. Variables don’t cross plays

Ansible plays are isolated. A variable set in the primary play is not available in the secondary play. This is why primary_private_hostname is in inventory.ini under [hana:vars] rather than set in the primary play and referenced later. Inventory group vars are available to all hosts in all plays.


Verifying Replication

Once the playbook completes, SSH to the primary and run:

ssh -i ~/.ssh/<your-key-pair-name>.pem ec2-user@<primary-public-ip>
sudo su - hdbadm
python3 /usr/sap/HDB/HDB00/exe/python_support/systemReplicationStatus.py

A healthy output looks like:

|Database |Host          |Port  |Service Name |Site ID |Site Name |Secondary     |Replication |Status |Fully Synced |
|SYSTEMDB |ip-10-0-1-154 |30001 |nameserver   |      1 |SITE1     |ip-10-0-2-150 |SYNCMEM     |ACTIVE |        True |
|HDB      |ip-10-0-1-154 |30007 |xsengine     |      1 |SITE1     |ip-10-0-2-150 |SYNCMEM     |ACTIVE |        True |
|HDB      |ip-10-0-1-154 |30003 |indexserver  |      1 |SITE1     |ip-10-0-2-150 |SYNCMEM     |ACTIVE |        True |

overall system replication status: ACTIVE

All three services — nameserver, xsengine, indexserver — must show ACTIVE and Fully Synced: True.


Tear Down

cd terraform
terraform destroy -auto-approve

The S3 bucket and HANA media are unmanaged by Terraform and persist between runs — you only pay S3 storage rates between deployments.


Key Lessons

Problem Root Cause Fix

sr_enable: backup not executed MDC has SYSTEMDB + tenant — both need backups Explicit backup task for each DB with catalog wait
sr_register: SSL/PKI error Hostname mismatch — Route 53 name vs EC2 hostname Use ip-<private-ip> format, derived from Terraform output
PKI copy failing Wrong file path Files are at /usr/sap/HDB/SYS/global/security/rsecssfs/
hdbadm not found Running chown before hdblcm creates the user Move ownership task to after hdblcm
Backup catalog check error regex_search() returns str not bool Use is not none to cast to boolean
HANA re-installs on re-run No idempotency check stat check on hdbnsutil before running hdblcm

Appendix

A) hana-install.yml

---
- name: SAP HANA prerequisites and installation
  hosts: all
  become: true
  gather_facts: true

  vars:
    hana_sid: "HDB"
    hana_instance: "00"
    hana_master_pw: "<your-password>"
    sapadm_password: "<your-password>"
    lss_password: "<your-password>"
    hana_s3_bucket: "<your-s3-bucket-name>"
    hana_s3_prefix: "SAP_HANA_DATABASE/DATA_UNITS/HDB_SERVER_LINUX_X86_64"
    hana_media_dir: "/hana/media"
    hana_install_dir: "/hana/shared"
    hana_data_dir: "/hana/data"
    hana_log_dir: "/hana/log"
    swap_size_mb: 20480

  tasks:

    - name: Confirm target is SUSE
      fail:
        msg: "This playbook requires SUSE Linux Enterprise Server. Got: {{ ansible_os_family }}"
      when: ansible_os_family != 'Suse'

    - name: Refresh zypper repositories
      zypper_repository:
        repo: '*'
        runrefresh: true
      ignore_errors: true

    - name: Install required OS packages
      zypper:
        name:
          - libaio1
          - libaio-devel
          - libltdl7
          - libatomic1
          - libnuma1
          - numactl
          - uuidd
          - rsync
          - lsof
          - unzip
          - xfsprogs
          - glibc-i18ndata
          - graphviz
          - expect
          - python3
        state: present

    - name: Enable and start uuidd
      systemd:
        name: uuidd
        state: started
        enabled: true

    - name: Check existing swap
      command: swapon --show=SIZE --noheadings --bytes
      register: swap_check
      changed_when: false
      failed_when: false

    - name: Create swapfile
      command: dd if=/dev/zero of=/swapfile bs=1M count={{ swap_size_mb }}
      when: swap_check.stdout == ""

    - name: Set swapfile permissions
      file:
        path: /swapfile
        mode: "0600"
      when: swap_check.stdout == ""

    - name: Format swapfile
      command: mkswap /swapfile
      when: swap_check.stdout == ""

    - name: Activate swapfile
      command: swapon /swapfile
      when: swap_check.stdout == ""

    - name: Persist swapfile in fstab
      lineinfile:
        path: /etc/fstab
        line: "/swapfile swap swap defaults 0 0"
        state: present

    - name: Set kernel parameters for HANA
      sysctl:
        name: "{{ item.key }}"
        value: "{{ item.value }}"
        sysctl_set: true
        state: present
        reload: true
      loop:
        - { key: "vm.max_map_count",            value: "2147483647" }
        - { key: "kernel.sem",                   value: "1250 256000 100 8192" }
        - { key: "net.core.somaxconn",           value: "4096" }
        - { key: "net.ipv4.tcp_max_syn_backlog", value: "8192" }
        - { key: "kernel.numa_balancing",        value: "0" }

    - name: Set ulimits for SAP
      copy:
        dest: /etc/security/limits.d/99-sap.conf
        mode: "0644"
        content: |
          @sapsys  soft  nofile  1048576
          @sapsys  hard  nofile  1048576
          @sapsys  soft  nproc   unlimited
          @sapsys  hard  nproc   unlimited
          @sdba    soft  nofile  1048576
          @sdba    hard  nofile  1048576

    - name: Disable THP at runtime
      shell: |
        echo never > /sys/kernel/mm/transparent_hugepage/enabled
        echo never > /sys/kernel/mm/transparent_hugepage/defrag
      changed_when: true

    - name: Create systemd unit to disable THP on boot
      copy:
        dest: /etc/systemd/system/disable-thp.service
        mode: "0644"
        content: |
          [Unit]
          Description=Disable Transparent Huge Pages
          After=network.target
          [Service]
          Type=oneshot
          ExecStart=/bin/sh -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag"
          RemainAfterExit=yes
          [Install]
          WantedBy=multi-user.target

    - name: Enable disable-thp service
      systemd:
        name: disable-thp
        enabled: true
        daemon_reload: true

    - name: Create HANA filesystem directories
      file:
        path: "{{ item }}"
        state: directory
        mode: "0755"
      loop:
        - "{{ hana_install_dir }}"
        - "{{ hana_data_dir }}"
        - "{{ hana_log_dir }}"
        - "/hana/backup"

    - name: Install saptune
      zypper:
        name: saptune
        state: present
      ignore_errors: true

    - name: Apply SAP HANA tuning profile via saptune
      command: saptune solution apply HANA
      changed_when: true
      ignore_errors: true

    - name: Enable saptune daemon
      systemd:
        name: saptune
        enabled: true
        state: started
      ignore_errors: true

    - name: Check if aws cli is present
      command: which aws
      register: aws_cli_check
      changed_when: false
      failed_when: false

    - name: Download and install AWS CLI v2
      shell: |
        curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
        unzip -q /tmp/awscliv2.zip -d /tmp/awscli-install
        /tmp/awscli-install/aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli --update
        rm -rf /tmp/awscliv2.zip /tmp/awscli-install
      when: aws_cli_check.rc != 0

    - name: Sync HANA media from S3
      command: >
        aws s3 sync s3://{{ hana_s3_bucket }}/{{ hana_s3_prefix }}/ {{ hana_media_dir }}/
        --no-progress
      register: s3_sync
      changed_when: "'download' in s3_sync.stdout"

    - name: Find hdblcm installer
      find:
        paths: "{{ hana_media_dir }}"
        patterns: "hdblcm"
        recurse: true
      register: hdblcm_find

    - name: Fail if hdblcm not found
      fail:
        msg: "hdblcm not found under {{ hana_media_dir }} — check hana_s3_bucket and hana_s3_prefix"
      when: hdblcm_find.files | length == 0

    - name: Set hdblcm path fact
      set_fact:
        hdblcm_path: "{{ hdblcm_find.files[0].path }}"

    - name: Make hdblcm executable
      file:
        path: "{{ hdblcm_path }}"
        mode: "0755"

    - name: Write hdblcm silent response file
      template:
        src: hana_install.cfg.j2
        dest: /tmp/hana_install.cfg
        mode: "0600"
        force: true

    - name: Move LSS media aside
      command: mv {{ hana_media_dir }}/lss {{ hana_media_dir }}/lss_aside
      args:
        removes: "{{ hana_media_dir }}/lss"

    - name: Make media directory fully executable
      shell: chmod -R 755 {{ hana_media_dir }}
      changed_when: true

    - name: Check if HANA already installed
      stat:
        path: "/usr/sap/{{ hana_sid }}/HDB{{ hana_instance }}/exe/hdbnsutil"
      register: hana_installed

    - name: Run SAP HANA installation (hdblcm)
      command: >
        {{ hdblcm_path }} --batch --configfile=/tmp/hana_install.cfg
      register: hdblcm_result
      changed_when: true
      async: 3600
      poll: 30
      when: not hana_installed.stat.exists

    - name: Show hdblcm output
      debug:
        msg: "{{ hdblcm_result.stdout_lines | default(['HANA already installed, skipped']) }}"

    - name: Set backup directory ownership for hdbadm
      file:
        path: /hana/backup
        owner: hdbadm
        group: sapsys
        recurse: true

    - name: Check HDB daemon is running
      shell: |
        su - {{ hana_sid | lower }}adm -c "HDB info" 2>&1 | grep -E "hdbdaemon|hdbnameserver"
      register: hdb_info
      changed_when: false
      failed_when: hdb_info.rc != 0

    - name: Confirm HANA installation complete
      debug:
        msg: >
          SAP HANA {{ hana_sid }} instance {{ hana_instance }} installed successfully.
          Connect: hdbsql -i {{ hana_instance }} -u SYSTEM -p <password>

B) hsr-setup.yml

---
- name: Enable HSR on primary
  hosts: primary
  become: true
  gather_facts: true

  vars:
    hana_sid: "HDB"
    hana_instance: "00"
    hana_master_pw: "<your-password>"
    hsr_replication_mode: "syncmem"
    hsr_operation_mode: "logreplay"
    hsr_site_name_primary: "SITE1"

  tasks:

    - name: Run SYSTEMDB backup
      shell: |
        su - {{ hana_sid | lower }}adm -c "
          hdbsql -i {{ hana_instance }} -d SYSTEMDB -u SYSTEM -p '{{ hana_master_pw }}' 
          "BACKUP DATA USING FILE ('systemdb_backup')"
        "
      changed_when: true

    - name: Run tenant database backup
      shell: |
        su - {{ hana_sid | lower }}adm -c "
          hdbsql -i {{ hana_instance }} -d {{ hana_sid }} -u SYSTEM -p '{{ hana_master_pw }}' 
          "BACKUP DATA USING FILE ('tenant_backup')"
        "
      changed_when: true

    - name: Wait for SYSTEMDB backup in catalog
      shell: |
        su - {{ hana_sid | lower }}adm -c "
          hdbsql -i {{ hana_instance }} -d SYSTEMDB -u SYSTEM -p '{{ hana_master_pw }}' 
          "SELECT COUNT(*) FROM SYS.M_BACKUP_CATALOG WHERE ENTRY_TYPE_NAME='complete data backup' AND STATE_NAME='successful'"
        "
      register: systemdb_backup_catalog
      until: (systemdb_backup_catalog.stdout | regex_search('[1-9][0-9]*')) is not none
      retries: 20
      delay: 15
      changed_when: false

    - name: Wait for tenant backup in catalog
      shell: |
        su - {{ hana_sid | lower }}adm -c "
          hdbsql -i {{ hana_instance }} -d {{ hana_sid }} -u SYSTEM -p '{{ hana_master_pw }}' 
          "SELECT COUNT(*) FROM SYS.M_BACKUP_CATALOG WHERE ENTRY_TYPE_NAME='complete data backup' AND STATE_NAME='successful'"
        "
      register: tenant_backup_catalog
      until: (tenant_backup_catalog.stdout | regex_search('[1-9][0-9]*')) is not none
      retries: 20
      delay: 15
      changed_when: false

    - name: Check if HSR already enabled on primary
      shell: |
        su - {{ hana_sid | lower }}adm -c "hdbnsutil -sr_state"
      register: sr_state_check
      changed_when: false

    - name: Enable system replication on primary
      shell: |
        su - {{ hana_sid | lower }}adm -c "
          hdbnsutil -sr_enable --name={{ hsr_site_name_primary }}
        "
      changed_when: true
      when: "'mode: primary' not in sr_state_check.stdout"
      ignore_errors: true

    - name: Confirm primary HSR state
      shell: |
        su - {{ hana_sid | lower }}adm -c "hdbnsutil -sr_state"
      register: sr_state_primary
      changed_when: false

    - name: Show primary HSR state
      debug:
        msg: "{{ sr_state_primary.stdout_lines }}"

    - name: Fail if primary not in HSR primary mode
      fail:
        msg: "Primary HSR enable failed"
      when: "'mode: primary' not in sr_state_primary.stdout"

    - name: Copy PKI files to tmp
      shell: |
        cp /usr/sap/{{ hana_sid }}/SYS/global/security/rsecssfs/data/SSFS_{{ hana_sid }}.DAT /tmp/SSFS_{{ hana_sid }}.DAT
        cp /usr/sap/{{ hana_sid }}/SYS/global/security/rsecssfs/key/SSFS_{{ hana_sid }}.KEY /tmp/SSFS_{{ hana_sid }}.KEY
        chmod 644 /tmp/SSFS_{{ hana_sid }}.DAT /tmp/SSFS_{{ hana_sid }}.KEY
      changed_when: true

    - name: Fetch PKI DAT to control node
      fetch:
        src: "/tmp/SSFS_{{ hana_sid }}.DAT"
        dest: "/tmp/SSFS_{{ hana_sid }}.DAT"
        flat: true

    - name: Fetch PKI KEY to control node
      fetch:
        src: "/tmp/SSFS_{{ hana_sid }}.KEY"
        dest: "/tmp/SSFS_{{ hana_sid }}.KEY"
        flat: true

- name: Stop HANA on secondary and push PKI files
  hosts: secondary
  become: true
  gather_facts: true

  vars:
    hana_sid: "HDB"
    hana_instance: "00"
    hana_master_pw: "<your-password>"
    hsr_replication_mode: "syncmem"
    hsr_operation_mode: "logreplay"
    hsr_site_name_secondary: "SITE2"

  tasks:

    - name: Stop HANA on secondary
      shell: |
        su - {{ hana_sid | lower }}adm -c "HDB stop"
      changed_when: true

    - name: Wait for HANA to fully stop
      shell: |
        su - {{ hana_sid | lower }}adm -c "HDB info" 2>&1 | grep -c hdbdaemon || true
      register: stop_check
      until: stop_check.stdout | int == 0
      retries: 20
      delay: 15
      changed_when: false

    - name: Push PKI DAT file to secondary
      copy:
        src: "/tmp/SSFS_{{ hana_sid }}.DAT"
        dest: "/usr/sap/{{ hana_sid }}/SYS/global/security/rsecssfs/data/SSFS_{{ hana_sid }}.DAT"
        owner: hdbadm
        group: sapsys
        mode: "0600"

    - name: Push PKI KEY file to secondary
      copy:
        src: "/tmp/SSFS_{{ hana_sid }}.KEY"
        dest: "/usr/sap/{{ hana_sid }}/SYS/global/security/rsecssfs/key/SSFS_{{ hana_sid }}.KEY"
        owner: hdbadm
        group: sapsys
        mode: "0600"

    - name: Register secondary with primary
      shell: |
        su - {{ hana_sid | lower }}adm -c "
          hdbnsutil -sr_register 
            --name={{ hsr_site_name_secondary }} 
            --remoteHost={{ primary_private_hostname }} 
            --remoteInstance={{ hana_instance }} 
            --replicationMode={{ hsr_replication_mode }} 
            --operationMode={{ hsr_operation_mode }}
        "
      register: sr_register
      changed_when: true

    - name: Show registration output
      debug:
        msg: "{{ sr_register.stdout_lines }}"

    - name: Start HANA on secondary
      shell: |
        su - {{ hana_sid | lower }}adm -c "HDB start"
      changed_when: true
      async: 600
      poll: 30

- name: Verify replication state from primary
  hosts: primary
  become: true
  gather_facts: false

  vars:
    hana_sid: "HDB"
    hana_instance: "00"

  tasks:

    - name: Wait for secondary to connect
      shell: |
        su - {{ hana_sid | lower }}adm -c "
          python3 /usr/sap/{{ hana_sid }}/{{ hana_sid }}{{ hana_instance }}/exe/python_support/systemReplicationStatus.py
        " 2>&1 || true
      register: hsr_status
      until: "'ACTIVE' in hsr_status.stdout or 'SYNCING' in hsr_status.stdout"
      retries: 20
      delay: 15
      changed_when: false

    - name: Show replication status
      debug:
        msg: "{{ hsr_status.stdout_lines }}"

    - name: Confirm replication is active
      fail:
        msg: "HSR did not reach ACTIVE or SYNCING state"
      when: "'ACTIVE' not in hsr_status.stdout and 'SYNCING' not in hsr_status.stdout"

    - name: Final replication status
      shell: |
        su - {{ hana_sid | lower }}adm -c "
          python3 /usr/sap/{{ hana_sid }}/{{ hana_sid }}{{ hana_instance }}/exe/python_support/systemReplicationStatus.py
        "
      register: final_status
      changed_when: false
      ignore_errors: true

    - name: Show final replication status
      debug:
        msg: "{{ final_status.stdout_lines }}"  

 

“}]] 

  Read More Technology Blog Posts by Members articles 

#abap

By ali

Leave a Reply