From 55e3ba6ac754d1eebcf3dadb4c199ec0e832ebe8 Mon Sep 17 00:00:00 2001 From: Laveena Fulwani Date: Wed, 21 Jan 2026 14:06:27 +0530 Subject: [PATCH] Add fabric mirroring CLI commands: az mysql flexible-server mirroing enable/disable --- .../command_modules/mysql/_client_factory.py | 4 ++ .../azure/cli/command_modules/mysql/_help.py | 30 +++++++++++++ .../cli/command_modules/mysql/_params.py | 7 +++ .../cli/command_modules/mysql/commands.py | 16 ++++++- .../azure/cli/command_modules/mysql/custom.py | 44 +++++++++++++++++++ .../mysql/tests/latest/test_mysql_scenario.py | 38 ++++++++++++++++ 6 files changed, 137 insertions(+), 2 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/mysql/_client_factory.py b/src/azure-cli/azure/cli/command_modules/mysql/_client_factory.py index 20b91412d57..395a4805c12 100644 --- a/src/azure-cli/azure/cli/command_modules/mysql/_client_factory.py +++ b/src/azure-cli/azure/cli/command_modules/mysql/_client_factory.py @@ -141,6 +141,10 @@ def private_dns_link_client_factory(cli_ctx, subscription_id=None): subscription_id=subscription_id).virtual_network_links +def cf_mysql_flexible_fabric_mirroring_settings(cli_ctx, _): + return get_mysql_flexible_management_client(cli_ctx).fabric_mirroring_settings + + # Operations for single server def cf_mysql_servers(cli_ctx, _): return get_mysql_management_client(cli_ctx).servers diff --git a/src/azure-cli/azure/cli/command_modules/mysql/_help.py b/src/azure-cli/azure/cli/command_modules/mysql/_help.py index dcf4e272b3d..7d41d14b1ec 100644 --- a/src/azure-cli/azure/cli/command_modules/mysql/_help.py +++ b/src/azure-cli/azure/cli/command_modules/mysql/_help.py @@ -902,3 +902,33 @@ - name: Create a export backup for 'testsvr' with backup name 'testbackup'. text: az mysql flexible-server export create -g testgroup -n testsvr -b testbackup -u destsasuri """ + +helps['mysql flexible-server mirroring'] = """ +type: group +short-summary: Manage Microsoft Fabric Mirroring for Azure Database for MySQL Flexible Server. +long-summary: | + Fabric Mirroring allows continuous data replication from MySQL Flexible Server into Microsoft Fabric for analytics and reporting. +""" + +helps['mysql flexible-server mirroring enable'] = """ +type: command +short-summary: Enable Fabric Mirroring for a MySQL Flexible Server. +long-summary: | + Configures the server for Fabric Mirroring. + Requires a User Assigned Managed Identity (UAMI) resource ID with appropriate permissions. +examples: + - name: Enable Fabric Mirroring for server 'testsvr' using a User Assigned Managed Identity. + text: > + az mysql flexible-server mirroring enable -g testgroup -n testsvr \ + --identity-resource-id /subscriptions//resourceGroups//providers/Microsoft.ManagedIdentity/userAssignedIdentities/ +""" + +helps['mysql flexible-server mirroring disable'] = """ +type: command +short-summary: Disable Fabric Mirroring for a MySQL Flexible Server. +long-summary: | + Removes Fabric Mirroring configuration from the server. +examples: + - name: Disable Fabric Mirroring for server 'testsvr'. + text: az mysql flexible-server mirroring disable -g testgroup -n testsvr +""" diff --git a/src/azure-cli/azure/cli/command_modules/mysql/_params.py b/src/azure-cli/azure/cli/command_modules/mysql/_params.py index 53bf92808de..4acec76225a 100644 --- a/src/azure-cli/azure/cli/command_modules/mysql/_params.py +++ b/src/azure-cli/azure/cli/command_modules/mysql/_params.py @@ -736,3 +736,10 @@ def load_arguments(self, _): # pylint: disable=too-many-statements, too-many- elif scope == "check-name-availability": c.argument('migration_name', arg_type=migration_id_arg_type, options_list=['--migration-name'], help='Name of the migration.') + + with self.argument_context('mysql flexible-server mirroring enable') as c: + c.argument('server_name', id_part=None, arg_type=server_name_arg_type) + c.argument('uami', options_list=['--identity-resource-id'], help='Resource ID of the User Assigned Managed Identity (UAMI) used for Fabric Mirroring. Example: /subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identity-name}') + + with self.argument_context('mysql flexible-server mirroring disable') as c: + c.argument('server_name', id_part=None, arg_type=server_name_arg_type) diff --git a/src/azure-cli/azure/cli/command_modules/mysql/commands.py b/src/azure-cli/azure/cli/command_modules/mysql/commands.py index de1be5c41a8..668fbf33f04 100644 --- a/src/azure-cli/azure/cli/command_modules/mysql/commands.py +++ b/src/azure-cli/azure/cli/command_modules/mysql/commands.py @@ -2,7 +2,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- - from azure.cli.core.commands import CliCommandType from azure.cli.command_modules.mysql._client_factory import ( cf_mysql_advanced_threat_protection, @@ -17,7 +16,8 @@ cf_mysql_flexible_backups, cf_mysql_flexible_adadmin, cf_mysql_flexible_export, - cf_mysql_flexible_maintenances) + cf_mysql_flexible_maintenances, + cf_mysql_flexible_fabric_mirroring_settings) from ._transformers import ( table_transform_output, table_transform_output_list_servers, @@ -95,6 +95,11 @@ def load_command_table(self, _): client_factory=cf_mysql_flexible_maintenances ) + mysql_flexible_fabric_mirroring_settings_sdk = CliCommandType( + operations_tmpl='azure.mgmt.rdbms.mysql_flexibleservers.operations#FabricMirroringSettingsOperations.{}', + client_factory=cf_mysql_flexible_fabric_mirroring_settings + ) + # MERU COMMANDS mysql_custom = CliCommandType(operations_tmpl='azure.cli.command_modules.mysql.custom#{}') @@ -236,3 +241,10 @@ def load_command_table(self, _): g.custom_command('reschedule', 'flexible_server_maintenance_reschedule') g.custom_command('list', 'flexible_server_maintenance_list') g.custom_show_command('show', 'flexible_server_maintenance_show') + + with self.command_group('mysql flexible-server mirroring', + mysql_flexible_fabric_mirroring_settings_sdk, + custom_command_type=mysql_custom, + client_factory=cf_mysql_flexible_fabric_mirroring_settings) as g: + g.custom_command('enable', 'flexible_server_mirroring_enable') + g.custom_command('disable', 'flexible_server_mirroring_disable') diff --git a/src/azure-cli/azure/cli/command_modules/mysql/custom.py b/src/azure-cli/azure/cli/command_modules/mysql/custom.py index cf49d1b8036..04beab1828a 100644 --- a/src/azure-cli/azure/cli/command_modules/mysql/custom.py +++ b/src/azure-cli/azure/cli/command_modules/mysql/custom.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. @@ -2023,6 +2024,49 @@ def migrate_firewall_rules_from_single_to_flex(db_context, cmd, source_server_id firewall_rule_name=rule.name) +def _fm_settings_payload(state, uami=None): + """ + Build the flattened payload expected by the SDK for FabricMirroringSettings. + Because of x-ms-client-flatten, we set properties at the top level. + """ + # Most recent autorest will let us pass state/identityResourceId directly on the Settings model + return mysql_models.FabricMirroringSettings( + state=state, + identity_resource_id=uami # optional when disabling + ) + + +def flexible_server_mirroring_enable(cmd, client, resource_group_name, server_name, identity_resource_id): + """ + 'Enable' translates to PUT the 'Default' FabricMirroringSettings with state=Enabled and UAMI. + The Swagger limits settings name to 'Default'. + """ + if not identity_resource_id: + raise RequiredArgumentMissingError( + "Parameter --identity-resource-id is required when enabling fabric mirroring." + ) + + payload = _fm_settings_payload(state='Enabled', uami=identity_resource_id) + + # Long-running operation + poller = (getattr(client, 'begin_create_or_update', None) or getattr(client, 'begin_put'))( + resource_group_name, server_name, 'Default', payload + ) + return resolve_poller(poller, cmd.cli_ctx, 'Enable Fabric mirroring') + + +def flexible_server_mirroring_disable(cmd, client, resource_group_name, server_name): + """ + 'Disable' translates to PUT the 'Default' FabricMirroringSettings with state=Disabled. + identityResourceId can be omitted when disabling<97>service will deactivate/clean bindings. + """ + payload = _fm_settings_payload(state='Disabled', uami=None) + poller = (getattr(client, 'begin_create_or_update', None) or getattr(client, 'begin_put'))( + resource_group_name, server_name, 'Default', payload + ) + return resolve_poller(poller, cmd.cli_ctx, 'Disable Fabric mirroring') + + # pylint: disable=too-many-instance-attributes, too-few-public-methods class DbContext: def __init__(self, cmd=None, azure_sdk=None, logging_name=None, cf_firewall=None, cf_db=None, diff --git a/src/azure-cli/azure/cli/command_modules/mysql/tests/latest/test_mysql_scenario.py b/src/azure-cli/azure/cli/command_modules/mysql/tests/latest/test_mysql_scenario.py index 9416c44dc06..486b0283f4f 100644 --- a/src/azure-cli/azure/cli/command_modules/mysql/tests/latest/test_mysql_scenario.py +++ b/src/azure-cli/azure/cli/command_modules/mysql/tests/latest/test_mysql_scenario.py @@ -2351,3 +2351,41 @@ def _test_flexible_server_export_create_mgmt(self, database_engine, resource_gro # deletion of single server created self.cmd('{} flexible-server delete -g {} -n {} --yes'.format(database_engine, resource_group, server), checks=NoneCheck()) + + +class MySQLFabricMirroringEnableDisableTest(ScenarioTest): + """Scenario tests for Fabric Mirroring enable/disable commands.""" + + @AllowLargeResponse() + @ResourceGroupPreparer(location='eastus2euap') + def test_mysql_fabric_mirroring_enable_disable(self, resource_group): + # Arrange + server_name = self.create_random_name(SERVER_NAME_PREFIX, SERVER_NAME_MAX_LENGTH) + identity_name = self.create_random_name('identity', 24) + location = 'eastus2euap' + + # Create server (GeneralPurpose tier required for Fabric Mirroring, using Standard_D2ads_v5 valid in eastus2euap) + create_result = self.cmd('mysql flexible-server create -l {} -g {} -n {} --public-access {} --tier GeneralPurpose --sku-name {}' + .format(location, resource_group, server_name, '0.0.0.0', 'Standard_D2ads_v5')).get_output_in_json() + + # Verify server was created by checking the host contains the server name + self.assertIn(server_name, create_result['host']) + + # Set binlog_row_image to 'noblob' as required for Fabric Mirroring + self.cmd('mysql flexible-server parameter set -g {} -s {} -n binlog_row_image -v noblob' + .format(resource_group, server_name)) + + # Create User Assigned Managed Identity + identity_result = self.cmd('identity create -g {} -n {}'.format(resource_group, identity_name)).get_output_in_json() + identity_id = identity_result['id'] + + # Enable Fabric Mirroring + self.cmd('mysql flexible-server mirroring enable -g {} -n {} --identity-resource-id {}' + .format(resource_group, server_name, identity_id)) + + # Disable Fabric Mirroring + self.cmd('mysql flexible-server mirroring disable -g {} -n {}' + .format(resource_group, server_name)) + + # Cleanup + self.cmd('mysql flexible-server delete -g {} -n {} --yes'.format(resource_group, server_name))