Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Don't merge until feature announced] Add mysql replication commands #88

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/rdbms/azext_rdbms/_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,7 @@ def cf_mysql_db(cli_ctx, _):

def cf_postgres_db(cli_ctx, _):
return get_postgresql_management_client(cli_ctx).databases


def cf_mysql_replica(cli_ctx, _):
return get_mysql_management_client(cli_ctx).replicas
22 changes: 22 additions & 0 deletions src/rdbms/azext_rdbms/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,28 @@ def add_helps(command_group, server_type):
- name: List all {0} servers in a resource group.
text: az {1} server list -g testgroup
""".format(server_type, command_group)
helps['mysql server replica'] = """
type: group
short-summary: Manage cloud replication.
"""
helps['mysql server replica create'] = """
type: command
short-summary: Create a replica for a server.
examples:
- name: Create replica for server testsvr.
text: az mysql server replica create -n testreplsvr -g testgroup -s testsvr
- name: Create replica testreplsvr for server testsvr2, where 'testreplsvr' is in a different resource group.
text: |
az mysql server replica create -n testreplsvr -g testgroup \\
-s "/subscriptions/${{SubID}}/resourceGroups/${{ResourceGroup}}/providers/Microsoft.DBforMySQL/servers/testsvr2"
"""
helps['mysql server replica promote'] = """
type: command
short-summary: Promote Replica to being an individual server.
examples:
- name: Promote a replica server testsvr to an individual server.
text: az mysql server replica promote -g testgroup -n testsvr
"""
helps['{} server firewall-rule'.format(command_group)] = """
type: group
short-summary: Manage firewall rules for a server.
Expand Down
16 changes: 16 additions & 0 deletions src/rdbms/azext_rdbms/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ def _complex_params(command_group, engine):

c.argument('source_server_id', options_list=['--source-server', '-s'], help='The name or ID of the source server to restore from.')

with self.argument_context('mysql server replica create') as c:
# Need to add sku.name in request to bypass the backend bug
c.expand('sku', engine.models.Sku)
c.ignore('name', 'size', 'family', 'tier', 'capacity')

c.expand('properties', mysql.models.ServerPropertiesForReplica)
c.ignore('version', 'ssl_enforcement', 'storage_profile')

c.expand('parameters', mysql.models.ServerForCreate)
c.ignore('tags', 'location')

c.argument('source_server_id', options_list=['--source-server', '-s'], help='The name or ID of the primary server to create replica for.')

with self.argument_context('{} server configuration set'.format(command_group)) as c:
c.argument('value', help='Value of the configuration. If not provided, configuration value will be set to default.', validator=configuration_value_validator)
c.ignore('source')
Expand Down Expand Up @@ -108,3 +121,6 @@ def _complex_params(command_group, engine):
with self.argument_context(scope) as c:
c.argument('server_name', options_list=['--server-name', '-s'])
c.argument('configuration_name', id_part='child_name_1', options_list=['--name', '-n'])

with self.argument_context('mysql server replica list') as c:
c.argument('server_name', options_list=['--server-name', '-s'], help='Name of the primary server.')
13 changes: 13 additions & 0 deletions src/rdbms/azext_rdbms/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
cf_mysql_firewall_rules,
cf_mysql_config,
cf_mysql_log,
cf_mysql_replica,
cf_postgres_servers,
cf_postgres_db,
cf_postgres_firewall_rules,
Expand All @@ -35,6 +36,12 @@ def load_command_table(self, _):
client_factory=cf_postgres_servers
)

mysql_replica_sdk = CliCommandType(
operations_tmpl='azext_rdbms.mysql.operations.replicas_operations#ReplicasOperations.{}',
client_arg_name='self',
client_factory=cf_mysql_replica
)

mysql_firewall_rule_sdk = CliCommandType(
operations_tmpl='azext_rdbms.mysql.operations.firewall_rules_operations#FirewallRulesOperations.{}',
client_arg_name='self',
Expand Down Expand Up @@ -109,6 +116,12 @@ def load_command_table(self, _):
custom_func_name='_server_update_custom_func')
g.generic_wait_command('wait', getter_name='_server_postgresql_get', getter_type=rdbms_custom)

with self.command_group('mysql server replica', mysql_replica_sdk, client_factory=cf_mysql_servers) as g:
g.custom_command('create', '_replica_create', no_wait_param='no_wait')
g.custom_command('promote', '_replica_promote')
g.command('list', 'list_by_server')
g.generic_wait_command('wait', getter_name='_server_mysql_get', getter_type=rdbms_custom)

with self.command_group('mysql server firewall-rule', mysql_firewall_rule_sdk) as g:
g.command('create', 'create_or_update')
g.command('delete', 'delete', confirmation=True)
Expand Down
82 changes: 58 additions & 24 deletions src/rdbms/azext_rdbms/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,7 @@
# arguments and validators
def _server_restore(cmd, client, resource_group_name, server_name, parameters, no_wait=False, **kwargs):
source_server = kwargs['source_server_id']

if not is_valid_resource_id(source_server):
if len(source_server.split('/')) == 1:
provider = 'Microsoft.DBForMySQL' if isinstance(client, ServersOperations) else 'Microsoft.DBforPostgreSQL'
source_server = resource_id(subscription=get_subscription_id(cmd.cli_ctx),
resource_group=resource_group_name,
namespace=provider,
type='servers',
name=source_server)
else:
raise ValueError('The provided source-server {} is invalid.'.format(source_server))

source_server = __check_server_id(cmd, client, resource_group_name, source_server)
parameters.properties.source_server_id = source_server

# Here is a workaround that we don't support cross-region restore currently,
Expand All @@ -49,18 +38,7 @@ def _server_restore(cmd, client, resource_group_name, server_name, parameters, n
# auguments and validators
def _server_georestore(cmd, client, resource_group_name, server_name, parameters, no_wait=False, **kwargs):
source_server = kwargs['source_server_id']

if not is_valid_resource_id(source_server):
if len(source_server.split('/')) == 1:
provider = 'Microsoft.DBForMySQL' if isinstance(client, ServersOperations) else 'Microsoft.DBforPostgreSQL'
source_server = resource_id(subscription=get_subscription_id(cmd.cli_ctx),
resource_group=resource_group_name,
namespace=provider,
type='servers',
name=source_server)
else:
raise ValueError('The provided source-server {} is invalid.'.format(source_server))

source_server = __check_server_id(cmd, client, resource_group_name, source_server)
parameters.properties.source_server_id = source_server

id_parts = parse_resource_id(source_server)
Expand Down Expand Up @@ -109,6 +87,46 @@ def _server_update_custom_func(instance,
return params


# Custom functions for replicas #
def _replica_create(cmd, client, resource_group_name, server_name, parameters, no_wait=False, **kwargs):
# Set source server id
source_server = kwargs['source_server_id']
source_server = __check_server_id(cmd, client, resource_group_name, source_server)
parameters.properties.source_server_id = source_server

# Here is a workaround that we don't support cross-region restore currently,
# so the location must be set as the same as source server (not the resource group)
id_parts = parse_resource_id(source_server)
try:
source_server_object = client.get(id_parts['resource_group'], id_parts['name'])
parameters.location = source_server_object.location
# Here is a workaround that if not provide sku name, replica cannot be created.
parameters.sku.name = source_server_object.sku.name
except Exception as e:
raise ValueError('Unable to get source server: {}.'.format(str(e)))

return client.create(resource_group_name, server_name, parameters, raw=no_wait)


def _replica_promote(client, resource_group_name, server_name):
try:
server_object = client.get(resource_group_name, server_name)
except Exception as e:
raise ValueError('Unable to get server: {}.'.format(str(e)))

if server_object.replication_role != "Replica":
raise ValueError('Server {} is not a replica server.'.format(server_name))

from importlib import import_module
server_module_path = server_object.__module__
module = import_module(server_module_path.replace('server', 'server_update_parameters'))
ServerUpdateParameters = getattr(module, 'ServerUpdateParameters')

params = ServerUpdateParameters(replication_role='None')

return client.update(resource_group_name, server_name, params)


def _server_mysql_get(cmd, resource_group_name, server_name):
client = get_mysql_management_client(cmd.cli_ctx)
return client.servers.get(resource_group_name, server_name)
Expand Down Expand Up @@ -201,3 +219,19 @@ def _server_list_custom_func(client, resource_group_name=None):
if resource_group_name:
return client.list_by_resource_group(resource_group_name)
return client.list()


# Private functions #
def __check_server_id(cmd, client, resource_group_name, source_server):
if not is_valid_resource_id(source_server):
if len(source_server.split('/')) == 1:
provider = 'Microsoft.DBForMySQL' if isinstance(client, ServersOperations) else 'Microsoft.DBforPostgreSQL'
return resource_id(subscription=get_subscription_id(cmd.cli_ctx),
resource_group=resource_group_name,
namespace=provider,
type='servers',
name=source_server)
else:
raise ValueError('The provided source-server {} is invalid.'.format(source_server))
else:
return source_server
2 changes: 2 additions & 0 deletions src/rdbms/azext_rdbms/mysql/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .server_properties_for_default_create import ServerPropertiesForDefaultCreate
from .server_properties_for_restore import ServerPropertiesForRestore
from .server_properties_for_geo_restore import ServerPropertiesForGeoRestore
from .server_properties_for_replica import ServerPropertiesForReplica
from .sku import Sku
from .server import Server
from .server_for_create import ServerForCreate
Expand Down Expand Up @@ -54,6 +55,7 @@
'ServerPropertiesForDefaultCreate',
'ServerPropertiesForRestore',
'ServerPropertiesForGeoRestore',
'ServerPropertiesForReplica',
'Sku',
'Server',
'ServerForCreate',
Expand Down
16 changes: 9 additions & 7 deletions src/rdbms/azext_rdbms/mysql/models/log_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ class LogFile(ProxyResource):
:type log_file_name: str
:param size_in_kb: Size of the log file.
:type size_in_kb: long
:param created_time: Creation timestamp of the log file.
:type created_time: datetime
:param last_modified_time: Last modified timestamp of the log file.
:type last_modified_time: datetime
:ivar created_time: Creation timestamp of the log file.
:vartype created_time: datetime
:ivar last_modified_time: Last modified timestamp of the log file.
:vartype last_modified_time: datetime
:param log_file_type: Type of the log file.
:type log_file_type: str
:param url: The url to download the log file from.
Expand All @@ -42,6 +42,8 @@ class LogFile(ProxyResource):
'id': {'readonly': True},
'name': {'readonly': True},
'type': {'readonly': True},
'created_time': {'readonly': True},
'last_modified_time': {'readonly': True},
}

_attribute_map = {
Expand All @@ -56,11 +58,11 @@ class LogFile(ProxyResource):
'url': {'key': 'properties.url', 'type': 'str'},
}

def __init__(self, log_file_name=None, size_in_kb=None, created_time=None, last_modified_time=None, log_file_type=None, url=None):
def __init__(self, log_file_name=None, size_in_kb=None, log_file_type=None, url=None):
super(LogFile, self).__init__()
self.log_file_name = log_file_name
self.size_in_kb = size_in_kb
self.created_time = created_time
self.last_modified_time = last_modified_time
self.created_time = None
self.last_modified_time = None
self.log_file_type = log_file_type
self.url = url
16 changes: 15 additions & 1 deletion src/rdbms/azext_rdbms/mysql/models/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,21 @@ class Server(TrackedResource):
:type earliest_restore_date: datetime
:param storage_profile: Storage profile of a server.
:type storage_profile: ~azure.mgmt.rdbms.mysql.models.StorageProfile
:param replication_role: The replication role of the server.
:type replication_role: str
:param primary_server_id: The primary server id of a relica server.
:type primary_server_id: str
:param replica_capacity: The maximum number of replicas that a primary
server can have.
:type replica_capacity: int
"""

_validation = {
'id': {'readonly': True},
'name': {'readonly': True},
'type': {'readonly': True},
'location': {'required': True},
'replica_capacity': {'minimum': 0},
}

_attribute_map = {
Expand All @@ -75,9 +83,12 @@ class Server(TrackedResource):
'fully_qualified_domain_name': {'key': 'properties.fullyQualifiedDomainName', 'type': 'str'},
'earliest_restore_date': {'key': 'properties.earliestRestoreDate', 'type': 'iso-8601'},
'storage_profile': {'key': 'properties.storageProfile', 'type': 'StorageProfile'},
'replication_role': {'key': 'properties.replicationRole', 'type': 'str'},
'primary_server_id': {'key': 'properties.primaryServerId', 'type': 'str'},
'replica_capacity': {'key': 'properties.replicaCapacity', 'type': 'int'},
}

def __init__(self, location, tags=None, sku=None, administrator_login=None, version=None, ssl_enforcement=None, user_visible_state=None, fully_qualified_domain_name=None, earliest_restore_date=None, storage_profile=None):
def __init__(self, location, tags=None, sku=None, administrator_login=None, version=None, ssl_enforcement=None, user_visible_state=None, fully_qualified_domain_name=None, earliest_restore_date=None, storage_profile=None, replication_role=None, primary_server_id=None, replica_capacity=None):
super(Server, self).__init__(location=location, tags=tags)
self.sku = sku
self.administrator_login = administrator_login
Expand All @@ -87,3 +98,6 @@ def __init__(self, location, tags=None, sku=None, administrator_login=None, vers
self.fully_qualified_domain_name = fully_qualified_domain_name
self.earliest_restore_date = earliest_restore_date
self.storage_profile = storage_profile
self.replication_role = replication_role
self.primary_server_id = primary_server_id
self.replica_capacity = replica_capacity
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class ServerPropertiesForCreate(Model):

You probably want to use the sub-classes and not this class directly. Known
sub-classes are: ServerPropertiesForDefaultCreate,
ServerPropertiesForRestore, ServerPropertiesForGeoRestore
ServerPropertiesForRestore, ServerPropertiesForGeoRestore,
ServerPropertiesForReplica

:param version: Server version. Possible values include: '5.6', '5.7'
:type version: str or ~azure.mgmt.rdbms.mysql.models.ServerVersion
Expand All @@ -43,7 +44,7 @@ class ServerPropertiesForCreate(Model):
}

_subtype_map = {
'create_mode': {'Default': 'ServerPropertiesForDefaultCreate', 'PointInTimeRestore': 'ServerPropertiesForRestore', 'GeoRestore': 'ServerPropertiesForGeoRestore'}
'create_mode': {'Default': 'ServerPropertiesForDefaultCreate', 'PointInTimeRestore': 'ServerPropertiesForRestore', 'GeoRestore': 'ServerPropertiesForGeoRestore', 'Replica': 'ServerPropertiesForReplica'}
}

def __init__(self, version=None, ssl_enforcement=None, storage_profile=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 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.
#
# Code generated by Microsoft (R) AutoRest Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is
# regenerated.
# --------------------------------------------------------------------------

from .server_properties_for_create import ServerPropertiesForCreate


class ServerPropertiesForReplica(ServerPropertiesForCreate):
"""The properties to create a new replica.

:param version: Server version. Possible values include: '5.6', '5.7'
:type version: str or ~azure.mgmt.rdbms.mysql.models.ServerVersion
:param ssl_enforcement: Enable ssl enforcement or not when connect to
server. Possible values include: 'Enabled', 'Disabled'
:type ssl_enforcement: str or
~azure.mgmt.rdbms.mysql.models.SslEnforcementEnum
:param storage_profile: Storage profile of a server.
:type storage_profile: ~azure.mgmt.rdbms.mysql.models.StorageProfile
:param create_mode: Constant filled by server.
:type create_mode: str
:param source_server_id: The primary server id to create replica from.
:type source_server_id: str
"""

_validation = {
'create_mode': {'required': True},
'source_server_id': {'required': True},
}

_attribute_map = {
'version': {'key': 'version', 'type': 'str'},
'ssl_enforcement': {'key': 'sslEnforcement', 'type': 'SslEnforcementEnum'},
'storage_profile': {'key': 'storageProfile', 'type': 'StorageProfile'},
'create_mode': {'key': 'createMode', 'type': 'str'},
'source_server_id': {'key': 'sourceServerId', 'type': 'str'},
}

def __init__(self, source_server_id, version=None, ssl_enforcement=None, storage_profile=None):
super(ServerPropertiesForReplica, self).__init__(version=version, ssl_enforcement=ssl_enforcement, storage_profile=storage_profile)
self.source_server_id = source_server_id
self.create_mode = 'Replica'
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class ServerUpdateParameters(Model):
server. Possible values include: 'Enabled', 'Disabled'
:type ssl_enforcement: str or
~azure.mgmt.rdbms.mysql.models.SslEnforcementEnum
:param replication_role: The replication role of the server.
:type replication_role: str
:param tags: Application-specific metadata in the form of key-value pairs.
:type tags: dict[str, str]
"""
Expand All @@ -39,14 +41,16 @@ class ServerUpdateParameters(Model):
'administrator_login_password': {'key': 'properties.administratorLoginPassword', 'type': 'str'},
'version': {'key': 'properties.version', 'type': 'str'},
'ssl_enforcement': {'key': 'properties.sslEnforcement', 'type': 'SslEnforcementEnum'},
'replication_role': {'key': 'properties.replicationRole', 'type': 'str'},
'tags': {'key': 'tags', 'type': '{str}'},
}

def __init__(self, sku=None, storage_profile=None, administrator_login_password=None, version=None, ssl_enforcement=None, tags=None):
def __init__(self, sku=None, storage_profile=None, administrator_login_password=None, version=None, ssl_enforcement=None, replication_role=None, tags=None):
super(ServerUpdateParameters, self).__init__()
self.sku = sku
self.storage_profile = storage_profile
self.administrator_login_password = administrator_login_password
self.version = version
self.ssl_enforcement = ssl_enforcement
self.replication_role = replication_role
self.tags = tags
Loading