Skip to content

Commit 6f01c5d

Browse files
authored
[Backup] az backup: Add support for HANA Snapshot (#27932)
1 parent 088ca11 commit 6f01c5d

File tree

8 files changed

+262
-17
lines changed

8 files changed

+262
-17
lines changed

src/azure-cli/azure/cli/command_modules/backup/_params.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
# ARGUMENT DEFINITIONS
2222

2323
allowed_container_types = ['AzureIaasVM']
24-
allowed_workload_types = ['VM', 'AzureFileShare', 'SAPHANA', 'SAPASE', 'MSSQL', 'SAPHanaDatabase', 'SQLDataBase', 'SAPAseDatabase']
25-
allowed_azure_workload_types = ['MSSQL', 'SAPHANA', 'SAPASE', 'SAPAseDatabase', 'SAPHanaDatabase', 'SQLDataBase']
24+
allowed_workload_types = ['VM', 'AzureFileShare', 'SAPHANA', 'SAPASE', 'MSSQL', 'SAPHanaDatabase', 'SQLDataBase', 'SAPAseDatabase', 'SAPHanaDBInstance']
25+
allowed_azure_workload_types = ['MSSQL', 'SAPHANA', 'SAPASE', 'SAPAseDatabase', 'SAPHanaDatabase', 'SQLDataBase', 'SAPHanaDBInstance']
2626
allowed_backup_management_types = ['AzureIaasVM', 'AzureStorage', 'AzureWorkload']
2727
allowed_extended_backup_management_types = allowed_backup_management_types + ['MAB']
28-
allowed_protectable_item_type = ['SQLAG', 'SQLInstance', 'SQLDatabase', 'HANAInstance', 'SAPAseDatabase', 'SAPHanaDatabase', 'SAPHanaSystem']
28+
allowed_protectable_item_type = ['SQLAG', 'SQLInstance', 'SQLDatabase', 'HANAInstance', 'SAPAseDatabase', 'SAPHanaDatabase', 'SAPHanaSystem', 'SAPHanaDBInstance']
2929
allowed_target_tier_type_chk_archivable = ['VaultArchive']
3030
allowed_tier_type = ['VaultStandard', 'Snapshot', 'VaultArchive', 'VaultStandardRehydrated', 'SnapshotAndVaultStandard', 'SnapshotAndVaultArchive']
3131
allowed_rehyd_priority_type = ['Standard', 'High']
@@ -453,6 +453,9 @@ def load_arguments(self, _):
453453
c.argument('target_resource_group', options_list=['--target-resource-group'], help="""Specify the resource group of target item for Cross Region Restore. Default value will be same as --resource-group if not specified.""")
454454
c.argument('target_vault_name', options_list=['--target-vault-name'], help="""Specify the vault name of target item for Cross Region Restore. Default value will be same as --vault-name if not specified.""")
455455
c.argument('target_subscription_id', help="""Specify the subscription of the target item for Cross Subscription Restore. Defaulted to source subscription if not specified.""")
456+
c.argument('attach_and_mount', arg_type=get_three_state_flag(), help='Specify attach and mount value for HANA Snapshot restores.')
457+
c.argument('identity_arm_id', help='Set Identity ARM ID for HANA Snapshot restores.')
458+
c.argument('snapshot_instance_resource_group', options_list=['--snapshot-rg'], help="""Specify the resource group for HANA Snapshot Instance restores. If not provided, the default value will be fetched from the target container details.""")
456459

457460
# Job
458461
with self.argument_context('backup job') as c:

src/azure-cli/azure/cli/command_modules/backup/_validators.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
55

6+
# pylint: disable=line-too-long
7+
68
from datetime import datetime
79
from azure.cli.core.azclierror import RequiredArgumentMissingError, MutuallyExclusiveArgumentError, \
810
ArgumentUsageError, InvalidArgumentValueError
@@ -71,7 +73,7 @@ def validate_wl_restore(item, item_type, restore_mode, recovery_mode):
7173
operation. Allowed values are: 'OriginalLocation', 'AlternateLocation'.
7274
""")
7375

74-
if recovery_mode is not None and recovery_mode != 'FileRecovery':
76+
if recovery_mode is not None and recovery_mode not in ['FileRecovery', 'SnapshotAttachAndRecover', 'SnapshotAttach']:
7577
raise InvalidArgumentValueError("""
7678
The recovery_mode specified in recovery config file is incorrect. Please correct it and retry the
7779
operation.

src/azure-cli/azure/cli/command_modules/backup/custom_base.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,8 @@ def show_recovery_config(cmd, client, resource_group_name, vault_name, restore_m
524524
rp_name=None, target_item_name=None, log_point_in_time=None, target_server_type=None,
525525
target_server_name=None, workload_type=None, backup_management_type="AzureWorkload",
526526
from_full_rp_name=None, filepath=None, target_container_name=None, target_resource_group=None,
527-
target_vault_name=None, target_subscription_id=None, target_instance_name=None):
527+
target_vault_name=None, target_subscription_id=None, target_instance_name=None,
528+
attach_and_mount=None, identity_arm_id=None, snapshot_instance_resource_group=None):
528529
target_subscription = get_subscription_id(cmd.cli_ctx)
529530
if target_subscription_id is not None:
530531
vault_csr_state = custom.get_vault_csr_state(vaults_cf(cmd.cli_ctx).get(resource_group_name, vault_name))
@@ -563,7 +564,9 @@ def show_recovery_config(cmd, client, resource_group_name, vault_name, restore_m
563564
return custom_wl.show_recovery_config(cmd, client, resource_group_name, vault_name, restore_mode, container_name,
564565
item_name, rp_name, target_item, target_item_name, log_point_in_time,
565566
from_full_rp_name, filepath, target_container, target_resource_group,
566-
target_vault_name, target_subscription)
567+
target_vault_name, target_subscription, workload_type=workload_type,
568+
attach_and_mount=attach_and_mount, identity_arm_id=identity_arm_id,
569+
snapshot_instance_resource_group=snapshot_instance_resource_group)
567570

568571

569572
def undelete_protection(cmd, client, resource_group_name, vault_name, container_name, item_name,

src/azure-cli/azure/cli/command_modules/backup/custom_common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
'SAPHANA': 'SAPHanaDatabase',
2424
'SQLDataBase': 'SQLDataBase',
2525
'SAPHanaDatabase': 'SAPHanaDatabase',
26+
'SAPHanaDBInstance': 'SAPHanaDBInstance',
2627
'SAPAseDatabase': 'SAPAseDatabase',
2728
'VM': 'VM',
2829
'AzureFileShare': 'AzureFileShare'}
2930

3031
workload_bmt_map = {'SQLDataBase': 'AzureWorkload',
3132
'SAPHanaDatabase': 'AzureWorkload',
33+
'SAPHanaDBInstance': 'AzureWorkload',
3234
'SAPAseDatabase': 'AzureWorkload',
3335
'VM': 'AzureIaasVM',
3436
'AzureFileShare': 'AzureStorage'}

src/azure-cli/azure/cli/command_modules/backup/custom_help.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def is_sql(resource_type):
7171

7272

7373
def is_hana(resource_type):
74-
return resource_type.lower() == 'saphanadatabase'
74+
return resource_type.lower() == 'saphanadatabase' or resource_type.lower() == "saphanadbinstance"
7575

7676

7777
def is_sapase(resource_type):

src/azure-cli/azure/cli/command_modules/backup/custom_wl.py

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# pylint: disable=broad-except
1111
# pylint: disable=too-many-locals
1212
# pylint: disable=too-many-statements
13+
# pylint: disable=too-many-branches
1314

1415
from knack.log import get_logger
1516

@@ -21,8 +22,9 @@
2122
AzureWorkloadSQLPointInTimeRestoreRequest, AzureWorkloadSAPAsePointInTimeRestoreRequest, \
2223
AzureVmWorkloadSAPHanaDatabaseProtectedItem, AzureVmWorkloadSQLDatabaseProtectedItem, \
2324
RecoveryPointRehydrationInfo, AzureWorkloadSAPHanaRestoreWithRehydrateRequest, \
24-
AzureWorkloadSQLRestoreWithRehydrateRequest, ProtectionState, AzureVmWorkloadSAPAseDatabaseProtectedItem, \
25-
MoveRPAcrossTiersRequest \
25+
AzureWorkloadSQLRestoreWithRehydrateRequest, ProtectionState, SnapshotRestoreParameters, \
26+
UserAssignedManagedIdentityDetails, UserAssignedIdentityProperties, \
27+
AzureVmWorkloadSAPAseDatabaseProtectedItem, MoveRPAcrossTiersRequest
2628

2729
from azure.mgmt.recoveryservicesbackup.passivestamp.models import CrossRegionRestoreRequest
2830

@@ -41,6 +43,7 @@
4143

4244
from azure.mgmt.recoveryservicesbackup.activestamp import RecoveryServicesBackupClient
4345
from azure.cli.core.commands.client_factory import get_mgmt_service_client
46+
from azure.cli.core.profiles import ResourceType
4447

4548

4649
fabric_name = "Azure"
@@ -51,6 +54,7 @@
5154
'SAPHANA': 'SAPHanaDatabase',
5255
'SQLDataBase': 'SQLDataBase',
5356
'SAPHanaDatabase': 'SAPHanaDatabase',
57+
'SAPHanaDBInstance': 'SAPHanaDBInstance',
5458
'SAPASE': 'SAPAseDatabase',
5559
'SAPAseDatabase': 'SAPAseDatabase'}
5660

@@ -70,6 +74,7 @@
7074
'HANAInstance': 'SAPHanaSystem',
7175
'SAPHanaSystem': 'SAPHanaSystem',
7276
'SQLInstance': 'SQLInstance',
77+
'SAPHanaDBInstance': 'SAPHanaDBInstance',
7378
'SQLAG': 'SQLAvailabilityGroupContainer',
7479
'SAPASE': 'SAPAseDatabase',
7580
'SAPAseDatabase': 'SAPAseDatabase'}
@@ -441,12 +446,12 @@ def enable_protection_for_azure_wl(cmd, client, resource_group_name, vault_name,
441446
# Get protectable item.
442447
protectable_item_object = protectable_item
443448
protectable_item_type = protectable_item_object.properties.protectable_item_type
444-
if protectable_item_type.lower() not in ["sqldatabase", "sqlinstance", "saphanadatabase", "saphanasystem",
445-
"sapasedatabase"]:
449+
if protectable_item_type.lower() not in ["sqldatabase", "sqlinstance", "saphanadatabase",
450+
"saphanasystem", "saphanadbinstance", "sapasedatabase"]:
446451
raise CLIError(
447452
"""
448-
Protectable Item must be either of type SQLDataBase, HANADatabase, HANAInstance, SAPAseDatabase or
449-
SQLInstance.
453+
Protectable Item must be of type SQLDataBase, HANADatabase, HANAInstance,
454+
SAPAseDatabase, SAPHanaDBInstance, or SQLInstance.
450455
""")
451456

452457
item_name = protectable_item_object.name
@@ -659,6 +664,9 @@ def restore_azure_wl(cmd, client, resource_group_name, vault_name, recovery_conf
659664
alternate_directory_paths = recovery_config_object['alternate_directory_paths']
660665
recovery_mode = recovery_config_object['recovery_mode']
661666
filepath = recovery_config_object['filepath']
667+
attach_and_mount = recovery_config_object['attach_and_mount']
668+
identity_arm_id = recovery_config_object['identity_arm_id']
669+
snapshot_instance_resource_group = recovery_config_object['snapshot_instance_resource_group']
662670

663671
item = common.show_item(cmd, backup_protected_items_cf(cmd.cli_ctx), resource_group_name, vault_name,
664672
container_uri, item_uri, "AzureWorkload")
@@ -734,6 +742,42 @@ def restore_azure_wl(cmd, client, resource_group_name, vault_name, recovery_conf
734742
setattr(trigger_restore_properties, 'should_use_alternate_target_location', True)
735743
setattr(trigger_restore_properties, 'is_non_recoverable', False)
736744

745+
if recovery_mode == 'SnapshotAttachAndRecover' or recovery_mode == 'SnapshotAttach':
746+
trigger_restore_properties.recovery_mode = recovery_mode
747+
if snapshot_instance_resource_group is None:
748+
target_resource_group_name = container_id.split('/')[4]
749+
else:
750+
target_resource_group_name = snapshot_instance_resource_group
751+
752+
# For SnapshotAttach (--attach-and-mount was not provided), skip_attach_and_mount should be False
753+
skip_attach_and_mount = False
754+
if recovery_mode == 'SnapshotAttachAndMount':
755+
skip_attach_and_mount = not attach_and_mount
756+
757+
snapshot_restore_parameters = SnapshotRestoreParameters(skip_attach_and_mount=skip_attach_and_mount,
758+
log_point_in_time_for_db_recovery='')
759+
760+
# Fetching UAMI details
761+
rg_name = identity_arm_id.split('/')[-5]
762+
id_name = identity_arm_id.split('/')[-1]
763+
sub_name = identity_arm_id.split('/')[-7]
764+
identity_client = get_mgmt_service_client(cmd.cli_ctx, ResourceType.MGMT_MSI,
765+
subscription_id=sub_name).user_assigned_identities
766+
identity_details = identity_client.get(resource_group_name=rg_name, resource_name=id_name)
767+
user_assigned_identity_properties = UserAssignedIdentityProperties(
768+
client_id=identity_details.client_id,
769+
principal_id=identity_details.principal_id
770+
)
771+
user_assigned_managed_identity_details = UserAssignedManagedIdentityDetails(
772+
identity_arm_id=identity_arm_id,
773+
user_assigned_identity_properties=user_assigned_identity_properties
774+
)
775+
776+
setattr(trigger_restore_properties, 'snapshot_restore_parameters', snapshot_restore_parameters)
777+
setattr(trigger_restore_properties, 'target_resource_group_name', target_resource_group_name)
778+
setattr(trigger_restore_properties, 'user_assigned_managed_identity_details',
779+
user_assigned_managed_identity_details)
780+
737781
trigger_restore_request = RestoreRequestResource(properties=trigger_restore_properties)
738782

739783
if use_secondary_region:
@@ -774,18 +818,19 @@ def restore_azure_wl(cmd, client, resource_group_name, vault_name, recovery_conf
774818

775819
def show_recovery_config(cmd, client, resource_group_name, vault_name, restore_mode, container_name, item_name,
776820
rp_name, target_item, target_item_name, log_point_in_time, from_full_rp_name,
777-
filepath, target_container, target_resource_group, target_vault_name, target_subscription):
821+
filepath, target_container, target_resource_group, target_vault_name, target_subscription,
822+
workload_type, attach_and_mount, identity_arm_id, snapshot_instance_resource_group):
778823
if log_point_in_time is not None:
779824
datetime_type(log_point_in_time)
780825

781826
if restore_mode == 'AlternateWorkloadRestore':
782827
_check_none_and_many(target_item, "Target Item")
783828

784829
protectable_item_type = target_item.properties.protectable_item_type
785-
if protectable_item_type.lower() not in ["sqlinstance", "saphanasystem"]:
830+
if protectable_item_type.lower() not in ["sqlinstance", "saphanasystem", "saphanadbinstance"]:
786831
raise CLIError(
787832
"""
788-
Target Item must be either of type HANAInstance or SQLInstance.
833+
Target Item must be of type HANAInstance, SQLInstance, or SAPHanaDBInstance.
789834
""")
790835

791836
if restore_mode == 'RestoreAsFiles' and target_container is None:
@@ -862,6 +907,11 @@ def show_recovery_config(cmd, client, resource_group_name, vault_name, restore_m
862907
if restore_mode == 'RestoreAsFiles':
863908
recovery_mode = 'FileRecovery'
864909
container_id = target_container.id
910+
if workload_type == 'SAPHanaDBInstance':
911+
if attach_and_mount is not None:
912+
recovery_mode = 'SnapshotAttachAndRecover'
913+
else:
914+
recovery_mode = 'SnapshotAttach'
865915

866916
return {
867917
'restore_mode': restore_mode_map[restore_mode],
@@ -876,7 +926,10 @@ def show_recovery_config(cmd, client, resource_group_name, vault_name, restore_m
876926
'container_id': container_id,
877927
'recovery_mode': recovery_mode,
878928
'filepath': filepath,
879-
'alternate_directory_paths': alternate_directory_paths}
929+
'alternate_directory_paths': alternate_directory_paths,
930+
'attach_and_mount': attach_and_mount,
931+
'identity_arm_id': identity_arm_id,
932+
'snapshot_instance_resource_group': snapshot_instance_resource_group}
880933

881934

882935
def _fetch_nodes_list_and_auto_protection_policy(cmd, paged_items, resource_group_name, vault_name,

0 commit comments

Comments
 (0)