Skip to content

{Compute} az vm create/ update: Support zone-resilient VM with --zone-movement and cross-zone movement#33242

Open
william051200 wants to merge 24 commits intoAzure:devfrom
william051200:29807-zone-resilient
Open

{Compute} az vm create/ update: Support zone-resilient VM with --zone-movement and cross-zone movement#33242
william051200 wants to merge 24 commits intoAzure:devfrom
william051200:29807-zone-resilient

Conversation

@william051200
Copy link
Copy Markdown
Member

@william051200 william051200 commented Apr 22, 2026

Related command

az vm create
az vm update
az vm show
az vm deallocate

Description

Resolve #29807

aaz Azure/aaz#992

Testing Guide

Refer to vm\tests\latest\test_vm_commands.py - test_vm_zone_movement

Recording is not being generated due to size too big

History Notes

[Compute] az vm create/ update: Support zone-resilient VM with --zone-movement
[Compute] az vm update: Support cross-zone movement
[Compute] az vm deallocate: Support vm force deallocate with --force-deallocate


This checklist is used to make sure that common guidelines for a pull request are followed.

Copilot AI review requested due to automatic review settings April 22, 2026 07:33
@azure-client-tools-bot-prd
Copy link
Copy Markdown

azure-client-tools-bot-prd Bot commented Apr 22, 2026

❌AzureCLI-FullTest
️✔️acr
️✔️latest
️✔️3.12
️✔️3.13
️✔️acs
️✔️latest
️✔️3.12
️✔️3.13
️✔️advisor
️✔️latest
️✔️3.12
️✔️3.13
️✔️ams
️✔️latest
️✔️3.12
️✔️3.13
️✔️apim
️✔️latest
️✔️3.12
️✔️3.13
️✔️appconfig
️✔️latest
️✔️3.12
️✔️3.13
️✔️appservice
️✔️latest
️✔️3.12
️✔️3.13
️✔️aro
️✔️latest
️✔️3.12
️✔️3.13
️✔️backup
️✔️latest
️✔️3.12
️✔️3.13
️✔️batch
️✔️latest
️✔️3.12
️✔️3.13
️✔️batchai
️✔️latest
️✔️3.12
️✔️3.13
️✔️billing
️✔️latest
️✔️3.12
️✔️3.13
️✔️botservice
️✔️latest
️✔️3.12
️✔️3.13
️✔️cdn
️✔️latest
️✔️3.12
️✔️3.13
️✔️cloud
️✔️latest
️✔️3.12
️✔️3.13
️✔️cognitiveservices
️✔️latest
️✔️3.12
️✔️3.13
️✔️compute_recommender
️✔️latest
️✔️3.12
️✔️3.13
️✔️computefleet
️✔️latest
️✔️3.12
️✔️3.13
️✔️config
️✔️latest
️✔️3.12
️✔️3.13
️✔️configure
️✔️latest
️✔️3.12
️✔️3.13
️✔️consumption
️✔️latest
️✔️3.12
️✔️3.13
️✔️container
️✔️latest
️✔️3.12
️✔️3.13
️✔️containerapp
️✔️latest
️✔️3.12
️✔️3.13
️✔️core
️✔️latest
️✔️3.12
️✔️3.13
️✔️cosmosdb
️✔️latest
️✔️3.12
️✔️3.13
️✔️databoxedge
️✔️latest
️✔️3.12
️✔️3.13
️✔️dls
️✔️latest
️✔️3.12
️✔️3.13
️✔️dms
️✔️latest
️✔️3.12
️✔️3.13
️✔️eventgrid
️✔️latest
️✔️3.12
️✔️3.13
️✔️eventhubs
️✔️latest
️✔️3.12
️✔️3.13
️✔️feedback
️✔️latest
️✔️3.12
️✔️3.13
️✔️find
️✔️latest
️✔️3.12
️✔️3.13
️✔️hdinsight
️✔️latest
️✔️3.12
️✔️3.13
️✔️identity
️✔️latest
️✔️3.12
️✔️3.13
️✔️iot
️✔️latest
️✔️3.12
️✔️3.13
️✔️keyvault
️✔️latest
️✔️3.12
️✔️3.13
️✔️lab
️✔️latest
️✔️3.12
️✔️3.13
️✔️managedservices
️✔️latest
️✔️3.12
️✔️3.13
️✔️maps
️✔️latest
️✔️3.12
️✔️3.13
️✔️marketplaceordering
️✔️latest
️✔️3.12
️✔️3.13
️✔️monitor
️✔️latest
️✔️3.12
️✔️3.13
️✔️mysql
️✔️latest
️✔️3.12
️✔️3.13
️✔️netappfiles
️✔️latest
️✔️3.12
️✔️3.13
️✔️network
️✔️latest
️✔️3.12
️✔️3.13
️✔️policyinsights
️✔️latest
️✔️3.12
️✔️3.13
️✔️postgresql
️✔️latest
️✔️3.12
️✔️3.13
️✔️privatedns
️✔️latest
️✔️3.12
️✔️3.13
️✔️profile
️✔️latest
️✔️3.12
️✔️3.13
️✔️rdbms
️✔️latest
️✔️3.12
️✔️3.13
️✔️redis
️✔️latest
️✔️3.12
️✔️3.13
️✔️relay
️✔️latest
️✔️3.12
️✔️3.13
️✔️resource
️✔️latest
️✔️3.12
️✔️3.13
️✔️role
️✔️latest
️✔️3.12
️✔️3.13
️✔️search
️✔️latest
️✔️3.12
️✔️3.13
️✔️security
️✔️latest
️✔️3.12
️✔️3.13
️✔️servicebus
️✔️latest
️✔️3.12
️✔️3.13
️✔️serviceconnector
️✔️latest
️✔️3.12
️✔️3.13
️✔️servicefabric
️✔️latest
️✔️3.12
️✔️3.13
️✔️signalr
️✔️latest
️✔️3.12
️✔️3.13
️✔️sql
️✔️latest
️✔️3.12
️✔️3.13
️✔️sqlvm
️✔️latest
️✔️3.12
️✔️3.13
️✔️storage
️✔️latest
️✔️3.12
️✔️3.13
️✔️synapse
️✔️latest
️✔️3.12
️✔️3.13
️✔️telemetry
️✔️latest
️✔️3.12
️✔️3.13
️✔️util
️✔️latest
️✔️3.12
️✔️3.13
❌vm
❌latest
❌3.12
Type Test Case Error Message Line
Failed test_create_vm_with_shared_gallery_image self = <azure.cli.testsdk.base.ExecutionResult object at 0x7fca6484ede0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7fca65a20fe0>
command = 'sig create -g clitest.rg000001 --gallery-name gellery000007 --permissions groups'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.12/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = HttpResponseError("(BadRequest) Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature... registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.")
args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception HttpResponseError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <latest.test_vm_commands.VMGalleryImage testMethod=test_create_vm_with_shared_gallery_image>
resource_group = 'clitest.rg000001', resource_group_location = 'westus'

    @AllowLargeResponse(size_kb=99999)
    @ResourceGroupPreparer(location='westus')
    def test_create_vm_with_shared_gallery_image(self, resource_group, resource_group_location):
        self.kwargs.update({
            'vm': self.create_random_name('vm', 16),
            'vm_with_shared_gallery': self.create_random_name('vm_sg', 16),
            'vm_with_shared_gallery_version': self.create_random_name('vm_sgv', 16),
            'vm_with_shared_gallery_version2': self.create_random_name('vm_sgv2', 16),
            'vmss_with_shared_gallery_version': self.create_random_name('vmss', 16),
            'gallery': self.create_random_name('gellery', 16),
            'image': self.create_random_name('image', 16),
            'version': '1.1.2',
            'captured': 'managedImage1',
            'location': resource_group_location,
            'subId': '0b1f6471-1bf0-4dda-aec3-cb9272f09590',  # share the gallery to tester's subscription, so the tester can get shared galleries
            'tenantId': '2f4a9838-26b7-47ee-be60-ccc1fdec5953',
            'subnet': 'subnet1',
            'vnet': 'vnet1',
            'nsg': 'nsg1',
            'pubip': 'pubip',
        })
    
>       self.cmd('sig create -g {rg} --gallery-name {gallery} --permissions groups')

src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:8804: 
 
                                       
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.12/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:800: in run_job
    result = LongRunningOperation(cmd_copy.cli_ctx, 'Starting {}'.format(cmd_copy.name))(result)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:1181: in call
    raise exception
src/azure-cli-core/azure/cli/core/commands/init.py:1168: in call
    result = poller.result()
             ^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/aaz/poller.py:105: in result
    self.wait(timeout)
env/lib/python3.12/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/aaz/poller.py:127: in wait
    raise self.exception
src/azure-cli-core/azure/cli/core/aaz/poller.py:80: in start
    for polling_method in self.polling_generator:
                          ^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/__cmds.py:653: in execute_operations
    yield self.GalleriesCreateOrUpdate(ctx=self.ctx)()
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/__cmds.py:693: in call
    return self.on_error(session.http_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
                             

self = <azure.cli.command_modules.vm.aaz.latest.sig.__cmds.Create.GalleriesCreateOrUpdate object at 0x7fca639a3140>
response = RequestsTransportResponse: 400 Bad Request, Content-Type: application/json; charset=utf-8

    def on_error(self, response):
        """ handle errors in response
        """
        # raise common http errors
        error_type = self.error_map.get(response.status_code)
        if error_type:
            raise error_type(response=response)
        # raise HttpResponseError
        error_format = self.ctx.get_error_format(self.error_format)
>       raise HttpResponseError(response=response, error_format=error_format)
E       azure.core.exceptions.HttpResponseError: (BadRequest) Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.
E       Code: BadRequest
E       Message: Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.

src/azure-cli-core/azure/cli/core/aaz/_operation.py:327: HttpResponseError
azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:8781
Failed test_shared_gallery self = <azure.cli.testsdk.base.ExecutionResult object at 0x7fca6484ede0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7fca65696a50>
command = 'sig create -g clitest.rg000001 --gallery-name gellery000002 --permissions groups'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.12/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = HttpResponseError("(BadRequest) Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature... registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.")
args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception HttpResponseError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <latest.test_vm_commands.VMGalleryImage testMethod=test_shared_gallery>
resource_group = 'clitest.rg000001', resource_group_location = 'westus'

    @AllowLargeResponse()
    @ResourceGroupPreparer(location='westus')
    def test_shared_gallery(self, resource_group, resource_group_location):
        self.kwargs.update({
            'vm': 'vm1',
            'gallery': self.create_random_name('gellery', 16),
            'image': 'image1',
            'version': '1.1.2',
            'captured': 'managedImage1',
            'location': resource_group_location,
            'subId': '0b1f6471-1bf0-4dda-aec3-cb9272f09590',  # share the gallery to tester's subscription, so the tester can get shared galleries
            'tenantId': '2f4a9838-26b7-47ee-be60-ccc1fdec5953',
            'sharedSubId': '34a4ab42-0d72-47d9-bd1a-aed207386dac',
            'subnet': 'subnet1',
            'vnet': 'vnet1',
            'pubip': 'pubip',
        })
    
>       self.cmd('sig create -g {rg} --gallery-name {gallery} --permissions groups')

src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:7859: 
 
                                       
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.12/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:800: in run_job
    result = LongRunningOperation(cmd_copy.cli_ctx, 'Starting {}'.format(cmd_copy.name))(result)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:1181: in call
    raise exception
src/azure-cli-core/azure/cli/core/commands/init.py:1168: in call
    result = poller.result()
             ^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/aaz/poller.py:105: in result
    self.wait(timeout)
env/lib/python3.12/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/aaz/poller.py:127: in wait
    raise self.exception
src/azure-cli-core/azure/cli/core/aaz/poller.py:80: in start
    for polling_method in self.polling_generator:
                          ^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/__cmds.py:653: in execute_operations
    yield self.GalleriesCreateOrUpdate(ctx=self.ctx)()
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/__cmds.py:693: in call
    return self.on_error(session.http_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
                             

self = <azure.cli.command_modules.vm.aaz.latest.sig.__cmds.Create.GalleriesCreateOrUpdate object at 0x7fca636dea80>
response = RequestsTransportResponse: 400 Bad Request, Content-Type: application/json; charset=utf-8

    def on_error(self, response):
        """ handle errors in response
        """
        # raise common http errors
        error_type = self.error_map.get(response.status_code)
        if error_type:
            raise error_type(response=response)
        # raise HttpResponseError
        error_format = self.ctx.get_error_format(self.error_format)
>       raise HttpResponseError(response=response, error_format=error_format)
E       azure.core.exceptions.HttpResponseError: (BadRequest) Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.
E       Code: BadRequest
E       Message: Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.

src/azure-cli-core/azure/cli/core/aaz/_operation.py:327: HttpResponseError
azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:7840
❌3.13
Type Test Case Error Message Line
Failed test_create_vm_with_shared_gallery_image self = <azure.cli.testsdk.base.ExecutionResult object at 0x7ff15fd1a7b0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7ff160f90a50>
command = 'sig create -g clitest.rg000001 --gallery-name gellery000007 --permissions groups'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.13/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = HttpResponseError("(BadRequest) Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature... registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.")
args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception HttpResponseError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <latest.test_vm_commands.VMGalleryImage testMethod=test_create_vm_with_shared_gallery_image>
resource_group = 'clitest.rg000001', resource_group_location = 'westus'

    @AllowLargeResponse(size_kb=99999)
    @ResourceGroupPreparer(location='westus')
    def test_create_vm_with_shared_gallery_image(self, resource_group, resource_group_location):
        self.kwargs.update({
            'vm': self.create_random_name('vm', 16),
            'vm_with_shared_gallery': self.create_random_name('vm_sg', 16),
            'vm_with_shared_gallery_version': self.create_random_name('vm_sgv', 16),
            'vm_with_shared_gallery_version2': self.create_random_name('vm_sgv2', 16),
            'vmss_with_shared_gallery_version': self.create_random_name('vmss', 16),
            'gallery': self.create_random_name('gellery', 16),
            'image': self.create_random_name('image', 16),
            'version': '1.1.2',
            'captured': 'managedImage1',
            'location': resource_group_location,
            'subId': '0b1f6471-1bf0-4dda-aec3-cb9272f09590',  # share the gallery to tester's subscription, so the tester can get shared galleries
            'tenantId': '2f4a9838-26b7-47ee-be60-ccc1fdec5953',
            'subnet': 'subnet1',
            'vnet': 'vnet1',
            'nsg': 'nsg1',
            'pubip': 'pubip',
        })
    
>       self.cmd('sig create -g {rg} --gallery-name {gallery} --permissions groups')

src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:8804: 
 
                                       
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:800: in run_job
    result = LongRunningOperation(cmd_copy.cli_ctx, 'Starting {}'.format(cmd_copy.name))(result)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:1181: in call
    raise exception
src/azure-cli-core/azure/cli/core/commands/init.py:1168: in call
    result = poller.result()
             ^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/aaz/poller.py:105: in result
    self.wait(timeout)
env/lib/python3.13/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/aaz/poller.py:127: in wait
    raise self.exception
src/azure-cli-core/azure/cli/core/aaz/poller.py:80: in start
    for polling_method in self.polling_generator:
                          ^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/__cmds.py:312: in execute_operations
    yield self.GalleriesCreateOrUpdate(ctx=self.ctx)()
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/__cmds.py:352: in call
    return self.on_error(session.http_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
                             

self = <azure.cli.command_modules.vm.aaz.latest.sig.__cmds.Create.GalleriesCreateOrUpdate object at 0x7ff15f189160>
response = RequestsTransportResponse: 400 Bad Request, Content-Type: application/json; charset=utf-8

    def on_error(self, response):
        """ handle errors in response
        """
        # raise common http errors
        error_type = self.error_map.get(response.status_code)
        if error_type:
            raise error_type(response=response)
        # raise HttpResponseError
        error_format = self.ctx.get_error_format(self.error_format)
>       raise HttpResponseError(response=response, error_format=error_format)
E       azure.core.exceptions.HttpResponseError: (BadRequest) Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.
E       Code: BadRequest
E       Message: Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.

src/azure-cli-core/azure/cli/core/aaz/_operation.py:327: HttpResponseError
azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:8781
Failed test_shared_gallery self = <azure.cli.testsdk.base.ExecutionResult object at 0x7ff15fd2a7b0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7ff160e2de50>
command = 'sig create -g clitest.rg000001 --gallery-name gellery000002 --permissions groups'
expect_failure = False

    def in_process_execute(self, cli_ctx, command, expect_failure=False):
        from io import StringIO
        from vcr.errors import CannotOverwriteExistingCassetteException
    
        if command.startswith('az '):
            command = command[3:]
    
        stdout_buf = StringIO()
        logging_buf = StringIO()
        try:
            # issue: stderr cannot be redirect in this form, as a result some failure information
            # is lost when command fails.
>           self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/azure-cli-testsdk/azure/cli/testsdk/base.py:303: 
                                        
env/lib/python3.13/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:157: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
                                        

ex = HttpResponseError("(BadRequest) Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature... registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.")
args = (), kwargs = {}

    def handle_main_exception(ex, *args, **kwargs):  # pylint: disable=unused-argument
        if isinstance(ex, CannotOverwriteExistingCassetteException):
            # This exception usually caused by a no match HTTP request. This is a product error
            # that is caused by change of SDK invocation.
            raise ex
    
>       raise CliExecutionError(ex)
E       azure.cli.testsdk.exceptions.CliExecutionError: The CLI throws exception HttpResponseError during execution and fails the command.

src/azure-cli-testsdk/azure/cli/testsdk/patches.py:35: CliExecutionError

During handling of the above exception, another exception occurred:

self = <latest.test_vm_commands.VMGalleryImage testMethod=test_shared_gallery>
resource_group = 'clitest.rg000001', resource_group_location = 'westus'

    @AllowLargeResponse()
    @ResourceGroupPreparer(location='westus')
    def test_shared_gallery(self, resource_group, resource_group_location):
        self.kwargs.update({
            'vm': 'vm1',
            'gallery': self.create_random_name('gellery', 16),
            'image': 'image1',
            'version': '1.1.2',
            'captured': 'managedImage1',
            'location': resource_group_location,
            'subId': '0b1f6471-1bf0-4dda-aec3-cb9272f09590',  # share the gallery to tester's subscription, so the tester can get shared galleries
            'tenantId': '2f4a9838-26b7-47ee-be60-ccc1fdec5953',
            'sharedSubId': '34a4ab42-0d72-47d9-bd1a-aed207386dac',
            'subnet': 'subnet1',
            'vnet': 'vnet1',
            'pubip': 'pubip',
        })
    
>       self.cmd('sig create -g {rg} --gallery-name {gallery} --permissions groups')

src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:7859: 
 
                                       
src/azure-cli-testsdk/azure/cli/testsdk/base.py:177: in cmd
    return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/base.py:252: in init
    self.in_process_execute(cli_ctx, command, expect_failure=expect_failure)
src/azure-cli-testsdk/azure/cli/testsdk/base.py:315: in in_process_execute
    raise ex.exception
env/lib/python3.13/site-packages/knack/cli.py:233: in invoke
    cmd_result = self.invocation.execute(args)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:677: in execute
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:820: in run_jobs_serially
    results.append(self.run_job(expanded_arg, cmd_copy))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:800: in run_job
    result = LongRunningOperation(cmd_copy.cli_ctx, 'Starting {}'.format(cmd_copy.name))(result)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:1181: in call
    raise exception
src/azure-cli-core/azure/cli/core/commands/init.py:1168: in call
    result = poller.result()
             ^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/aaz/poller.py:105: in result
    self.wait(timeout)
env/lib/python3.13/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/aaz/poller.py:127: in wait
    raise self.exception
src/azure-cli-core/azure/cli/core/aaz/poller.py:80: in start
    for polling_method in self.polling_generator:
                          ^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/__cmds.py:312: in execute_operations
    yield self.GalleriesCreateOrUpdate(ctx=self.ctx)()
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/__cmds.py:352: in call
    return self.on_error(session.http_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
                             

self = <azure.cli.command_modules.vm.aaz.latest.sig.__cmds.Create.GalleriesCreateOrUpdate object at 0x7ff15ef99160>
response = RequestsTransportResponse: 400 Bad Request, Content-Type: application/json; charset=utf-8

    def on_error(self, response):
        """ handle errors in response
        """
        # raise common http errors
        error_type = self.error_map.get(response.status_code)
        if error_type:
            raise error_type(response=response)
        # raise HttpResponseError
        error_format = self.ctx.get_error_format(self.error_format)
>       raise HttpResponseError(response=response, error_format=error_format)
E       azure.core.exceptions.HttpResponseError: (BadRequest) Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.
E       Code: BadRequest
E       Message: Subscription '88939486-3f56-4b35-bd43-5d6b34df022f' is not registered with the feature Microsoft.Compute/SIGSharing. Please register the subscription before retrying the call.

src/azure-cli-core/azure/cli/core/aaz/_operation.py:327: HttpResponseError
azure/cli/command_modules/vm/tests/latest/test_vm_commands.py:7840

@azure-client-tools-bot-prd
Copy link
Copy Markdown

Hi @william051200,
Since the current milestone time is less than 7 days, this pr will be reviewed in the next milestone.

@azure-client-tools-bot-prd
Copy link
Copy Markdown

azure-client-tools-bot-prd Bot commented Apr 22, 2026

⚠️AzureCLI-BreakingChangeTest
⚠️vm
rule cmd_name rule_message suggest_message
⚠️ 1001 - CmdAdd sig identity wait cmd sig identity wait added
⚠️ 1006 - ParaAdd vm create cmd vm create added parameter zone_movement
⚠️ 1006 - ParaAdd vm update cmd vm update added parameter zone_movement
⚠️ 1006 - ParaAdd vm update cmd vm update added parameter zone

@yonzhan
Copy link
Copy Markdown
Collaborator

yonzhan commented Apr 22, 2026

Thank you for your contribution! We will review the pull request and get back to you soon.

@github-actions
Copy link
Copy Markdown

The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR.

Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions).
After that please run the following commands to enable git hooks:

pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for zone-resilient VMs by introducing --zone-movement on az vm create / az vm update, and implements cross-zone movement orchestration when updating a VM’s zone.

Changes:

  • Add --zone-movement support to VM create/update payloads (resiliencyProfile.zoneMovement.isEnabled).
  • Implement zone move orchestration on az vm update --zone ... (force deallocate → PUT VM → start).
  • Regenerate/extend AAZ VM schemas and add a live-only scenario test for zone movement.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/azure-cli/azure/cli/command_modules/vm/custom.py Adds zone_movement handling and implements cross-zone move orchestration during vm update.
src/azure-cli/azure/cli/command_modules/vm/_template_builder.py Adds resiliencyProfile.zoneMovement into the ARM template for vm create.
src/azure-cli/azure/cli/command_modules/vm/_params.py Exposes --zone-movement argument for vm create and vm update.
src/azure-cli/azure/cli/command_modules/vm/operations/vm.py Extends snake_case conversion to include resiliencyProfile.zoneMovement.
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_create.py Updates VM Create AAZ model/version and adds resiliencyProfile schema support.
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_update.py Updates VM Update AAZ model/version and adds resiliencyProfile schema support.
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_show.py Updates VM Show AAZ model/version and adds resiliencyProfile schema support.
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_wait.py Updates VM Wait AAZ resource version and adds resiliencyProfile fields in schema.
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/vm/_deallocate.py Adds --force-deallocate support and updates API version/LRO behavior.
src/azure-cli/azure/cli/command_modules/vm/aaz/latest/sig/identity/__init__.py Exports generated sig identity wait command.
src/azure-cli/azure/cli/command_modules/vm/tests/latest/test_vm_commands.py Adds a live-only scenario test for create/update zone movement behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2089 to +2091
# Step 1: Force deallocate the VM
logger.warning('Changing to zone %s. Force-deallocating VM...', target_zones)
from .aaz.latest.vm import Deallocate as VMDeallocate
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These log lines represent normal workflow for a zone move (not an anomalous condition). Using logger.warning will surface as warnings to users and can be noisy; please use logger.info (or logger.debug) for progress messages and reserve warnings for actionable problems.

Copilot uses AI. Check for mistakes.
c.argument('align_regional_disks_to_vm_zone', options_list=['--align-regional-disks-to-vm-zone', '--align-regional-disks'], arg_type=get_three_state_flag(), min_api='2024-11-01', help='Specify whether the regional disks should be aligned/moved to the VM zone. This is applicable only for VMs with placement property set. Please note that this change is irreversible.')
c.argument('key_incarnation_id', type=int, min_api='2024-11-01', help='Increase the value of this property allows user to reset the key used for securing communication channel between guest and host.')
c.argument('security_type', arg_type=get_enum_type(["TrustedLaunch", "Standard", "ConfidentialVM"], default=None), help='Specify the security type of the virtual machine. The value Standard can be used if subscription has feature flag UseStandardSecurityType registered under Microsoft.Compute namespace. Refer to https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/preview-features for steps to enable required feature.')
c.argument('zone_movement', arg_type=get_three_state_flag(), help='Indicates if zone movement is enabled. By default isEnabled is set to false i.e VM can\'t be moved from one zone to another.')
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--zone-movement is a service/API-version gated feature (per issue metadata). Without a min_api guard here, the argument will appear in profiles/clouds where the property isn’t supported and will fail at runtime. Add an appropriate min_api (and/or a validator check) to gate the argument.

Suggested change
c.argument('zone_movement', arg_type=get_three_state_flag(), help='Indicates if zone movement is enabled. By default isEnabled is set to false i.e VM can\'t be moved from one zone to another.')
c.argument('zone_movement', arg_type=get_three_state_flag(), min_api='2024-11-01', help='Indicates if zone movement is enabled. By default isEnabled is set to false i.e VM can\'t be moved from one zone to another.')

Copilot uses AI. Check for mistakes.
Comment on lines 18 to 22
_aaz_info = {
"version": "2025-04-01",
"version": "2025-11-01",
"resources": [
["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.compute/virtualmachines/{}", "2025-04-01"],
["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.compute/virtualmachines/{}", "2025-11-01"],
]
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR metadata/linked issue indicates a minimum Compute API version of 2025-04-01, but these vm create/update/show operations are pinned to 2025-11-01. Please confirm the minimum required version for zone movement and either keep the API version at the minimum needed (to maximize cloud/profile compatibility) or update the PR description accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +14553 to +14556
@live_only()
@AllowLargeResponse(size_kb=999999)
@ResourceGroupPreparer(name_prefix='cli_test_vm_zone_movement_', location='eastus2euap')
def test_vm_zone_movement(self, resource_group):
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scenario is marked @live_only(), so it won’t run in the standard recorded test pipeline/CI and won’t protect the feature from regressions. If possible, add a recorded scenario (or a smaller-scope unit/integration test) for zone movement, or document why only live coverage is feasible.

Copilot uses AI. Check for mistakes.
Comment on lines +2114 to +2118
if zone_change:
try:
logger.warning('Updating VM with new zone...')
create_poller = VMCreate(cli_ctx=cmd.cli_ctx)(command_args=vm)
LongRunningOperation(cmd.cli_ctx)(create_poller)
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The zone-change path always blocks on the PUT and start operations via LongRunningOperation, which ignores the command’s --no-wait support (vm update is registered with supports_no_wait=True). Consider rejecting --no-wait when --zone triggers a move, or refactor to preserve no-wait semantics (e.g., return an LRO/poller for the orchestration).

Copilot uses AI. Check for mistakes.
Comment on lines +2073 to +2077
vm['resiliency_profile'] = {
'zone_movement': {
'is_enabled': zone_movement
}
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting vm['resiliency_profile'] = {...} overwrites any existing resiliency_profile fields that may already be present on the VM (from the GET) and could unintentionally drop other resiliency settings on update. Prefer merging into the existing dict (create if missing) and only updating zone_movement.is_enabled.

Suggested change
vm['resiliency_profile'] = {
'zone_movement': {
'is_enabled': zone_movement
}
}
if vm.get("resiliency_profile", None) is None:
vm["resiliency_profile"] = {}
if vm["resiliency_profile"].get("zone_movement", None) is None:
vm["resiliency_profile"]["zone_movement"] = {}
vm["resiliency_profile"]["zone_movement"]["is_enabled"] = zone_movement

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

act-observability-squad Auto-Assign Auto assign by bot Compute az vm/vmss/image/disk/snapshot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Zone resilient VM] [CLI] Enable new capability for new and existing VM

5 participants