From 7dcda1e1718ed2fb9d07bd45dbea1f160fe54cd9 Mon Sep 17 00:00:00 2001 From: Haider Agha Date: Tue, 15 Aug 2023 14:46:47 -0400 Subject: [PATCH 1/6] arm64 support --- src/vm-repair/HISTORY.rst | 4 +++ .../azext_vm_repair/command_helper_class.py | 5 ++- src/vm-repair/azext_vm_repair/custom.py | 27 ++++++++++++-- src/vm-repair/azext_vm_repair/repair_utils.py | 36 ++++++++++++++++++- src/vm-repair/azext_vm_repair/telemetry.py | 17 +++++++++ src/vm-repair/setup.py | 2 +- 6 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/vm-repair/HISTORY.rst b/src/vm-repair/HISTORY.rst index 3ab43979917..344ffaafd33 100644 --- a/src/vm-repair/HISTORY.rst +++ b/src/vm-repair/HISTORY.rst @@ -2,6 +2,10 @@ Release History =============== +0.5.5 +++++++ +Adding ARM64 support. + 0.5.4 ++++++ Adding repair-and-restore command to create a one command flow for vm-repair with fstab scripts. diff --git a/src/vm-repair/azext_vm_repair/command_helper_class.py b/src/vm-repair/azext_vm_repair/command_helper_class.py index f61ebfcf7fc..05945c1fc9f 100644 --- a/src/vm-repair/azext_vm_repair/command_helper_class.py +++ b/src/vm-repair/azext_vm_repair/command_helper_class.py @@ -12,13 +12,14 @@ from azure.cli.core.commands.client_factory import get_subscription_id -from .telemetry import _track_command_telemetry, _track_run_command_telemetry +from .telemetry import _track_command_telemetry, _track_run_command_telemetry, _track_command_telemetry_repair_and_restore from .repair_utils import _get_function_param_dict STATUS_SUCCESS = 'SUCCESS' STATUS_ERROR = 'ERROR' VM_REPAIR_RUN_COMMAND = 'vm repair run' +VM_REPAIR_AND_RESTORE_COMMAND = 'vm repair repair-and-restore' class command_helper: @@ -88,6 +89,8 @@ def __del__(self): elapsed_time = timeit.default_timer() - self.start_time if self.command_name == VM_REPAIR_RUN_COMMAND: _track_run_command_telemetry(self.logger, self.command_name, self.command_params, self.status, self.message, self.error_message, self.error_stack_trace, elapsed_time, get_subscription_id(self.cmd.cli_ctx), self.return_dict, self.script.run_id, self.script.status, self.script.output, self.script.run_time) + if self.command_name == VM_REPAIR_AND_RESTORE_COMMAND: + _track_command_telemetry_repair_and_restore(self.logger, self.command_name, self.status, self.message, self.error_message, self.error_stack_trace, elapsed_time, get_subscription_id(self.cmd.cli_ctx)) else: _track_command_telemetry(self.logger, self.command_name, self.command_params, self.status, self.message, self.error_message, self.error_stack_trace, elapsed_time, get_subscription_id(self.cmd.cli_ctx), self.return_dict) diff --git a/src/vm-repair/azext_vm_repair/custom.py b/src/vm-repair/azext_vm_repair/custom.py index 3d7bc92f2ab..f3292b14dbd 100644 --- a/src/vm-repair/azext_vm_repair/custom.py +++ b/src/vm-repair/azext_vm_repair/custom.py @@ -43,7 +43,9 @@ _unlock_encrypted_vm_run, _create_repair_vm, _check_n_start_vm, - _check_existing_rg + _check_existing_rg, + _fetch_architecture, + _select_distro_linux_Arm64 ) from .exceptions import AzCommandError, RunScriptNotFoundForIdError, SupportingResourceNotFoundError, CommandCanceledByUserError logger = get_logger(__name__) @@ -70,6 +72,7 @@ def create(cmd, vm_name, resource_group_name, repair_password=None, repair_usern copy_disk_id = None resource_tag = _get_repair_resource_tag(resource_group_name, vm_name) created_resources = [] + architecture_type = _fetch_architecture(source_vm) # Fetch OS image urn and set OS type for disk create if is_linux and _uses_managed_disk(source_vm): @@ -77,8 +80,11 @@ def create(cmd, vm_name, resource_group_name, repair_password=None, repair_usern os_type = 'Linux' hyperV_generation_linux = _check_linux_hyperV_gen(source_vm) if hyperV_generation_linux == 'V2': - logger.info('Generation 2 VM detected, RHEL/Centos/Oracle 6 distros not available to be used for rescue VM ') + logger.info('Generation 2 VM detected') os_image_urn = _select_distro_linux_gen2(distro) + if architecture_type == 'Arm64': + logger.info('ARM64 VM detected') + os_image_urn = _select_distro_linux_Arm64(distro) else: os_image_urn = _select_distro_linux(distro) else: @@ -690,7 +696,7 @@ def repair_and_restore(cmd, vm_name, resource_group_name, repair_password=None, existing_rg = _check_existing_rg(repair_group_name) create_out = create(cmd, vm_name, resource_group_name, repair_password, repair_username, repair_vm_name=repair_vm_name, copy_disk_name=copy_disk_name, repair_group_name=repair_group_name, associate_public_ip=False, yes=True) - + # log create_out logger.info('create_out: %s', create_out) @@ -732,3 +738,18 @@ def repair_and_restore(cmd, vm_name, resource_group_name, repair_password=None, repair_vm_id = _call_az_command(show_vm_id) restore(cmd, vm_name, resource_group_name, copy_disk_name, repair_vm_id, yes=True) + + command.message = 'fstab script has been applied to the source VM. A new repair VM \'{n}\' was created in the resource group \'{repair_rg}\' with disk \'{d}\' attached as data disk. ' \ + 'The repairs were complete using the fstab script and the repair VM was then deleted. ' \ + 'The repair disk was restored to the source VM. ' \ + .format(n=repair_vm_name, repair_rg=repair_group_name, d=copy_disk_name) + + command.set_status_success() + if command.error_stack_trace: + logger.debug(command.error_stack_trace) + # Generate return object and log errors if needed + return_dict = command.init_return_dict() + + logger.info('\n%s\n', command.message) + + return return_dict \ No newline at end of file diff --git a/src/vm-repair/azext_vm_repair/repair_utils.py b/src/vm-repair/azext_vm_repair/repair_utils.py index f08a5b07ae3..a624b720dd2 100644 --- a/src/vm-repair/azext_vm_repair/repair_utils.py +++ b/src/vm-repair/azext_vm_repair/repair_utils.py @@ -542,9 +542,28 @@ def _select_distro_linux(distro): return os_image_urn +def _select_distro_linux_Arm64(distro): + image_lookup = { + 'rhel8': 'RedHat:rhel-arm64:8_8-arm64:latest', + 'rhel9': 'RedHat:rhel-arm64:9_2-arm64:latest', + 'ubuntu18': 'Canonical:UbuntuServer:18_04-lts-arm64:latest', + 'ubuntu20': 'Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:latest', + 'centos7': 'OpenLogic:CentOS:7_9-arm64:latest', + } + if distro in image_lookup: + os_image_urn = image_lookup[distro] + else: + if distro.count(":") == 3: + logger.info('A custom URN was provided , will be used as distro for the recovery VM') + os_image_urn = distro + else: + logger.info('No specific distro was provided , using the default ARM64 Ubuntu distro') + os_image_urn = "Canonical:UbuntuServer:18_04-lts-arm64:latest" + return os_image_urn + + def _select_distro_linux_gen2(distro): # base on the document : https://docs.microsoft.com/en-us/azure/virtual-machines/generation-2#generation-2-vm-images-in-azure-marketplace - # RHEL/Centos/Oracle 6 are not supported for Gen 2 image_lookup = { 'rhel6': 'RedHat:rhel-raw:7-raw-gen2:latest', 'rhel7': 'RedHat:rhel-raw:7-raw-gen2:latest', @@ -720,3 +739,18 @@ def _create_repair_vm(copy_disk_id, create_repair_vm_command, repair_password, r _call_az_command(create_repair_vm_command + ' --validate', secure_params=[repair_password, repair_username]) logger.info('Creating repair VM...') _call_az_command(create_repair_vm_command, secure_params=[repair_password, repair_username]) + + +def _fetch_architecture(source_vm): + """ + Returns the architecture of the source VM. + """ + location = source_vm.location + vm_size = source_vm.hardware_profile.vm_size + architecture_type_cmd = 'az vm list-skus -l {loc} --size {vm_size} --query "[].capabilities[?name==\'CpuArchitectureType\'].value" -o json' \ + .format(loc=location, vm_size=vm_size) + + logger.info('Fetching architecture type of the source VM...') + architecture = loads(_call_az_command(architecture_type_cmd).strip('\n')) + + return architecture[0][0] diff --git a/src/vm-repair/azext_vm_repair/telemetry.py b/src/vm-repair/azext_vm_repair/telemetry.py index 81f4da45158..b765863dbbc 100644 --- a/src/vm-repair/azext_vm_repair/telemetry.py +++ b/src/vm-repair/azext_vm_repair/telemetry.py @@ -55,3 +55,20 @@ def _track_run_command_telemetry(logger, command_name, parameters, status, messa tc.flush() except Exception as exception: logger.error('Unexpected error sending telemetry with exception: %s', str(exception)) + + +def _track_command_telemetry_repair_and_restore(logger, command_name, status, message, error_message, error_stack_trace, duration, subscription_id): + try: + properties = { + 'command_name': command_name, + 'command_status': status, + 'message': message, + 'error_message': error_message, + 'error_stack_trace': error_stack_trace, + 'subscription_id': subscription_id + } + measurements = {'command_duration': duration} + tc.track_event(command_name, properties, measurements) + tc.flush() + except Exception as exception: + logger.error('Unexpected error sending telemetry with exception: %s', str(exception)) diff --git a/src/vm-repair/setup.py b/src/vm-repair/setup.py index 1f7f4473f5d..588f1ff335e 100644 --- a/src/vm-repair/setup.py +++ b/src/vm-repair/setup.py @@ -8,7 +8,7 @@ from codecs import open from setuptools import setup, find_packages -VERSION = "0.5.4" +VERSION = "0.5.5" CLASSIFIERS = [ 'Development Status :: 4 - Beta', From c5a67b2e04774d7a3a71bc4066cc40ac59a2664f Mon Sep 17 00:00:00 2001 From: Haider Agha Date: Tue, 15 Aug 2023 15:03:34 -0400 Subject: [PATCH 2/6] style check --- src/vm-repair/azext_vm_repair/custom.py | 10 +++++----- src/vm-repair/azext_vm_repair/repair_utils.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vm-repair/azext_vm_repair/custom.py b/src/vm-repair/azext_vm_repair/custom.py index f3292b14dbd..6391f06a8eb 100644 --- a/src/vm-repair/azext_vm_repair/custom.py +++ b/src/vm-repair/azext_vm_repair/custom.py @@ -696,7 +696,7 @@ def repair_and_restore(cmd, vm_name, resource_group_name, repair_password=None, existing_rg = _check_existing_rg(repair_group_name) create_out = create(cmd, vm_name, resource_group_name, repair_password, repair_username, repair_vm_name=repair_vm_name, copy_disk_name=copy_disk_name, repair_group_name=repair_group_name, associate_public_ip=False, yes=True) - + # log create_out logger.info('create_out: %s', create_out) @@ -740,9 +740,9 @@ def repair_and_restore(cmd, vm_name, resource_group_name, repair_password=None, restore(cmd, vm_name, resource_group_name, copy_disk_name, repair_vm_id, yes=True) command.message = 'fstab script has been applied to the source VM. A new repair VM \'{n}\' was created in the resource group \'{repair_rg}\' with disk \'{d}\' attached as data disk. ' \ - 'The repairs were complete using the fstab script and the repair VM was then deleted. ' \ - 'The repair disk was restored to the source VM. ' \ - .format(n=repair_vm_name, repair_rg=repair_group_name, d=copy_disk_name) + 'The repairs were complete using the fstab script and the repair VM was then deleted. ' \ + 'The repair disk was restored to the source VM. ' \ + .format(n=repair_vm_name, repair_rg=repair_group_name, d=copy_disk_name) command.set_status_success() if command.error_stack_trace: @@ -752,4 +752,4 @@ def repair_and_restore(cmd, vm_name, resource_group_name, repair_password=None, logger.info('\n%s\n', command.message) - return return_dict \ No newline at end of file + return return_dict diff --git a/src/vm-repair/azext_vm_repair/repair_utils.py b/src/vm-repair/azext_vm_repair/repair_utils.py index a624b720dd2..985276d725f 100644 --- a/src/vm-repair/azext_vm_repair/repair_utils.py +++ b/src/vm-repair/azext_vm_repair/repair_utils.py @@ -748,7 +748,7 @@ def _fetch_architecture(source_vm): location = source_vm.location vm_size = source_vm.hardware_profile.vm_size architecture_type_cmd = 'az vm list-skus -l {loc} --size {vm_size} --query "[].capabilities[?name==\'CpuArchitectureType\'].value" -o json' \ - .format(loc=location, vm_size=vm_size) + .format(loc=location, vm_size=vm_size) logger.info('Fetching architecture type of the source VM...') architecture = loads(_call_az_command(architecture_type_cmd).strip('\n')) From 59b8411eeb02e755b825d24f0f8867e1ef6ecb7c Mon Sep 17 00:00:00 2001 From: Haider Agha Date: Thu, 17 Aug 2023 14:58:20 -0400 Subject: [PATCH 3/6] changing default distro --- src/vm-repair/azext_vm_repair/repair_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm-repair/azext_vm_repair/repair_utils.py b/src/vm-repair/azext_vm_repair/repair_utils.py index 985276d725f..f9a8296fb66 100644 --- a/src/vm-repair/azext_vm_repair/repair_utils.py +++ b/src/vm-repair/azext_vm_repair/repair_utils.py @@ -538,7 +538,7 @@ def _select_distro_linux(distro): os_image_urn = distro else: logger.info('No specific distro was provided , using the default Ubuntu distro') - os_image_urn = "UbuntuLTS" + os_image_urn = "Ubuntu2204" return os_image_urn From 811330209d157e2963fcc0a077d6d1b4154b3e73 Mon Sep 17 00:00:00 2001 From: Haider Agha Date: Wed, 23 Aug 2023 10:28:26 -0400 Subject: [PATCH 4/6] nested hyperv changes --- .../azext_vm_repair/scripts/win-enable-nested-hyperv.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vm-repair/azext_vm_repair/scripts/win-enable-nested-hyperv.ps1 b/src/vm-repair/azext_vm_repair/scripts/win-enable-nested-hyperv.ps1 index d3688188bcc..92320b879be 100644 --- a/src/vm-repair/azext_vm_repair/scripts/win-enable-nested-hyperv.ps1 +++ b/src/vm-repair/azext_vm_repair/scripts/win-enable-nested-hyperv.ps1 @@ -72,7 +72,7 @@ if ($hyperv.Installed -and $hypervTools.Installed -and $hypervPowerShell.Install $return = Set-DhcpServerv4OptionValue -DnsServer 168.63.129.16 -Router 192.168.0.1 -ErrorAction Stop # Create the nested guest VM - if (!$gen) { + if (!$gen -or ($gen -eq 1)) { Log-Info 'Creating Gen1 VM with 4GB memory' | Out-File -FilePath $logFile -Append $return = New-VM -Name $nestedGuestVmName -MemoryStartupBytes 4GB -NoVHD -BootDevice IDE -Generation 1 -ErrorAction Stop } @@ -84,7 +84,7 @@ if ($hyperv.Installed -and $hypervTools.Installed -and $hypervPowerShell.Install $disk = get-disk -ErrorAction Stop | where {$_.FriendlyName -eq 'Msft Virtual Disk'} $return = $disk | set-disk -IsOffline $true -ErrorAction Stop - if (!$gen) { + if (!$gen -or ($gen -eq 1)) { Log-Info "Gen1: Adding hard drive to IDE controller" | Out-File -FilePath $logFile -Append $return = $disk | Add-VMHardDiskDrive -VMName $nestedGuestVmName -ErrorAction Stop } From 81e8f657979dfbbbcd9db136760e1e914dbdb644 Mon Sep 17 00:00:00 2001 From: Haider Agha Date: Wed, 23 Aug 2023 10:37:36 -0400 Subject: [PATCH 5/6] change history --- src/vm-repair/HISTORY.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vm-repair/HISTORY.rst b/src/vm-repair/HISTORY.rst index 344ffaafd33..90ed95e6d5d 100644 --- a/src/vm-repair/HISTORY.rst +++ b/src/vm-repair/HISTORY.rst @@ -5,6 +5,8 @@ Release History 0.5.5 ++++++ Adding ARM64 support. +Fix for telemetry for repair-and-restore command. +Repair VM fix for gen1 VM attaching disk on SCSI controller, preventing nested VM from booting (by Ryan McCallum) 0.5.4 ++++++ From 6a8b61d6b436af37cb1a541f622256bceb70ca2f Mon Sep 17 00:00:00 2001 From: Haider Agha Date: Thu, 31 Aug 2023 10:35:25 -0400 Subject: [PATCH 6/6] adding test --- .../tests/latest/test_repair_commands.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py b/src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py index 54f12d79acf..5e9723720ba 100644 --- a/src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py +++ b/src/vm-repair/azext_vm_repair/tests/latest/test_repair_commands.py @@ -741,4 +741,39 @@ def test_vmrepair_RepairAndRestoreLinuxVM(self, resource_group): # Check swapped OS disk vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() source_vm = vms[0] - assert source_vm['storageProfile']['osDisk']['name'] == result['copied_disk_name'] \ No newline at end of file + assert source_vm['storageProfile']['osDisk']['name'] == result['copied_disk_name'] + + +@pytest.mark.arm64 +class LinuxARMManagedDiskCreateRestoreTest(LiveScenarioTest): + + @ResourceGroupPreparer(location='eastus') + def test_vmrepair_LinuxManagedCreateRestore(self, resource_group): + self.kwargs.update({ + 'vm': 'vm1' + }) + + # Create test VM + self.cmd('vm create -g {rg} -n {vm} --image Canonical:UbuntuServer:18_04-lts-arm64:latest --admin-username azureadmin --admin-password !Passw0rd2018') + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + # Something wrong with vm create command if it fails here + assert len(vms) == 1 + + # Test create + result = self.cmd('vm repair create -g {rg} -n {vm} --repair-username azureadmin --repair-password !Passw0rd2018 --yes -o json').get_output_in_json() + assert result['status'] == STATUS_SUCCESS, result['error_message'] + + # Check repair VM + repair_vms = self.cmd('vm list -g {} -o json'.format(result['repair_resource_group'])).get_output_in_json() + assert len(repair_vms) == 1 + repair_vm = repair_vms[0] + # Check attached data disk + assert repair_vm['storageProfile']['dataDisks'][0]['name'] == result['copied_disk_name'] + + # Call Restore + self.cmd('vm repair restore -g {rg} -n {vm} --yes') + + # Check swapped OS disk + vms = self.cmd('vm list -g {rg} -o json').get_output_in_json() + source_vm = vms[0] + assert source_vm['storageProfile']['osDisk']['name'] == result['copied_disk_name']