Skip to content

[Backup] Add Azure File Share cross-region restore support - #33809

Open
bharatpurwar wants to merge 1 commit into
Azure:devfrom
bharatpurwar:bharatpurwar/afs-crr-6d028121
Open

[Backup] Add Azure File Share cross-region restore support#33809
bharatpurwar wants to merge 1 commit into
Azure:devfrom
bharatpurwar:bharatpurwar/afs-crr-6d028121

Conversation

@bharatpurwar

@bharatpurwar bharatpurwar commented Jul 29, 2026

Copy link
Copy Markdown
Member

🤖 PR Validation — ❌ Action needed

Breaking Changes Tests
⚠️ None ❌ 128/130
⚠️AzureCLI-BreakingChangeTest
⚠️backup
rule cmd_name rule_message suggest_message
⚠️ 1006 - ParaAdd backup restore restore-azurefileshare cmd backup restore restore-azurefileshare added parameter use_secondary_region
❌AzureCLI-FullTest
️✔️acr
️✔️latest
️✔️3.12
️✔️3.14
️✔️acs
️✔️latest
️✔️3.12
️✔️3.14
️✔️advisor
️✔️latest
️✔️3.12
️✔️3.14
️✔️ams
️✔️latest
️✔️3.12
️✔️3.14
️✔️apim
️✔️latest
️✔️3.12
️✔️3.14
️✔️appconfig
️✔️latest
️✔️3.12
️✔️3.14
️✔️appservice
️✔️latest
️✔️3.12
️✔️3.14
️✔️aro
️✔️latest
️✔️3.12
️✔️3.14
❌backup
❌latest
❌3.12
Type Test Case Error Message Line
Failed test_afs_cross_region_restore self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f999f5219d0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f99a2e362a0>
command = 'backup restore restore-azurefileshare --subscription 00000000-0000-0000-0000-000000000000 -g afsbvtlonghaulrgne -v af...crrne --target-file-share afsfilesharetarget1hr --target-resource-group-name afsbvtlonghaulrgne --use-secondary-region'
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:153: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/patches.py:33: in handle_main_exception
    raise ex
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:812: in run_job
    return cmd_copy.exception_handler(ex)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/backup/exception_handler.py:11: in backup_exception_handler
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/backup/custom_base.py:511: in restore_azurefileshare
    return custom_afs.restore_AzureFileShare(cmd, client, resource_group_name, vault_name, rp_name, item, restore_mode,
src/azure-cli/azure/cli/command_modules/backup/custom_afs.py:315: in restore_AzureFileShare
    vault = vaults_cf(cmd.cli_ctx).get(resource_group_name, vault_name)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/mgmt/recoveryservices/operations/operations.py:915: in get
    pipeline_response: PipelineResponse = self.client.pipeline.run(  # pylint: disable=protected-access
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:242: in run
    return first_node.send(pipeline_request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/mgmt/core/policies/base.py:95: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/policies/redirect.py:205: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/policies/retry.py:545: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/policies/authentication.py:194: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/base.py:130: in send
    self.sender.send(request.http_request, **request.context.options),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/azure/core/pipeline/transport/requests_basic.py:375: in send
    response = self.session.request(  # type: ignore
env/lib/python3.12/site-packages/requests/sessions.py:592: in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/requests/sessions.py:706: in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.12/site-packages/requests/adapters.py:645: in send
    resp = conn.urlopen(
env/lib/python3.12/site-packages/urllib3/connectionpool.py:788: in urlopen
    response = self.make_request(
env/lib/python3.12/site-packages/urllib3/connectionpool.py:534: in make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
           

self = <VCRRequestsHTTPSConnection/mnt/vss/work/1/s/src/azure-cli/azure/cli/command_modules/backup/tests/latest/recordings/test_afs_cross_region_restore.yaml(host='management.azure.com', port=443) at 0x7f999efad4c0>
 = False, kwargs = {}

    def getresponse(self, _=False, **kwargs):
        """Retrieve the response"""
        # Check to see if the cassette has a response for this request. If so,
        # then return it
        if self.cassette.can_play_response_for(self._vcr_request):
            log.info(f"Playing response for {self._vcr_request} from cassette")
            response = self.cassette.play_response(self._vcr_request)
            return VCRHTTPResponse(response, self._vcr_request.uri)
        else:
            if self.cassette.write_protected and self.cassette.filter_request(self._vcr_request):
>               raise CannotOverwriteExistingCassetteException(
                    cassette=self.cassette,
                    failed_request=self._vcr_request,
                )
E               vcr.errors.CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/backup/tests/latest/recordings/test_afs_cross_region_restore.yaml') in your current record mode (RecordMode.ONCE: 'once').
E               No match for the request (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2026-05-01>)&nbsp;was&nbsp;found.
E               Found 3 similar requests with 1 different matcher(s) :
E               
E               1 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E               Matchers failed :
E               _custom_request_query_matcher - assertion failure :
E               None
E               
E               2 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E               Matchers failed :
E               _custom_request_query_matcher - assertion failure :
E               None
E               
E               3 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E               Matchers failed :
E               _custom_request_query_matcher - assertion failure :
E               None

env/lib/python3.12/site-packages/vcr/stubs/init.py:279: CannotOverwriteExistingCassetteException

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.backup.tests.latest.test_afs_commands.BackupTests testMethod=test_afs_cross_region_restore>

    @AllowLargeResponse()
    @record_only()
    def test_afs_cross_region_restore(self):
        self.kwargs.update({
            'sub': ('14d16a2a-56f6-4c75-b091-084df9640297'
                    if self.is_live else self.get_subscription_id()),
            'rg': 'afsbvtlonghaulrgne',
            'vault': 'afsbvtcrrlonghaulvaultne',
            'container': 'StorageContainer;Storage;afsbvtlonghaulrgne;afsbvtlonghaulcrrsane',
            'item': 'AzureFileShare;DCAEC29D689395B269170A2156B6771285091236BA6D65E2A8FAD70B90587754',
            'friendly_item': 'afs1hrtestfs',
            'target_sa': 'afsbvttargetsacrrne',
            'target_share': 'afsfilesharetarget1hr'
        })
    
        self.cmd(
            'backup item list --subscription {sub} -g {rg} -v {vault} '
            '--backup-management-type AzureStorage --workload-type AzureFileShare '
            '--use-secondary-region',
            checks=[
                self.check("length([?name == '{item}'])", 1),
                self.check(
                    "[?name == '{item}'] 
 [0].properties.friendlyName",
                    '{friendly_item}'
                )
            ]
        )
    
        self.cmd(
            'backup item show --subscription {sub} -g {rg} -v {vault} '
            '-c {container} -n {item} --backup-management-type AzureStorage '
            '--workload-type AzureFileShare --use-secondary-region',
            checks=[
                self.check('name', '{item}'),
                self.check('properties.friendlyName', '{friendly_item}'),
                self.check('properties.backupManagementType', 'AzureStorage')
            ]
        )
    
        self.kwargs['rp'] = self.cmd(
            'backup recoverypoint list --subscription {sub} -g {rg} -v {vault} '
            '-c {container} -i {item} --backup-management-type AzureStorage '
            '--workload-type AzureFileShare --use-secondary-region --query [0].name'
        ).get_output_in_json()
    
>       restore_job = self.cmd(
            'backup restore restore-azurefileshare --subscription {sub} -g {rg} -v {vault} '
            '-c {container} -i {item} -r {rp} --restore-mode AlternateLocation '
            '--resolve-conflict Overwrite --target-storage-account {target_sa} '
            '--target-file-share {target_share} --target-resource-group-name {rg} '
            '--use-secondary-region',
            checks=[
                self.check('properties.operation', 'CrossRegionRestore'),
                self.check('properties.status', 'InProgress')
            ]
        ).get_output_in_json()

src/azure-cli/azure/cli/command_modules/backup/tests/latest/test_afs_commands.py:234: 
                                        
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)
                                       _ 

self = <azure.cli.testsdk.base.ExecutionResult object at 0x7f999f5219d0>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7f99a2e362a0>
command = 'backup restore restore-azurefileshare --subscription 00000000-0000-0000-0000-000000000000 -g afsbvtlonghaulrgne -v af...crrne --target-file-share afsfilesharetarget1hr --target-resource-group-name afsbvtlonghaulrgne --use-secondary-region'
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
            self.output = stdout_buf.getvalue()
            self.applog = logging_buf.getvalue()
    
        except CannotOverwriteExistingCassetteException as ex:
>           raise AssertionError(ex)
E           AssertionError: Can't overwrite existing cassette ('/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/backup/tests/latest/recordings/test_afs_cross_region_restore.yaml') in your current record mode (RecordMode.ONCE: 'once').
E           No match for the request (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2026-05-01>)&nbsp;was&nbsp;found.
E           Found 3 similar requests with 1 different matcher(s) :
E           
E           1 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E           Matchers failed :
E           _custom_request_query_matcher - assertion failure :
E           None
E           
E           2 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E           Matchers failed :
E           _custom_request_query_matcher - assertion failure :
E           None
E           
E           3 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E           Matchers failed :
E           _custom_request_query_matcher - assertion failure :
E           None

src/azure-cli-testsdk/azure/cli/testsdk/base.py:308: AssertionError
❌3.14
Type Test Case Error Message Line
Failed test_afs_cross_region_restore self = <azure.cli.testsdk.base.ExecutionResult object at 0x7fd29cdf1e00>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7fd2a4a2a210>
command = 'backup restore restore-azurefileshare --subscription 00000000-0000-0000-0000-000000000000 -g afsbvtlonghaulrgne -v af...crrne --target-file-share afsfilesharetarget1hr --target-resource-group-name afsbvtlonghaulrgne --use-secondary-region'
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.14/site-packages/knack/cli.py:245: in invoke
    exit_code = self.exception_handler(ex)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/init.py:153: in exception_handler
    return handle_exception(ex)
           ^^^^^^^^^^^^^^^^^^^^
src/azure-cli-testsdk/azure/cli/testsdk/patches.py:33: in handle_main_exception
    raise ex
env/lib/python3.14/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:812: in run_job
    return cmd_copy.exception_handler(ex)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/backup/exception_handler.py:11: in backup_exception_handler
    raise ex
src/azure-cli-core/azure/cli/core/commands/init.py:789: in run_job
    result = cmd_copy(params)
             ^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/init.py:335: in call
    return self.handler(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/azure-cli-core/azure/cli/core/commands/command_operation.py:120: in handler
    return op(**command_args)
           ^^^^^^^^^^^^^^^^^^
src/azure-cli/azure/cli/command_modules/backup/custom_base.py:511: in restore_azurefileshare
    return custom_afs.restore_AzureFileShare(cmd, client, resource_group_name, vault_name, rp_name, item, restore_mode,
src/azure-cli/azure/cli/command_modules/backup/custom_afs.py:315: in restore_AzureFileShare
    vault = vaults_cf(cmd.cli_ctx).get(resource_group_name, vault_name)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/tracing/decorator.py:119: in wrapper_use_tracer
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/mgmt/recoveryservices/operations/operations.py:915: in get
    pipeline_response: PipelineResponse = self.client.pipeline.run(  # pylint: disable=protected-access
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:242: in run
    return first_node.send(pipeline_request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/mgmt/core/policies/base.py:95: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/policies/redirect.py:205: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/policies/retry.py:545: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/policies/authentication.py:194: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:98: in send
    response = self.next.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/base.py:130: in send
    self.sender.send(request.http_request, **request.context.options),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/azure/core/pipeline/transport/requests_basic.py:375: in send
    response = self.session.request(  # type: ignore
env/lib/python3.14/site-packages/requests/sessions.py:592: in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/requests/sessions.py:706: in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
env/lib/python3.14/site-packages/requests/adapters.py:645: in send
    resp = conn.urlopen(
env/lib/python3.14/site-packages/urllib3/connectionpool.py:788: in urlopen
    response = self.make_request(
env/lib/python3.14/site-packages/urllib3/connectionpool.py:534: in make_request
    response = conn.getresponse()
               ^^^^^^^^^^^^^^^^^^
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
           

self = <VCRRequestsHTTPSConnection/mnt/vss/work/1/s/src/azure-cli/azure/cli/command_modules/backup/tests/latest/recordings/test_afs_cross_region_restore.yaml(host='management.azure.com', port=443) at 0x7fd29cfffc50>
 = False, kwargs = {}

    def getresponse(self, _=False, **kwargs):
        """Retrieve the response"""
        # Check to see if the cassette has a response for this request. If so,
        # then return it
        if self.cassette.can_play_response_for(self._vcr_request):
            log.info(f"Playing response for {self._vcr_request} from cassette")
            response = self.cassette.play_response(self._vcr_request)
            return VCRHTTPResponse(response, self._vcr_request.uri)
        else:
            if self.cassette.write_protected and self.cassette.filter_request(self._vcr_request):
>               raise CannotOverwriteExistingCassetteException(
                    cassette=self.cassette,
                    failed_request=self._vcr_request,
                )
E               vcr.errors.CannotOverwriteExistingCassetteException: Can't overwrite existing cassette ('/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/backup/tests/latest/recordings/test_afs_cross_region_restore.yaml') in your current record mode (RecordMode.ONCE: 'once').
E               No match for the request (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2026-05-01>)&nbsp;was&nbsp;found.
E               Found 3 similar requests with 1 different matcher(s) :
E               
E               1 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E               Matchers failed :
E               _custom_request_query_matcher - assertion failure :
E               None
E               
E               2 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E               Matchers failed :
E               _custom_request_query_matcher - assertion failure :
E               None
E               
E               3 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E               Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E               Matchers failed :
E               _custom_request_query_matcher - assertion failure :
E               None

env/lib/python3.14/site-packages/vcr/stubs/init.py:279: CannotOverwriteExistingCassetteException

During handling of the above exception, another exception occurred:

self = <azure.cli.command_modules.backup.tests.latest.test_afs_commands.BackupTests testMethod=test_afs_cross_region_restore>

    @AllowLargeResponse()
    @record_only()
    def test_afs_cross_region_restore(self):
        self.kwargs.update({
            'sub': ('14d16a2a-56f6-4c75-b091-084df9640297'
                    if self.is_live else self.get_subscription_id()),
            'rg': 'afsbvtlonghaulrgne',
            'vault': 'afsbvtcrrlonghaulvaultne',
            'container': 'StorageContainer;Storage;afsbvtlonghaulrgne;afsbvtlonghaulcrrsane',
            'item': 'AzureFileShare;DCAEC29D689395B269170A2156B6771285091236BA6D65E2A8FAD70B90587754',
            'friendly_item': 'afs1hrtestfs',
            'target_sa': 'afsbvttargetsacrrne',
            'target_share': 'afsfilesharetarget1hr'
        })
    
        self.cmd(
            'backup item list --subscription {sub} -g {rg} -v {vault} '
            '--backup-management-type AzureStorage --workload-type AzureFileShare '
            '--use-secondary-region',
            checks=[
                self.check("length([?name == '{item}'])", 1),
                self.check(
                    "[?name == '{item}'] 
 [0].properties.friendlyName",
                    '{friendly_item}'
                )
            ]
        )
    
        self.cmd(
            'backup item show --subscription {sub} -g {rg} -v {vault} '
            '-c {container} -n {item} --backup-management-type AzureStorage '
            '--workload-type AzureFileShare --use-secondary-region',
            checks=[
                self.check('name', '{item}'),
                self.check('properties.friendlyName', '{friendly_item}'),
                self.check('properties.backupManagementType', 'AzureStorage')
            ]
        )
    
        self.kwargs['rp'] = self.cmd(
            'backup recoverypoint list --subscription {sub} -g {rg} -v {vault} '
            '-c {container} -i {item} --backup-management-type AzureStorage '
            '--workload-type AzureFileShare --use-secondary-region --query [0].name'
        ).get_output_in_json()
    
>       restore_job = self.cmd(
            'backup restore restore-azurefileshare --subscription {sub} -g {rg} -v {vault} '
            '-c {container} -i {item} -r {rp} --restore-mode AlternateLocation '
            '--resolve-conflict Overwrite --target-storage-account {target_sa} '
            '--target-file-share {target_share} --target-resource-group-name {rg} '
            '--use-secondary-region',
            checks=[
                self.check('properties.operation', 'CrossRegionRestore'),
                self.check('properties.status', 'InProgress')
            ]
        ).get_output_in_json()

src/azure-cli/azure/cli/command_modules/backup/tests/latest/test_afs_commands.py:234: 
                                        
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)
                                       _ 

self = <azure.cli.testsdk.base.ExecutionResult object at 0x7fd29cdf1e00>
cli_ctx = <azure.cli.core.mock.DummyCli object at 0x7fd2a4a2a210>
command = 'backup restore restore-azurefileshare --subscription 00000000-0000-0000-0000-000000000000 -g afsbvtlonghaulrgne -v af...crrne --target-file-share afsfilesharetarget1hr --target-resource-group-name afsbvtlonghaulrgne --use-secondary-region'
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
            self.output = stdout_buf.getvalue()
            self.applog = logging_buf.getvalue()
    
        except CannotOverwriteExistingCassetteException as ex:
>           raise AssertionError(ex)
E           AssertionError: Can't overwrite existing cassette ('/mnt/vss/_work/1/s/src/azure-cli/azure/cli/command_modules/backup/tests/latest/recordings/test_afs_cross_region_restore.yaml') in your current record mode (RecordMode.ONCE: 'once').
E           No match for the request (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2026-05-01>)&nbsp;was&nbsp;found.
E           Found 3 similar requests with 1 different matcher(s) :
E           
E           1 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E           Matchers failed :
E           _custom_request_query_matcher - assertion failure :
E           None
E           
E           2 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E           Matchers failed :
E           _custom_request_query_matcher - assertion failure :
E           None
E           
E           3 - (<Request (GET) https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/afsbvtlonghaulrgne/providers/Microsoft.RecoveryServices/vaults/afsbvtcrrlonghaulvaultne?api-version=2025-08-01>).
E           Matchers succeeded : ['method', 'scheme', 'host', 'port', 'path']
E           Matchers failed :
E           _custom_request_query_matcher - assertion failure :
E           None

src/azure-cli-testsdk/azure/cli/testsdk/base.py:308: AssertionError
️✔️batch
️✔️latest
️✔️3.12
️✔️3.14
️✔️batchai
️✔️latest
️✔️3.12
️✔️3.14
️✔️billing
️✔️latest
️✔️3.12
️✔️3.14
️✔️botservice
️✔️latest
️✔️3.12
️✔️3.14
️✔️cloud
️✔️latest
️✔️3.12
️✔️3.14
️✔️cognitiveservices
️✔️latest
️✔️3.12
️✔️3.14
️✔️compute_recommender
️✔️latest
️✔️3.12
️✔️3.14
️✔️computefleet
️✔️latest
️✔️3.12
️✔️3.14
️✔️config
️✔️latest
️✔️3.12
️✔️3.14
️✔️configure
️✔️latest
️✔️3.12
️✔️3.14
️✔️consumption
️✔️latest
️✔️3.12
️✔️3.14
️✔️container
️✔️latest
️✔️3.12
️✔️3.14
️✔️containerapp
️✔️latest
️✔️3.12
️✔️3.14
️✔️core
️✔️latest
️✔️3.12
️✔️3.14
️✔️cosmosdb
️✔️latest
️✔️3.12
️✔️3.14
️✔️databoxedge
️✔️latest
️✔️3.12
️✔️3.14
️✔️dls
️✔️latest
️✔️3.12
️✔️3.14
️✔️dms
️✔️latest
️✔️3.12
️✔️3.14
️✔️eventgrid
️✔️latest
️✔️3.12
️✔️3.14
️✔️eventhubs
️✔️latest
️✔️3.12
️✔️3.14
️✔️feedback
️✔️latest
️✔️3.12
️✔️3.14
️✔️find
️✔️latest
️✔️3.12
️✔️3.14
️✔️hdinsight
️✔️latest
️✔️3.12
️✔️3.14
️✔️identity
️✔️latest
️✔️3.12
️✔️3.14
️✔️iot
️✔️latest
️✔️3.12
️✔️3.14
️✔️keyvault
️✔️latest
️✔️3.12
️✔️3.14
️✔️lab
️✔️latest
️✔️3.12
️✔️3.14
️✔️managedservices
️✔️latest
️✔️3.12
️✔️3.14
️✔️maps
️✔️latest
️✔️3.12
️✔️3.14
️✔️marketplaceordering
️✔️latest
️✔️3.12
️✔️3.14
️✔️monitor
️✔️latest
️✔️3.12
️✔️3.14
️✔️mysql
️✔️latest
️✔️3.12
️✔️3.14
️✔️netappfiles
️✔️latest
️✔️3.12
️✔️3.14
️✔️network
️✔️latest
️✔️3.12
️✔️3.14
️✔️policyinsights
️✔️latest
️✔️3.12
️✔️3.14
️✔️postgresql
️✔️latest
️✔️3.12
️✔️3.14
️✔️privatedns
️✔️latest
️✔️3.12
️✔️3.14
️✔️profile
️✔️latest
️✔️3.12
️✔️3.14
️✔️rdbms
️✔️latest
️✔️3.12
️✔️3.14
️✔️redis
️✔️latest
️✔️3.12
️✔️3.14
️✔️relay
️✔️latest
️✔️3.12
️✔️3.14
️✔️resource
️✔️latest
️✔️3.12
️✔️3.14
️✔️role
️✔️latest
️✔️3.12
️✔️3.14
️✔️search
️✔️latest
️✔️3.12
️✔️3.14
️✔️security
️✔️latest
️✔️3.12
️✔️3.14
️✔️servicebus
️✔️latest
️✔️3.12
️✔️3.14
️✔️serviceconnector
️✔️latest
️✔️3.12
️✔️3.14
️✔️servicefabric
️✔️latest
️✔️3.12
️✔️3.14
️✔️signalr
️✔️latest
️✔️3.12
️✔️3.14
️✔️sql
️✔️latest
️✔️3.12
️✔️3.14
️✔️sqlvm
️✔️latest
️✔️3.12
️✔️3.14
️✔️storage
️✔️latest
️✔️3.12
️✔️3.14
️✔️synapse
️✔️latest
️✔️3.12
️✔️3.14
️✔️telemetry
️✔️latest
️✔️3.12
️✔️3.14
️✔️util
️✔️latest
️✔️3.12
️✔️3.14
️✔️vm
️✔️latest
️✔️3.12
️✔️3.14

Related command

az backup item list --use-secondary-region
az backup item show --use-secondary-region
az backup recoverypoint list --use-secondary-region
az backup restore restore-azurefileshare --use-secondary-region
az backup job show --use-secondary-region

Description

Adds Azure File Share cross-region restore support for full-share alternate-location restores. The implementation resolves protected items and recovery points from the secondary region, validates the target file share, builds the passive-stamp CRR request with the Azure Storage access token, triggers the restore, and tracks the resulting CRR job.

Also fixes Azure Storage CRR job formatting for task models without start/end timestamps.

Testing Guide

  • azdev test test_afs_cross_region_restore --series
  • azdev style backup
  • azdev linter backup

The scenario is playback-only and uses test_afs_cross_region_restore.yaml; it does not contact live Azure APIs during CI or normal local test runs.

History Notes

[Backup] az backup restore restore-azurefileshare: Add Azure File Share cross-region restore support.


Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7a8ea165-11b7-4fc8-9919-ec589073b1fe
@bharatpurwar
bharatpurwar requested a review from a team as a code owner July 29, 2026 04:06
Copilot AI review requested due to automatic review settings July 29, 2026 04:06
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@microsoft-github-policy-service

Copy link
Copy Markdown
Contributor

🔔 Routing this PR to @Azure/act-observability-squad.

@a0x1ab

a0x1ab commented Jul 29, 2026

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds Azure File Share cross-region restore (CRR) support to the backup command module, enabling --use-secondary-region for full-share alternate-location restores and validating target file shares before triggering the passive-stamp CRR workflow. Also adjusts CRR job/task formatting to tolerate task models missing timestamps and adds a record-only scenario test + recording.

Changes:

  • Enable CRR restore flow for Azure File Share restores via passive-stamp access token + CRR trigger, tracked as a CRR job.
  • Allow --use-secondary-region for AzureStorage recovery point listing and wire the flag through restore entrypoints/params.
  • Add playback-only scenario test and recording for the AFS CRR restore path; harden CRR task timestamp formatting.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/azure-cli/azure/cli/command_modules/backup/custom_afs.py Implements the AFS CRR restore path (passive stamp) and enables secondary-region recovery point listing; adds target file share validation.
src/azure-cli/azure/cli/command_modules/backup/custom_base.py Threads use_secondary_region into the restore_azurefileshare entrypoint and item resolution.
src/azure-cli/azure/cli/command_modules/backup/custom_common.py Allows AzureStorage to use --use-secondary-region by updating the “not supported” list.
src/azure-cli/azure/cli/command_modules/backup/custom_help.py Makes CRR task timestamp normalization resilient to missing start_time/end_time.
src/azure-cli/azure/cli/command_modules/backup/_params.py Adds --use-secondary-region to backup restore restore-azurefileshare.
src/azure-cli/azure/cli/command_modules/backup/_client_factory.py Introduces a Storage mgmt client factory and a file_shares_cf accessor used for target share validation.
src/azure-cli/azure/cli/command_modules/backup/tests/latest/test_afs_commands.py Adds a record-only scenario test covering AFS CRR restore and CRR job tracking.
src/azure-cli/azure/cli/command_modules/backup/tests/latest/recordings/test_afs_cross_region_restore.yaml Adds the test recording for the new scenario.

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

Comment on lines 18 to +25
from azure.cli.core.util import CLIError
from azure.cli.command_modules.backup._client_factory import protection_containers_cf, protectable_containers_cf, \
protection_policies_cf, backup_protection_containers_cf, backup_protectable_items_cf, \
resources_cf, backup_protected_items_cf, protected_items_cf
from azure.cli.core.azclierror import ArgumentUsageError, ValidationError
resources_cf, backup_protected_items_cf, protected_items_cf, \
recovery_points_crr_cf, recovery_points_passive_cf, aad_properties_cf, cross_region_restore_cf, vaults_cf, \
file_shares_cf
from azure.cli.core.azclierror import ArgumentUsageError, ValidationError, InvalidArgumentValueError
from azure.core.exceptions import ResourceNotFoundError
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants