Skip to content

Commit d630035

Browse files
committed
feat: enhance Azure Backup setup and restore tasks for HANA with support for standalone mode and improved variable handling
1 parent 89a3810 commit d630035

2 files changed

Lines changed: 128 additions & 56 deletions

File tree

scripts/setup_azure_backup.sh

Lines changed: 108 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
#
5-
# setup_azure_backup.sh — Automate Azure Backup setup for SAP HANA (HSR)
5+
# setup_azure_backup.sh — Automate Azure Backup setup for SAP HANA (HSR or standalone)
66
#
77
# Steps:
88
# 1. Create a Recovery Services vault
9-
# 2. Run the pre-registration script on both VMs via az vm run-command
10-
# 3. Register both VMs (containers) to the vault
9+
# 2. Run the pre-registration script on VM(s) via az vm run-command
10+
# 3. Register VM(s) (containers) to the vault
1111
# 4. Discover protectable HANA databases
1212
# 5. Create a backup policy (full + log + differential)
1313
# 6. Enable protection on discovered databases
1414
#
15+
# Supports both HSR (two-VM) and standalone (single-VM) modes.
16+
# When --secondary-vm and --hsr-unique-id are omitted, runs in standalone mode.
17+
#
1518
# Reference:
1619
# https://learn.microsoft.com/en-us/azure/backup/quick-backup-hana-cli
1720
# https://learn.microsoft.com/en-us/azure/backup/sap-hana-database-with-hana-system-replication-backup
@@ -23,6 +26,7 @@ set -euo pipefail
2326
# ──────────────────────────────────────────────
2427
SUBSCRIPTION="${SUBSCRIPTION:-}"
2528
RESOURCE_GROUP="${RESOURCE_GROUP:-}"
29+
VM_RESOURCE_GROUP="${VM_RESOURCE_GROUP:-}" # RG containing the VMs (defaults to RESOURCE_GROUP)
2630
LOCATION="${LOCATION:-}"
2731
VAULT_NAME="${VAULT_NAME:-}"
2832
PRIMARY_VM="${PRIMARY_VM:-}"
@@ -34,6 +38,7 @@ HSR_UNIQUE_ID="${HSR_UNIQUE_ID:-}"
3438
POLICY_NAME="${POLICY_NAME:-sap-hana-backup-policy}"
3539
STORAGE_REDUNDANCY="${STORAGE_REDUNDANCY:-GeoRedundant}"
3640
DATABASE_NAMES="${DATABASE_NAMES:-}" # comma-separated, e.g. "SYSTEMDB,DB1"
41+
HA_MODE=false # auto-detected: true when secondary-vm + hsr-unique-id provided
3742

3843
# Pre-registration script URL
3944
PREREG_SCRIPT_URL="https://aka.ms/ScriptForPermsOnHANA"
@@ -60,15 +65,18 @@ Usage: setup_azure_backup.sh [OPTIONS]
6065
6166
Required:
6267
--subscription Azure Subscription ID
63-
--resource-group Resource group containing the HANA VMs
68+
--resource-group Resource group for the Recovery Services vault
6469
--location Azure region (e.g., eastus2)
6570
--vault-name Recovery Services vault name
6671
--primary-vm Primary HANA VM name
67-
--secondary-vm Secondary HANA VM name
6872
--sid SAP HANA System ID (e.g., HDB)
73+
74+
HSR mode (both required for HA):
75+
--secondary-vm Secondary HANA VM name
6976
--hsr-unique-id Unique HSR identifier (6-35 chars, alphanumeric)
7077
7178
Optional:
79+
--vm-resource-group Resource group containing the HANA VMs (default: same as --resource-group)
7280
--instance-number HANA instance number (default: 00)
7381
--backup-key hdbuserstore key for backup user (default: AZUREWLBACKUPHANAUSER)
7482
--policy-name Backup policy name (default: sap-hana-backup-policy)
@@ -80,7 +88,7 @@ Optional:
8088
-h, --help Show this help
8189
8290
Examples:
83-
# Full setup for HSR
91+
# Full setup for HSR (two VMs, same resource group)
8492
./setup_azure_backup.sh \
8593
--subscription "aaaa-bbbb-cccc" \
8694
--resource-group "sap-hana-rg" \
@@ -92,6 +100,16 @@ Examples:
92100
--instance-number "00" \
93101
--hsr-unique-id "HSRProd01"
94102
103+
# Standalone (non-HA) single VM, vault in a different RG
104+
./setup_azure_backup.sh \
105+
--subscription "aaaa-bbbb-cccc" \
106+
--resource-group "vault-rg" \
107+
--vm-resource-group "vm-rg" \
108+
--location "eastus2" \
109+
--vault-name "hana-vault" \
110+
--primary-vm "hana-vm1" \
111+
--sid "HDB"
112+
95113
# Skip vault creation, use existing
96114
./setup_azure_backup.sh \
97115
--subscription "aaaa-bbbb-cccc" \
@@ -115,6 +133,7 @@ parse_args() {
115133
case "$1" in
116134
--subscription) SUBSCRIPTION="$2"; shift 2 ;;
117135
--resource-group) RESOURCE_GROUP="$2"; shift 2 ;;
136+
--vm-resource-group) VM_RESOURCE_GROUP="$2"; shift 2 ;;
118137
--location) LOCATION="$2"; shift 2 ;;
119138
--vault-name) VAULT_NAME="$2"; shift 2 ;;
120139
--primary-vm) PRIMARY_VM="$2"; shift 2 ;;
@@ -142,21 +161,35 @@ validate_params() {
142161
[[ -z "$LOCATION" ]] && missing+=("--location")
143162
[[ -z "$VAULT_NAME" ]] && missing+=("--vault-name")
144163
[[ -z "$PRIMARY_VM" ]] && missing+=("--primary-vm")
145-
[[ -z "$SECONDARY_VM" ]] && missing+=("--secondary-vm")
146164
[[ -z "$SID" ]] && missing+=("--sid")
147-
[[ -z "$HSR_UNIQUE_ID" ]] && missing+=("--hsr-unique-id")
148165

149166
if [[ ${#missing[@]} -gt 0 ]]; then
150167
fatal "Missing required parameters: ${missing[*]}"
151168
fi
152169

153-
# Validate HSR unique ID format (6-35 chars, must have digit+lowercase+uppercase)
154-
if [[ ${#HSR_UNIQUE_ID} -lt 6 || ${#HSR_UNIQUE_ID} -gt 35 ]]; then
155-
fatal "HSR_UNIQUE_ID must be 6-35 characters. Got: ${#HSR_UNIQUE_ID}"
170+
# Default VM_RESOURCE_GROUP to RESOURCE_GROUP if not specified
171+
VM_RESOURCE_GROUP="${VM_RESOURCE_GROUP:-$RESOURCE_GROUP}"
172+
173+
# Determine HA vs standalone mode
174+
if [[ -n "$SECONDARY_VM" && -n "$HSR_UNIQUE_ID" ]]; then
175+
HA_MODE=true
176+
info "Mode: HSR (High Availability) — two VMs"
177+
elif [[ -n "$SECONDARY_VM" || -n "$HSR_UNIQUE_ID" ]]; then
178+
fatal "HSR mode requires both --secondary-vm and --hsr-unique-id. Provide both or neither."
179+
else
180+
HA_MODE=false
181+
info "Mode: Standalone (single VM, non-HA)"
182+
fi
183+
184+
# Validate HSR unique ID format when in HA mode
185+
if [[ "$HA_MODE" == "true" ]]; then
186+
if [[ ${#HSR_UNIQUE_ID} -lt 6 || ${#HSR_UNIQUE_ID} -gt 35 ]]; then
187+
fatal "HSR_UNIQUE_ID must be 6-35 characters. Got: ${#HSR_UNIQUE_ID}"
188+
fi
156189
fi
157190

158-
# Validate combined VM name + RG length <= 84
159-
local combined_len=$(( ${#PRIMARY_VM} + ${#RESOURCE_GROUP} ))
191+
# Validate combined VM name + VM RG length <= 84
192+
local combined_len=$(( ${#PRIMARY_VM} + ${#VM_RESOURCE_GROUP} ))
160193
if [[ $combined_len -gt 84 ]]; then
161194
fatal "Combined VM name + resource group length ($combined_len) exceeds 84 characters."
162195
fi
@@ -181,9 +214,9 @@ get_vm_resource_id() {
181214
local vm_name="$1"
182215
az vm show \
183216
--name "$vm_name" \
184-
--resource-group "$RESOURCE_GROUP" \
217+
--resource-group "$VM_RESOURCE_GROUP" \
185218
--query "id" -o tsv 2>/dev/null \
186-
|| fatal "VM '$vm_name' not found in resource group '$RESOURCE_GROUP'"
219+
|| fatal "VM '$vm_name' not found in resource group '$VM_RESOURCE_GROUP'"
187220
}
188221

189222
# ──────────────────────────────────────────────
@@ -269,13 +302,28 @@ run_prereg_script() {
269302
# MDC port format: 3<instance_number>13
270303
port="3${INSTANCE_NUMBER}13"
271304

272-
info "Running pre-registration script on both VMs..."
273-
info " SID=$SID, Instance=$INSTANCE_NUMBER, Port=$port, HSR_ID=$HSR_UNIQUE_ID"
305+
# Build VM list
306+
local -a vm_list=("$PRIMARY_VM")
307+
if [[ "$HA_MODE" == "true" ]]; then
308+
vm_list+=("$SECONDARY_VM")
309+
fi
310+
311+
info "Running pre-registration script on ${#vm_list[@]} VM(s)..."
312+
info " SID=$SID, Instance=$INSTANCE_NUMBER, Port=$port"
313+
if [[ "$HA_MODE" == "true" ]]; then
314+
info " HSR_ID=$HSR_UNIQUE_ID"
315+
fi
274316

275-
for vm_name in "$PRIMARY_VM" "$SECONDARY_VM"; do
317+
# Build HSR-specific flags for the pre-registration script
318+
local hsr_flags=""
319+
if [[ "$HA_MODE" == "true" ]]; then
320+
hsr_flags="-hn ${HSR_UNIQUE_ID}"
321+
fi
322+
323+
for vm_name in "${vm_list[@]}"; do
276324
info "Running pre-registration script on '$vm_name'..."
277325
az vm run-command invoke \
278-
--resource-group "$RESOURCE_GROUP" \
326+
--resource-group "$VM_RESOURCE_GROUP" \
279327
--name "$vm_name" \
280328
--command-id RunShellScript \
281329
--scripts "
@@ -293,7 +341,7 @@ run_prereg_script() {
293341
-n ${INSTANCE_NUMBER} \
294342
-sk SYSTEMKEY \
295343
-bk ${BACKUP_KEY} \
296-
-hn ${HSR_UNIQUE_ID} \
344+
${hsr_flags} \
297345
-p ${port} \
298346
-sn
299347
" \
@@ -309,7 +357,13 @@ run_prereg_script() {
309357
register_containers() {
310358
info "Registering VM containers to vault..."
311359

312-
for vm_name in "$PRIMARY_VM" "$SECONDARY_VM"; do
360+
# Build VM list
361+
local -a vm_list=("$PRIMARY_VM")
362+
if [[ "$HA_MODE" == "true" ]]; then
363+
vm_list+=("$SECONDARY_VM")
364+
fi
365+
366+
for vm_name in "${vm_list[@]}"; do
313367
local resource_id
314368
resource_id=$(get_vm_resource_id "$vm_name")
315369

@@ -341,7 +395,7 @@ register_containers() {
341395

342396
info "Container registration complete."
343397

344-
# Verify both registered
398+
# Verify registered containers
345399
info "Registered containers:"
346400
az backup container list \
347401
--resource-group "$RESOURCE_GROUP" \
@@ -356,7 +410,7 @@ register_containers() {
356410
discover_databases() {
357411
info "Initiating database discovery on primary VM..."
358412

359-
local container_name="VMAppContainer;Compute;${RESOURCE_GROUP};${PRIMARY_VM}"
413+
local container_name="VMAppContainer;Compute;${VM_RESOURCE_GROUP};${PRIMARY_VM}"
360414

361415
az backup protectable-item initialize \
362416
--resource-group "$RESOURCE_GROUP" \
@@ -512,19 +566,21 @@ POLICY_EOF
512566
enable_protection() {
513567
info "Enabling backup protection on discovered databases..."
514568

515-
# Get HSR container parent name for --server-name parameter
516-
local hsr_parent
517-
hsr_parent=$(az backup protectable-item list \
518-
--resource-group "$RESOURCE_GROUP" \
519-
--vault-name "$VAULT_NAME" \
520-
--workload-type SAPHANA \
521-
--query "[?properties.protectableItemType=='HanaHSRContainer'].properties.parentName" \
522-
-o tsv 2>/dev/null | head -1)
569+
# In HA mode, look for HSR container parent for --server-name parameter
570+
local hsr_parent=""
571+
if [[ "$HA_MODE" == "true" ]]; then
572+
hsr_parent=$(az backup protectable-item list \
573+
--resource-group "$RESOURCE_GROUP" \
574+
--vault-name "$VAULT_NAME" \
575+
--workload-type SAPHANA \
576+
--query "[?properties.protectableItemType=='HanaHSRContainer'].properties.parentName" \
577+
-o tsv 2>/dev/null | head -1)
523578

524-
if [[ -z "$hsr_parent" ]]; then
525-
warn "No HSR container found. Databases may need to be protected individually."
526-
else
527-
info "HSR container parent: $hsr_parent"
579+
if [[ -z "$hsr_parent" ]]; then
580+
warn "No HSR container found. Databases may need to be protected individually."
581+
else
582+
info "HSR container parent: $hsr_parent"
583+
fi
528584
fi
529585

530586
# Get list of SAPHanaDatabase items
@@ -605,17 +661,25 @@ main() {
605661
validate_params
606662
check_az_cli
607663

664+
local mode_label="Standalone"
665+
if [[ "$HA_MODE" == "true" ]]; then
666+
mode_label="HSR (High Availability)"
667+
fi
668+
608669
info "============================================="
609-
info "Azure Backup Setup for SAP HANA (HSR)"
670+
info "Azure Backup Setup for SAP HANA $mode_label"
610671
info "============================================="
611-
info " Vault: $VAULT_NAME"
612-
info " Resource Group: $RESOURCE_GROUP"
613-
info " Location: $LOCATION"
614-
info " Primary VM: $PRIMARY_VM"
615-
info " Secondary VM: $SECONDARY_VM"
616-
info " SID: $SID"
617-
info " HSR Unique ID: $HSR_UNIQUE_ID"
618-
info " Policy: $POLICY_NAME"
672+
info " Vault: $VAULT_NAME"
673+
info " Vault RG: $RESOURCE_GROUP"
674+
info " VM RG: $VM_RESOURCE_GROUP"
675+
info " Location: $LOCATION"
676+
info " Primary VM: $PRIMARY_VM"
677+
if [[ "$HA_MODE" == "true" ]]; then
678+
info " Secondary VM: $SECONDARY_VM"
679+
info " HSR Unique ID: $HSR_UNIQUE_ID"
680+
fi
681+
info " SID: $SID"
682+
info " Policy: $POLICY_NAME"
619683
info "============================================="
620684
echo ""
621685

src/roles/backup_db_hana/tasks/restore-to-db.yml

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@
5959
ansible.builtin.set_fact:
6060
backup_primary_node: "{{ ansible_hostname }}"
6161
backup_secondary_node: ""
62+
backup_restore_vm_name: "{{ azure_instance_metadata.json.compute.name }}"
63+
backup_restore_vm_rg: "{{ azure_instance_metadata.json.compute.resourceGroupName }}"
64+
backup_restore_subscription: "{{ azure_instance_metadata.json.compute.subscriptionId }}"
65+
run_once: true
66+
when: not (database_high_availability | default(false) | bool)
67+
68+
- name: "Pre Validation: Build standalone restore container"
69+
ansible.builtin.set_fact:
70+
backup_restore_target_container: >-
71+
VMAppContainer;Compute;{{ backup_restore_vm_rg }};{{ backup_restore_vm_name }}
6272
run_once: true
6373
when: not (database_high_availability | default(false) | bool)
6474

@@ -125,7 +135,7 @@
125135
run_once: true
126136
changed_when: hsr_secondary_stop.rc == 0
127137
failed_when: false
128-
when: database_high_availability | bool
138+
when: database_high_availability | default(false) | bool
129139

130140
- name: "Test Execution: HSR - Unregister secondary from replication"
131141
become_user: "{{ db_sid | lower }}adm"
@@ -135,7 +145,7 @@
135145
run_once: true
136146
changed_when: hsr_unregister_result.rc == 0
137147
failed_when: false
138-
when: database_high_availability | bool
148+
when: database_high_availability | default(false) | bool
139149

140150
- name: "Test Execution: HSR - Disable replication on primary"
141151
become_user: "{{ db_sid | lower }}adm"
@@ -150,7 +160,7 @@
150160
run_once: true
151161
changed_when: hsr_disable_result.rc == 0
152162
failed_when: false
153-
when: database_high_availability | bool
163+
when: database_high_availability | default(false) | bool
154164

155165
- name: "Test Execution: Stop HANA DB before restore"
156166
become_user: "{{ db_sid | lower }}adm"
@@ -176,11 +186,10 @@
176186
container_name: "{{ db_item.container_name }}"
177187
item_name: "{{ db_item.item_name }}"
178188
restore_point_time: "{{ backup_restore_point_time | default('') }}"
179-
target_container_name: "{{ (backup_restore_target_container | trim) if (database_high_availability | default(false) | bool) else '' }}"
180-
target_database_name: "{{ ((db_sid | upper) ~ '/' ~ db_item.name) if (database_high_availability | default(false) | bool) else '' }}"
189+
target_container_name: "{{ backup_restore_target_container | default('') | trim }}"
190+
target_database_name: "{{ (db_sid | upper) ~ '/' ~ db_item.name }}"
181191
source_resource_id: >-
182-
{{ '/subscriptions/' ~ backup_restore_subscription ~ '/resourceGroups/' ~ backup_restore_vm_rg ~ '/providers/Microsoft.Compute/virtualMachines/' ~ backup_restore_vm_name
183-
if (database_high_availability | default(false) | bool) else '' }}
192+
{{ '/subscriptions/' ~ backup_restore_subscription ~ '/resourceGroups/' ~ backup_restore_vm_rg ~ '/providers/Microsoft.Compute/virtualMachines/' ~ backup_restore_vm_name }}
184193
register: systemdb_restore_results
185194
run_once: true
186195
failed_when: systemdb_restore_results.status | default('') == 'ERROR'
@@ -264,11 +273,10 @@
264273
container_name: "{{ db_item.container_name }}"
265274
item_name: "{{ db_item.item_name }}"
266275
restore_point_time: "{{ backup_restore_point_time | default('') }}"
267-
target_container_name: "{{ (backup_restore_target_container | trim) if (database_high_availability | default(false) | bool) else '' }}"
268-
target_database_name: "{{ ((db_sid | upper) ~ '/' ~ db_item.name) if (database_high_availability | default(false) | bool) else '' }}"
276+
target_container_name: "{{ backup_restore_target_container | default('') | trim }}"
277+
target_database_name: "{{ (db_sid | upper) ~ '/' ~ db_item.name }}"
269278
source_resource_id: >-
270-
{{ '/subscriptions/' ~ backup_restore_subscription ~ '/resourceGroups/' ~ backup_restore_vm_rg ~ '/providers/Microsoft.Compute/virtualMachines/' ~ backup_restore_vm_name
271-
if (database_high_availability | default(false) | bool) else '' }}
279+
{{ '/subscriptions/' ~ backup_restore_subscription ~ '/resourceGroups/' ~ backup_restore_vm_rg ~ '/providers/Microsoft.Compute/virtualMachines/' ~ backup_restore_vm_name }}
272280
register: tenant_restore_results
273281
run_once: true
274282
failed_when: tenant_restore_results.status | default('') == 'ERROR'
@@ -341,7 +349,7 @@
341349
failed_when: false
342350

343351
- name: "Test Execution: HSR - Re-establish replication"
344-
when: database_high_availability | bool
352+
when: database_high_availability | default(false) | bool
345353
block:
346354

347355
- name: "Test Execution: HSR - Enable replication on primary"

0 commit comments

Comments
 (0)