Skip to content

Commit 701f77f

Browse files
committed
add support for multiple partner failover group commands; add automated test cases
1 parent bb2b31a commit 701f77f

File tree

3 files changed

+284
-5
lines changed

3 files changed

+284
-5
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
DatabaseCapabilitiesAdditionalDetails,
5959
ElasticPoolCapabilitiesAdditionalDetails,
6060
FailoverPolicyType,
61+
FailoverReadOnlyEndpointPolicy,
6162
ResourceIdType,
6263
ServicePrincipalType,
6364
SqlServerMinimalTlsVersionType,
@@ -1727,6 +1728,10 @@ def _configure_security_policy_storage_params(arg_ctx):
17271728
arg_type=try_planned_before_forced_failover_param_type)
17281729
c.argument('secondary_type', help="Databases secondary type on partner server",
17291730
arg_type=get_enum_type(FailoverGroupDatabasesSecondaryType))
1731+
c.argument('partner_server_ids', nargs='+', help="The list of partner server resource id's of the Failover Group")
1732+
c.argument('readonly_failover_policy', help="The policy of the read only endpoint of the Failover Group",
1733+
arg_type=get_enum_type(FailoverReadOnlyEndpointPolicy))
1734+
c.argument('readonly_endpoint_target', help="The resource id of the read only endpoint target server")
17301735

17311736
###############################################
17321737
# sql instance pool #

src/azure-cli/azure/cli/command_modules/sql/custom.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ def _get_database_identity(
576576
def _failover_group_update_common(
577577
instance,
578578
failover_policy=None,
579-
grace_period=None,):
579+
grace_period=None):
580580
'''
581581
Updates the failover group grace period and failover policy. Common logic for both Sterling and Managed Instance
582582
'''
@@ -644,6 +644,9 @@ class FailoverPolicyType(Enum):
644644
automatic = 'Automatic'
645645
manual = 'Manual'
646646

647+
class FailoverReadOnlyEndpointPolicy(Enum):
648+
enabled = 'Enabled'
649+
disabled = 'Disabled'
647650

648651
class FailoverGroupDatabasesSecondaryType(Enum):
649652
geo = 'Geo'
@@ -6519,7 +6522,10 @@ def failover_group_create(
65196522
partner_resource_group=None,
65206523
failover_policy=FailoverPolicyType.manual.value,
65216524
grace_period=1,
6522-
add_db=None):
6525+
add_db=None,
6526+
partner_server_ids=None,
6527+
readonly_failover_policy=FailoverReadOnlyEndpointPolicy.disabled.value,
6528+
readonly_endpoint_target=None):
65236529
'''
65246530
Creates a failover group.
65256531
'''
@@ -6552,14 +6558,24 @@ def failover_group_create(
65526558
add_db,
65536559
[])
65546560

6561+
if partner_server_ids is not None:
6562+
print(partner_server_ids)
6563+
partner_servers = [PartnerInfo(id=p) for p in partner_server_ids]
6564+
else:
6565+
partner_servers = [partner_server]
6566+
6567+
if readonly_endpoint_target is None:
6568+
readonly_endpoint_target = partner_server_id
6569+
65556570
failover_group_params = FailoverGroup(
6556-
partner_servers=[partner_server],
6571+
partner_servers=partner_servers,
65576572
databases=databases,
65586573
read_write_endpoint=FailoverGroupReadWriteEndpoint(
65596574
failover_policy=failover_policy,
65606575
failover_with_data_loss_grace_period_minutes=grace_period),
65616576
read_only_endpoint=FailoverGroupReadOnlyEndpoint(
6562-
failover_policy="Disabled")
6577+
failover_policy=readonly_failover_policy,
6578+
target_server=readonly_endpoint_target)
65636579
)
65646580

65656581
if secondary_type is not None:
@@ -6581,7 +6597,10 @@ def failover_group_update(
65816597
failover_policy=None,
65826598
grace_period=None,
65836599
add_db=None,
6584-
remove_db=None):
6600+
remove_db=None,
6601+
readonly_endpoint_target=None,
6602+
readonly_failover_policy=None,
6603+
partner_server_ids=None):
65856604
'''
65866605
Updates the failover group.
65876606
'''
@@ -6609,6 +6628,13 @@ def failover_group_update(
66096628
if secondary_type is not None:
66106629
instance.secondary_type = secondary_type
66116630

6631+
if partner_server_ids is not None:
6632+
instance.partner_servers = [PartnerInfo(id=p) for p in partner_server_ids]
6633+
6634+
instance.read_only_endpoint = FailoverGroupReadOnlyEndpoint(
6635+
failover_policy=readonly_failover_policy if readonly_failover_policy is not None else instance.read_only_endpoint.failover_policy,
6636+
target_server=readonly_endpoint_target if readonly_endpoint_target is not None else instance.readonly_endpoint_target)
6637+
66126638
return instance
66136639

66146640

src/azure-cli/azure/cli/command_modules/sql/tests/latest/test_sql_commands.py

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6804,6 +6804,254 @@ def __init__(self, name, group, location):
68046804
JMESPathCheck('length(@)', 0)
68056805
])
68066806

6807+
# create 2 servers in the same resource group, and 1 server in a different resource group
6808+
@ResourceGroupPreparer(parameter_name="resource_group_1",
6809+
parameter_name_for_location="resource_group_location_1")
6810+
@ResourceGroupPreparer(parameter_name="resource_group_2",
6811+
parameter_name_for_location="resource_group_location_2")
6812+
@SqlServerPreparer(parameter_name="server_name_1",
6813+
resource_group_parameter_name="resource_group_1",
6814+
location='northeurope')
6815+
@SqlServerPreparer(parameter_name="server_name_2",
6816+
resource_group_parameter_name="resource_group_1",
6817+
location='uksouth')
6818+
@SqlServerPreparer(parameter_name="server_name_3",
6819+
resource_group_parameter_name="resource_group_2",
6820+
location='swedencentral')
6821+
def test_sql_failover_group_mgmt_multiple_partners(self,
6822+
resource_group_1, resource_group_location_1,
6823+
resource_group_2, resource_group_location_2,
6824+
server_name_1, server_name_2, server_name_3):
6825+
# helper class so that it's clear which servers are in which groups
6826+
class ServerInfo: # pylint disable=too-few-public-methods
6827+
def __init__(self, name, group, location):
6828+
self.name = name
6829+
self.group = group
6830+
self.location = location
6831+
6832+
from azure.cli.core.commands.client_factory import get_subscription_id
6833+
6834+
s1 = ServerInfo(server_name_1, resource_group_1, resource_group_location_1)
6835+
s2 = ServerInfo(server_name_2, resource_group_1, resource_group_location_1)
6836+
s3 = ServerInfo(server_name_3, resource_group_2, resource_group_location_2)
6837+
6838+
failover_group_name = "fgclitest-multiplepartner123789"
6839+
6840+
database_name = "db1"
6841+
6842+
server2_id = "/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}".format(
6843+
get_subscription_id(self.cli_ctx),
6844+
resource_group_1,
6845+
server_name_2)
6846+
6847+
server3_id = "/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Sql/servers/{}".format(
6848+
get_subscription_id(self.cli_ctx),
6849+
resource_group_2,
6850+
server_name_3)
6851+
6852+
# Create database on primary server
6853+
self.cmd('sql db create -g {} --server {} --name {}'
6854+
.format(s1.group, s1.name, database_name),
6855+
checks=[
6856+
JMESPathCheck('resourceGroup', s1.group),
6857+
JMESPathCheck('name', database_name)
6858+
])
6859+
6860+
# Create Failover Group
6861+
self.cmd(
6862+
'sql failover-group create -n {} -g {} -s {} --partner-resource-group {} --partner-server {} --failover-policy Automatic --grace-period 2 --partner-server-ids {} {} --readonly-failover-policy Enabled'
6863+
.format(failover_group_name, s1.group, s1.name, s2.group, s2.name, server2_id, server3_id),
6864+
checks=[
6865+
JMESPathCheck('name', failover_group_name),
6866+
JMESPathCheck('resourceGroup', s1.group),
6867+
JMESPathCheck('partnerServers[0].id', server2_id),
6868+
JMESPathCheck('partnerServers[1].id', server3_id),
6869+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Automatic'),
6870+
JMESPathCheck('readWriteEndpoint.failoverWithDataLossGracePeriodMinutes', 120),
6871+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Enabled'),
6872+
JMESPathCheck('readOnlyEndpoint.targetServer', server2_id),
6873+
JMESPathCheck('length(databases)', 0)
6874+
])
6875+
6876+
# List of all failover groups on the primary server
6877+
self.cmd('sql failover-group list -g {} -s {}'
6878+
.format(s1.group, s1.name),
6879+
checks=[
6880+
JMESPathCheck('length(@)', 1),
6881+
JMESPathCheck('[0].name', failover_group_name),
6882+
JMESPathCheck('[0].replicationRole', 'Primary')
6883+
])
6884+
6885+
# Get Failover Group on a partner server and check if role is secondary
6886+
self.cmd('sql failover-group show -g {} -s {} -n {}'
6887+
.format(s2.group, s2.name, failover_group_name),
6888+
checks=[
6889+
JMESPathCheck('name', failover_group_name),
6890+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Automatic'),
6891+
JMESPathCheck('readWriteEndpoint.failoverWithDataLossGracePeriodMinutes', 120),
6892+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Enabled'),
6893+
JMESPathCheck('replicationRole', 'Secondary'),
6894+
JMESPathCheck('length(databases)', 0)
6895+
])
6896+
6897+
self.cmd('sql failover-group show -g {} -s {} -n {}'
6898+
.format(s3.group, s3.name, failover_group_name),
6899+
checks=[
6900+
JMESPathCheck('name', failover_group_name),
6901+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Automatic'),
6902+
JMESPathCheck('readWriteEndpoint.failoverWithDataLossGracePeriodMinutes', 120),
6903+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Enabled'),
6904+
JMESPathCheck('replicationRole', 'Secondary'),
6905+
JMESPathCheck('length(databases)', 0)
6906+
])
6907+
6908+
if self.in_recording:
6909+
time.sleep(60)
6910+
6911+
# Update Failover Group
6912+
self.cmd('sql failover-group update -g {} -s {} -n {} --grace-period 3 --add-db {} --readonly-endpoint-target {} --readonly-failover-policy Disabled'
6913+
.format(s1.group, s1.name, failover_group_name, database_name, server3_id),
6914+
checks=[
6915+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Automatic'),
6916+
JMESPathCheck('readWriteEndpoint.failoverWithDataLossGracePeriodMinutes', 180),
6917+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Disabled'),
6918+
JMESPathCheck('readOnlyEndpoint.targetServer', server3_id),
6919+
JMESPathCheck('length(databases)', 1)
6920+
])
6921+
6922+
# Check if properties got propagated to secondary server
6923+
self.cmd('sql failover-group show -g {} -s {} -n {}'
6924+
.format(s2.group, s2.name, failover_group_name),
6925+
checks=[
6926+
JMESPathCheck('name', failover_group_name),
6927+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Automatic'),
6928+
JMESPathCheck('readWriteEndpoint.failoverWithDataLossGracePeriodMinutes', 180),
6929+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Disabled'),
6930+
JMESPathCheck('readOnlyEndpoint.targetServer', server3_id),
6931+
JMESPathCheck('replicationRole', 'Secondary'),
6932+
JMESPathCheck('length(databases)', 1)
6933+
])
6934+
6935+
self.cmd('sql failover-group show -g {} -s {} -n {}'
6936+
.format(s3.group, s3.name, failover_group_name),
6937+
checks=[
6938+
JMESPathCheck('name', failover_group_name),
6939+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Automatic'),
6940+
JMESPathCheck('readWriteEndpoint.failoverWithDataLossGracePeriodMinutes', 180),
6941+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Disabled'),
6942+
JMESPathCheck('readOnlyEndpoint.targetServer', server3_id),
6943+
JMESPathCheck('replicationRole', 'Secondary'),
6944+
JMESPathCheck('length(databases)', 1)
6945+
])
6946+
6947+
# Check if database is created on partner side
6948+
self.cmd('sql db list -g {} -s {}'
6949+
.format(s2.group, s2.name),
6950+
checks=[
6951+
JMESPathCheck('length(@)', 2)
6952+
])
6953+
6954+
self.cmd('sql db list -g {} -s {}'
6955+
.format(s3.group, s3.name),
6956+
checks=[
6957+
JMESPathCheck('length(@)', 2)
6958+
])
6959+
6960+
if self.in_recording:
6961+
time.sleep(60)
6962+
6963+
# Check secondary type parameter functionality
6964+
self._test_failover_group_with_secondary_type(s1, s2, failover_group_name, "Standby")
6965+
6966+
self._test_failover_group_with_secondary_type(s1, s2, failover_group_name, "Geo")
6967+
6968+
# Update Failover Group failover policy to Manual
6969+
self.cmd('sql failover-group update -g {} -s {} -n {} --failover-policy Manual'
6970+
.format(s1.group, s1.name, failover_group_name),
6971+
checks=[
6972+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Manual'),
6973+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Disabled'),
6974+
JMESPathCheck('length(databases)', 1)
6975+
])
6976+
6977+
# Failover failover group from secondary server and then fail back
6978+
self._test_failover_group_failover(s1, s2, failover_group_name, self.FailoverType.planned)
6979+
6980+
self._test_failover_group_failover(s1, s2, failover_group_name, self.FailoverType.forced)
6981+
6982+
self._test_failover_group_failover(s1, s2, failover_group_name, self.FailoverType.hybrid)
6983+
6984+
self._test_failover_group_failover(s1, s3, failover_group_name, self.FailoverType.planned)
6985+
6986+
self._test_failover_group_failover(s1, s3, failover_group_name, self.FailoverType.forced)
6987+
6988+
self._test_failover_group_failover(s1, s3, failover_group_name, self.FailoverType.hybrid)
6989+
6990+
# Failover failover group from primary server (No-op)
6991+
self._test_failover_group_failover_from_primary(s1, s2, failover_group_name, self.FailoverType.planned)
6992+
6993+
self._test_failover_group_failover_from_primary(s1, s2, failover_group_name, self.FailoverType.forced)
6994+
6995+
self._test_failover_group_failover_from_primary(s1, s2, failover_group_name, self.FailoverType.hybrid)
6996+
6997+
# Remove database from failover group
6998+
self.cmd('sql failover-group update -g {} -s {} -n {} --remove-db {}'
6999+
.format(s1.group, s1.name, failover_group_name, database_name),
7000+
checks=[
7001+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Manual'),
7002+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Disabled'),
7003+
JMESPathCheck('length(databases)', 0)
7004+
])
7005+
7006+
# Check if database got removed
7007+
self.cmd('sql db show -g {} -s {} -n {}'
7008+
.format(s2.group, s2.name, database_name),
7009+
checks=[
7010+
JMESPathCheck('[0].failoverGroupId', 'None')
7011+
])
7012+
7013+
self.cmd('sql db show -g {} -s {} -n {}'
7014+
.format(s3.group, s3.name, database_name),
7015+
checks=[
7016+
JMESPathCheck('[0].failoverGroupId', 'None')
7017+
])
7018+
7019+
# Remove partner server from failover group
7020+
self.cmd('sql failover-group update -g {} -s {} -n {} --partner-server-ids {}'
7021+
.format(s1.group, s1.name, failover_group_name, server3_id),
7022+
checks=[
7023+
JMESPathCheck('partnerServers[0].id', server3_id),
7024+
JMESPathCheck('readWriteEndpoint.failoverPolicy', 'Automatic'),
7025+
JMESPathCheck('readWriteEndpoint.failoverWithDataLossGracePeriodMinutes', 180),
7026+
JMESPathCheck('readOnlyEndpoint.failoverPolicy', 'Disabled'),
7027+
JMESPathCheck('readOnlyEndpoint.targetServer', server3_id),
7028+
JMESPathCheck('length(databases)', 1)
7029+
])
7030+
7031+
# Check partner server was removed
7032+
self.cmd('sql failover-group list -g {} -s {}'
7033+
.format(s2.group, s2.name),
7034+
checks=[
7035+
JMESPathCheck('length(@)', 0)
7036+
])
7037+
7038+
# Drop failover group
7039+
self.cmd('sql failover-group delete -g {} -s {} -n {}'
7040+
.format(s1.group, s1.name, failover_group_name))
7041+
7042+
# Check if failover group really got dropped
7043+
self.cmd('sql failover-group list -g {} -s {}'
7044+
.format(s1.group, s1.name),
7045+
checks=[
7046+
JMESPathCheck('length(@)', 0)
7047+
])
7048+
7049+
self.cmd('sql failover-group list -g {} -s {}'
7050+
.format(s3.group, s3.name),
7051+
checks=[
7052+
JMESPathCheck('length(@)', 0)
7053+
])
7054+
68077055
class SqlVirtualClusterMgmtScenarioTest(ScenarioTest):
68087056
@ManagedInstancePreparer()
68097057
def test_sql_virtual_cluster_mgmt(self, mi, rg):

0 commit comments

Comments
 (0)