Skip to content

Commit 51899c0

Browse files
[rdbms] az postgres flexible-server migration: Add customer facing feature to migrate postgres db servers from Sterling to Meru platform (#18161)
* Support for "az postgres flexible-server migration" commands * Fix a linter error caused by copy-paste. * Fix more linter errors * Use custom_show_command instead of custom_command for 'show' to fix a linter finding. * 1. Remove the --subscription-id parameter. Use the CLI command param --subscription instead * Rename body to properties, which makes more sense in this context. * Fix isses reported by "azdev style rdbms" * Fix linter issues * Fix issues reported by azdev style check * Mark the migration command group as experimental * Cannot have is_preview and is_experimental at the same time. So removed is_preview. * 1. Added a confirmation for migration delete, 2. Deleted unused code, 3. Return instead of just the migration_id from update * Replace parameters db1, db2, ...db8 with an db-names parameter which takes a space-separated list of values
1 parent a8dce7f commit 51899c0

File tree

8 files changed

+597
-0
lines changed

8 files changed

+597
-0
lines changed

src/azure-cli/azure/cli/command_modules/rdbms/_helptext_pg.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,59 @@
176176
text: az postgres flexible-server list --resource-group testGroup
177177
"""
178178

179+
helps['postgres flexible-server migration'] = """
180+
type: group
181+
short-summary: Manage migration workflows for PostgreSQL Flexible Servers.
182+
"""
183+
184+
helps['postgres flexible-server migration create'] = """
185+
type: command
186+
short-summary: Create a new migration workflow for a flexible server.
187+
examples:
188+
- name: Start a migration workflow on the target server identified by the parameters. The configurations of the migration should be specified in the migrationConfig.json file.
189+
text: az postgres flexible-server migration create --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --properties @"migrationConfig.json"
190+
"""
191+
192+
helps['postgres flexible-server migration list'] = """
193+
type: command
194+
short-summary: List the migrations of a flexible server.
195+
examples:
196+
- name: List the currently active migrations of a target flexible server.
197+
text: az postgres flexible-server migration list --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --filter Active
198+
- name: List all (Active/Completed) migrations of a target flexible server.
199+
text: az postgres flexible-server migration list --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --filter All
200+
"""
201+
202+
helps['postgres flexible-server migration show'] = """
203+
type: command
204+
short-summary: Get the details of a specific migration.
205+
examples:
206+
- name: Get the details of a specific migration of a target flexible server.
207+
text: az postgres flexible-server migration show --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --migration-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
208+
"""
209+
210+
helps['postgres flexible-server migration update'] = """
211+
type: command
212+
short-summary: Update a specific migration.
213+
examples:
214+
- name: Allow the migration workflow to setup logical replication on the source. Note that this command will restart the source server.
215+
text: az postgres flexible-server migration update --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --migration-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --setup-replication
216+
- name: Space-separated list of DBs to migrate. A minimum of 1 and a maximum of 8 DBs can be specified. You can migrate more DBs concurrently using additional migrations. Note that each additional DB affects the performance of the source server.
217+
text: az postgres flexible-server migration update --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --migration-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --db-names db1 db2
218+
- name: Allow the migration workflow to overwrite the DB on the target.
219+
text: az postgres flexible-server migration update --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --migration-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --overwrite-dbs
220+
- name: Cutover the data migration. After this is complete, subsequent updates to the source DB will not be migrated to the target.
221+
text: az postgres flexible-server migration update --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --migration-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --cutover
222+
"""
223+
224+
helps['postgres flexible-server migration delete'] = """
225+
type: command
226+
short-summary: Delete a specific migration.
227+
examples:
228+
- name: Cancel/delete the migration workflow. The migration workflows can be canceled/deleted at any point.
229+
text: az postgres flexible-server migration delete --subscription xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group testGroup --name testServer --migration-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
230+
"""
231+
179232
helps['postgres flexible-server parameter'] = """
180233
type: group
181234
short-summary: Commands for managing server parameter values for flexible server.

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ def _flexible_server_params(command_group):
228228
help="Name of the server. The name can contain only lowercase letters, numbers, and the hyphen (-) character. Minimum 3 characters and maximum 63 characters.",
229229
local_context_attribute=LocalContextAttribute(name='server_name', actions=[LocalContextAction.SET, LocalContextAction.GET], scopes=['{} flexible-server'.format(command_group)]))
230230

231+
migration_id_arg_type = CLIArgumentType(metavar='NAME',
232+
help="ID of the migration.",
233+
local_context_attribute=LocalContextAttribute(name='migration_id', actions=[LocalContextAction.SET, LocalContextAction.GET], scopes=['{} flexible-server'.format(command_group)]))
234+
231235
administrator_login_setter_arg_type = CLIArgumentType(metavar='NAME',
232236
local_context_attribute=LocalContextAttribute(name='administrator_login', actions=[LocalContextAction.SET], scopes=['{} flexible-server'.format(command_group)]))
233237

@@ -400,6 +404,8 @@ def _flexible_server_params(command_group):
400404
else:
401405
c.argument('server_name', id_part='name', options_list=['--name', '-n'], arg_type=server_name_arg_type)
402406

407+
handle_migration_parameters(command_group, server_name_arg_type, migration_id_arg_type)
408+
403409
for scope in ['create', 'delete', 'show', 'update']:
404410
argument_context_string = '{} flexible-server firewall-rule {}'.format(command_group, scope)
405411
with self.argument_context(argument_context_string) as c:
@@ -482,5 +488,44 @@ def _flexible_server_params(command_group):
482488
c.argument('action_name', options_list=['--action-name'], help='The name of the github action')
483489
c.argument('branch', options_list=['--branch'], help='The name of the branch you want upload github action file. The default will be your current branch.')
484490

491+
def handle_migration_parameters(command_group, server_name_arg_type, migration_id_arg_type):
492+
for scope in ['create', 'show', 'list', 'update', 'delete']:
493+
argument_context_string = '{} flexible-server migration {}'.format(command_group, scope)
494+
with self.argument_context(argument_context_string) as c:
495+
c.argument('resource_group_name', arg_type=resource_group_name_type,
496+
help='Resource Group Name of the migration target server.')
497+
c.argument('server_name', id_part='name', options_list=['--name', '-n'], arg_type=server_name_arg_type,
498+
help='Migration target server name.')
499+
if scope == "create":
500+
c.argument('properties', options_list=['--properties', '-b'],
501+
help='Request properties. Use @{file} to load from a file. For quoting issues in different terminals, '
502+
'see https://github.com/Azure/azure-cli/blob/dev/doc/use_cli_effectively.md#quoting-issues')
503+
c.argument('migration_id', arg_type=migration_id_arg_type, options_list=['--migration-id'],
504+
help='Name or ID of the migration.')
505+
elif scope == "show":
506+
c.argument('migration_id', arg_type=migration_id_arg_type, options_list=['--migration-id'],
507+
help='Name or ID of the migration.')
508+
c.argument('level', options_list=['--level'], required=False,
509+
help='Specify the level of migration details requested. Valid values are Active and All. Active is the default.')
510+
elif scope == "list":
511+
c.argument('migration_filter', options_list=['--filter'], required=False,
512+
help='Indicate whether all the migrations or just the Active migrations are returned. Active is the default. Valid values are: Active, All.')
513+
elif scope == "update":
514+
c.argument('migration_id', arg_type=migration_id_arg_type, options_list=['--migration-id'],
515+
help='Name or ID of the migration.')
516+
c.argument('setup_logical_replication', options_list=['--setup-replication'], action='store_true', required=False,
517+
help='Allow the migration workflow to setup logical replication on the source. Note that this command will restart the source server.')
518+
c.argument('db_names', nargs='+', options_list=['--db-names', '--dbs'], required=False,
519+
help='Space-separated list of DBs to migrate. A minimum of 1 and a maximum of 8 DBs can be specified. You can migrate more DBs concurrently using additional migrations. Note that each additional DB affects the performance of the source server.')
520+
c.argument('overwrite_dbs', options_list=['--overwrite-dbs'], action='store_true', required=False,
521+
help='Allow the migration workflow to overwrite the DB on the target.')
522+
c.argument('cutover', options_list=['--cutover'], action='store_true', required=False,
523+
help='Cut-over the data migration. After this is complete, subsequent updates to the source DB will not be migrated to the target.')
524+
elif scope == "delete":
525+
c.argument('migration_id', arg_type=migration_id_arg_type, options_list=['--migration-id'],
526+
help='Name or ID of the migration.')
527+
c.argument('yes', options_list=['--yes', '-y'], action='store_true',
528+
help='Do not prompt for confirmation.')
529+
485530
_flexible_server_params('postgres')
486531
_flexible_server_params('mysql')

src/azure-cli/azure/cli/command_modules/rdbms/flexible_server_commands.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ def load_flexibleserver_command_table(self, _):
128128
custom_func_name='flexible_firewall_rule_update_custom_func',
129129
custom_func_type=flexible_server_custom_common)
130130

131+
with self.command_group('postgres flexible-server migration', postgres_flexible_firewall_rule_sdk,
132+
custom_command_type=flexible_servers_custom_postgres,
133+
client_factory=cf_postgres_flexible_firewall_rules,
134+
is_experimental=True) as g:
135+
g.custom_command('create', 'migration_create_func', custom_command_type=flexible_server_custom_common)
136+
g.custom_show_command('show', 'migration_show_func', custom_command_type=flexible_server_custom_common)
137+
g.custom_command('list', 'migration_list_func', custom_command_type=flexible_server_custom_common)
138+
g.custom_command('update', 'migration_update_func', custom_command_type=flexible_server_custom_common)
139+
g.custom_command('delete', 'migration_delete_func', custom_command_type=flexible_server_custom_common)
140+
131141
with self.command_group('postgres flexible-server parameter', postgres_flexible_config_sdk,
132142
custom_command_type=flexible_servers_custom_postgres,
133143
client_factory=cf_postgres_flexible_config,

src/azure-cli/azure/cli/command_modules/rdbms/flexible_server_custom_common.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
# --------------------------------------------------------------------------------------------
55

66
# pylint: disable=unused-argument, line-too-long
7+
8+
import uuid
79
from datetime import datetime
810
from knack.log import get_logger
911
from knack.util import CLIError
12+
from azure.cli.core.azclierror import MutuallyExclusiveArgumentError
13+
from azure.cli.core.commands.client_factory import get_subscription_id
14+
from azure.cli.core.util import send_raw_request
1015
from azure.cli.core.util import user_confirmation
1116
from azure.cli.core.azclierror import ClientRequestError, RequiredArgumentMissingError
1217
from azure.mgmt.rdbms.mysql_flexibleservers.operations._servers_operations import ServersOperations as MySqlServersOperations
@@ -75,6 +80,89 @@ def firewall_rule_create_func(client, resource_group_name, server_name, firewall
7580
parameters)
7681

7782

83+
def migration_create_func(cmd, client, resource_group_name, server_name, properties, migration_id=None):
84+
85+
subscription_id = get_subscription_id(cmd.cli_ctx)
86+
87+
if migration_id is None:
88+
# Convert a UUID to a string of hex digits in standard form
89+
migration_id = str(uuid.uuid4())
90+
91+
r = send_raw_request(cmd.cli_ctx, "put", "https://management.azure.com/subscriptions/{}/resourceGroups/{}/providers/Microsoft.DBforPostgreSQL/flexibleServers/{}/migrations/{}?api-version=2020-02-14-privatepreview".format(subscription_id, resource_group_name, server_name, migration_id), None, None, properties)
92+
93+
return r.json()
94+
95+
96+
def migration_show_func(cmd, client, resource_group_name, server_name, migration_id, level="Default"):
97+
98+
subscription_id = get_subscription_id(cmd.cli_ctx)
99+
100+
r = send_raw_request(cmd.cli_ctx, "get", "https://management.azure.com/subscriptions/{}/resourceGroups/{}/providers/Microsoft.DBforPostgreSQL/flexibleServers/{}/migrations/{}?level={}&api-version=2020-02-14-privatepreview".format(subscription_id, resource_group_name, server_name, migration_id, level))
101+
102+
return r.json()
103+
104+
105+
def migration_list_func(cmd, client, resource_group_name, server_name, migration_filter="Active"):
106+
107+
subscription_id = get_subscription_id(cmd.cli_ctx)
108+
109+
r = send_raw_request(cmd.cli_ctx, "get", "https://management.azure.com/subscriptions/{}/resourceGroups/{}/providers/Microsoft.DBforPostgreSQL/flexibleServers/{}/migrations?migrationListFilter={}&api-version=2020-02-14-privatepreview".format(subscription_id, resource_group_name, server_name, migration_filter))
110+
111+
return r.json()
112+
113+
114+
def migration_update_func(cmd, client, resource_group_name, server_name, migration_id, setup_logical_replication=None, db_names=None, overwrite_dbs=None, cutover=None):
115+
116+
subscription_id = get_subscription_id(cmd.cli_ctx)
117+
118+
operationSpecified = False
119+
if setup_logical_replication is True:
120+
operationSpecified = True
121+
properties = "{\"properties\": {\"setupLogicalReplicationOnSourceDBIfNeeded\": \"true\"} }"
122+
123+
if db_names is not None:
124+
if operationSpecified is True:
125+
raise MutuallyExclusiveArgumentError("Incorrect Usage: Can only specify one update operation.")
126+
operationSpecified = True
127+
prefix = "{ \"properties\": { \"dBsToMigrate\": ["
128+
db_names_str = "\"" + "\", \"".join(db_names) + "\""
129+
suffix = "] } }"
130+
properties = prefix + db_names_str + suffix
131+
132+
if overwrite_dbs is True:
133+
if operationSpecified is True:
134+
raise MutuallyExclusiveArgumentError("Incorrect Usage: Can only specify one update operation.")
135+
operationSpecified = True
136+
properties = "{\"properties\": {\"overwriteDBsInTarget\": \"true\"} }"
137+
138+
if cutover is True:
139+
if operationSpecified is True:
140+
raise MutuallyExclusiveArgumentError("Incorrect Usage: Can only specify one update operation.")
141+
operationSpecified = True
142+
properties = "{\"properties\": {\"triggerCutover\": \"true\"} }"
143+
144+
if operationSpecified is False:
145+
raise RequiredArgumentMissingError("Incorrect Usage: Atleast one update operation needs to be specified.")
146+
147+
r = send_raw_request(cmd.cli_ctx, "patch", "https://management.azure.com/subscriptions/{}/resourceGroups/{}/providers/Microsoft.DBforPostgreSQL/flexibleServers/{}/migrations/{}?api-version=2020-02-14-privatepreview".format(subscription_id, resource_group_name, server_name, migration_id), None, None, properties)
148+
149+
return r.json()
150+
151+
152+
def migration_delete_func(cmd, client, resource_group_name, server_name, migration_id, yes=None):
153+
154+
subscription_id = get_subscription_id(cmd.cli_ctx)
155+
156+
if not yes:
157+
user_confirmation(
158+
"Are you sure you want to delete the migration '{0}' on target server '{1}', resource group '{2}'".format(
159+
migration_id, server_name, resource_group_name))
160+
161+
r = send_raw_request(cmd.cli_ctx, "delete", "https://management.azure.com/subscriptions/{}/resourceGroups/{}/providers/Microsoft.DBforPostgreSQL/flexibleServers/{}/migrations/{}?api-version=2020-02-14-privatepreview".format(subscription_id, resource_group_name, server_name, migration_id))
162+
163+
return r.json()
164+
165+
78166
def firewall_rule_delete_func(client, resource_group_name, server_name, firewall_rule_name, yes=None):
79167
result = None
80168
if not yes:
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"properties": {
3+
"SourceDBServerResourceId": "subscriptions/6a37df99-a9de-48c4-91e5-7e6ab00b2362/resourceGroups/raganesa-s-s-pg-1/providers/Microsoft.DBforPostgreSQL/servers/raganesa-s-s-pg-1",
4+
"SecretParameters":
5+
{
6+
"AdminCredentials":
7+
{
8+
"SourceServerPassword": "xxxx",
9+
"TargetServerPassword": "xxxx"
10+
},
11+
"AADApp":
12+
{
13+
"ClientId": "xxxxxx",
14+
"TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
15+
"AadSecret": "xxxxx"
16+
}
17+
},
18+
"DBsToMigrate": [
19+
"dvdrental"
20+
],
21+
"MigrationResourceGroup":
22+
{
23+
"ResourceId": "subscriptions/6a37df99-a9de-48c4-91e5-7e6ab00b2362/resourceGroups/raganesa-DMSBuddy-Demo",
24+
"Location": "West US 2"
25+
},
26+
"SetupLogicalReplicationOnSourceDBIfNeeded": "true",
27+
"OverwriteDBsinTarget": "true",
28+
"TriggerCutover": "true"
29+
}
30+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"properties": {
3+
"SourceDBServerResourceId": "subscriptions/6a37df99-a9de-48c4-91e5-7e6ab00b2362/resourceGroups/raganesa-s-s-pg-1/providers/Microsoft.DBforPostgreSQL/servers/raganesa-s-s-pg-1-vnet-1",
4+
"SecretParameters":
5+
{
6+
"AdminCredentials":
7+
{
8+
"SourceServerPassword": "xxxx",
9+
"TargetServerPassword": "xxxx"
10+
},
11+
"AADApp":
12+
{
13+
"ClientId": "xxxxxx",
14+
"TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
15+
"AadSecret": "xxxxx"
16+
}
17+
},
18+
"DBsToMigrate": [
19+
"dvdrental"
20+
],
21+
"MigrationResourceGroup":
22+
{
23+
"ResourceId": "subscriptions/6a37df99-a9de-48c4-91e5-7e6ab00b2362/resourceGroups/raganesa-DMSBuddy-Demo",
24+
"Location": "West US 2"
25+
},
26+
"TargetDBServerSubnetResourceId": "subscriptions/6a37df99-a9de-48c4-91e5-7e6ab00b2362/resourceGroups/raganesa-s-s-pg-1/providers/Microsoft.Network/virtualNetworks/raganesa-s-s-pg-1-vnet/subnets/raganesa-s-s-pg-1-vnet-s-s-subnet",
27+
"SetupLogicalReplicationOnSourceDBIfNeeded": "true",
28+
"OverwriteDBsInTarget": "true",
29+
"TriggerCutover": "true"
30+
}
31+
}

0 commit comments

Comments
 (0)