Scale in and scale out
Handle adding and removing units of the mysql-innodb-cluster gracefully. Review and land: https://github.com/openstack-charmers/zaza/pull/353 https://github.com/openstack-charmers/zaza-openstack-tests/pull/275 Closes-Bug: #1874479 Closes-Bug: #1877546 Closes-Bug: #1877616 Depends-On: Ie658c42b095bcff822cdfb0b771d41704ddc85ea Change-Id: I39210c760e7204f84365d5dcb4eeedbc0889041f
This commit is contained in:
parent
1519f17aaa
commit
7cbf9a88b0
|
@ -29,6 +29,10 @@ reboot-cluster-from-complete-outage:
|
|||
description: |
|
||||
In the case of a complete outage, reboot the cluster from this instance's
|
||||
GTID superset.
|
||||
cluster-rescan:
|
||||
description: |
|
||||
Clean up cluster metadata by rescanning the cluster.
|
||||
See https://dev.mysql.com/doc/refman/8.0/en/mysql-innodb-cluster-working-with-cluster.html#rescan-cluster
|
||||
rejoin-instance:
|
||||
params:
|
||||
address:
|
||||
|
@ -38,6 +42,25 @@ rejoin-instance:
|
|||
Rejoin an instance to the cluster. *Note* This action must be run on an
|
||||
instance that is a functioning member of the cluster. For example, after a
|
||||
complete outage the unit which ran reboot-cluster-from-complete-outage.
|
||||
remove-instance:
|
||||
params:
|
||||
address:
|
||||
type: string
|
||||
description: Address of the instance to be removed from the cluster
|
||||
force:
|
||||
type: boolean
|
||||
description: Remove the instance even if it is unreachable.
|
||||
description: |
|
||||
Remove an instance from the cluster. *Note* This action must be run on an
|
||||
instance that is a functioning member of the cluster.
|
||||
add-instance:
|
||||
params:
|
||||
address:
|
||||
type: string
|
||||
description: Address of the instance to add to the cluster
|
||||
description: |
|
||||
Configure and add an instnace to the cluster. *Note* This action must be
|
||||
run on an instance that is a functioning member of the cluster.
|
||||
set-cluster-option:
|
||||
params:
|
||||
key:
|
||||
|
|
|
@ -171,6 +171,26 @@ def reboot_cluster_from_complete_outage(args):
|
|||
)
|
||||
|
||||
|
||||
def cluster_rescan(args):
|
||||
"""Rescan the cluster
|
||||
|
||||
Execute cluster.rescan() to clean up metadata.
|
||||
|
||||
:param args: sys.argv
|
||||
:type args: sys.argv
|
||||
:side effect: Calls instance.cluster_rescan
|
||||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
:action return: Dictionary with command output
|
||||
"""
|
||||
with charm.provide_charm_instance() as instance:
|
||||
output = instance.cluster_rescan()
|
||||
ch_core.hookenv.action_set({
|
||||
"output": output,
|
||||
"outcome": "Success"}
|
||||
)
|
||||
|
||||
|
||||
def rejoin_instance(args):
|
||||
"""Rejoin a given instance to the cluster.
|
||||
|
||||
|
@ -199,6 +219,60 @@ def rejoin_instance(args):
|
|||
)
|
||||
|
||||
|
||||
def add_instance(args):
|
||||
"""Add an instance to the cluster.
|
||||
|
||||
If a new instance is not able to be joined to the cluster, this action will
|
||||
configure and add the unit to the cluster.
|
||||
|
||||
:param args: sys.argv
|
||||
:type args: sys.argv
|
||||
:side effect: Calls instance.configure_and_add_instance
|
||||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
:action param address: String address of the instance to be joined
|
||||
:action return: Dictionary with command output
|
||||
"""
|
||||
# Note: Due to issues/# reactive does not initiate Endpoints during an
|
||||
# action execution. This is here to work around that until the issue is
|
||||
# resolved.
|
||||
reactive.Endpoint._startup()
|
||||
address = ch_core.hookenv.action_get("address")
|
||||
with charm.provide_charm_instance() as instance:
|
||||
output = instance.configure_and_add_instance(address)
|
||||
ch_core.hookenv.action_set({
|
||||
"output": output,
|
||||
"outcome": "Success"}
|
||||
)
|
||||
|
||||
|
||||
def remove_instance(args):
|
||||
"""Remove an instance from the cluster.
|
||||
|
||||
This action cleanly removes an instance from the cluster. If an instance
|
||||
has died and is unrecoverable it shows up in metadata as MISSING. This
|
||||
action will remove an instance from the metadata using the force option
|
||||
even if it is unreachable.
|
||||
|
||||
:param args: sys.argv
|
||||
:type args: sys.argv
|
||||
:side effect: Calls instance.remove_instance
|
||||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
:action param address: String address of the instance to be removed
|
||||
:action param force: Boolean force removal of missing instance
|
||||
:action return: Dictionary with command output
|
||||
"""
|
||||
address = ch_core.hookenv.action_get("address")
|
||||
force = ch_core.hookenv.action_get("force")
|
||||
with charm.provide_charm_instance() as instance:
|
||||
output = instance.remove_instance(address, force=force)
|
||||
ch_core.hookenv.action_set({
|
||||
"output": output,
|
||||
"outcome": "Success"}
|
||||
)
|
||||
|
||||
|
||||
def set_cluster_option(args):
|
||||
"""Set cluster option.
|
||||
|
||||
|
@ -231,7 +305,10 @@ ACTIONS = {"mysqldump": mysqldump, "cluster-status": cluster_status,
|
|||
"set-cluster-option": set_cluster_option,
|
||||
"reboot-cluster-from-complete-outage":
|
||||
reboot_cluster_from_complete_outage,
|
||||
"rejoin-instance": rejoin_instance}
|
||||
"rejoin-instance": rejoin_instance,
|
||||
"add-instance": add_instance,
|
||||
"remove-instance": remove_instance,
|
||||
"cluster-rescan": cluster_rescan}
|
||||
|
||||
|
||||
def main(args):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
actions.py
|
|
@ -0,0 +1 @@
|
|||
actions.py
|
|
@ -0,0 +1 @@
|
|||
actions.py
|
|
@ -470,8 +470,13 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
if cluster_address in self.cluster_address:
|
||||
addresses.append("localhost")
|
||||
|
||||
m_helper = self.get_db_helper()
|
||||
m_helper.connect(password=self.mysql_password)
|
||||
# If this is scale out and the cluster already exists, use the cluster
|
||||
# RW node for writes.
|
||||
m_helper = self.get_cluster_rw_db_helper()
|
||||
if not m_helper:
|
||||
m_helper = self.get_db_helper()
|
||||
m_helper.connect(password=self.mysql_password)
|
||||
|
||||
for address in addresses:
|
||||
try:
|
||||
m_helper.execute(SQL_CLUSTER_USER_CREATE.format(
|
||||
|
@ -603,12 +608,14 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
"""
|
||||
_primary = self.get_cluster_primary_address(nocache=True)
|
||||
_script = (
|
||||
"shell.connect('{}:{}@{}')\n"
|
||||
"cluster = dba.get_cluster('{}')\n"
|
||||
"cluster.set_option('{}', {})"
|
||||
.format(
|
||||
self.cluster_user, self.cluster_password, self.cluster_address,
|
||||
self.cluster_user, self.cluster_password,
|
||||
_primary or self.cluster_address,
|
||||
self.options.cluster_name, key, value))
|
||||
try:
|
||||
output = self.run_mysqlsh_script(_script).decode("UTF-8")
|
||||
|
@ -626,7 +633,7 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
|
||||
:param self: Self
|
||||
:type self: MySQLInnoDBClusterCharm instance
|
||||
:param address: Address of the MySQL instance to be configured
|
||||
:param address: Address of the MySQL instance to be clustered
|
||||
:type address: str
|
||||
:side effect: Calls self.run_mysqlsh_script
|
||||
:returns: This function is called for its side effect
|
||||
|
@ -639,6 +646,7 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
.format(address), "WARNING")
|
||||
return
|
||||
|
||||
_primary = self.get_cluster_primary_address(nocache=True)
|
||||
ch_core.hookenv.log("Adding instance, {}, to the cluster."
|
||||
.format(address), "INFO")
|
||||
_script = (
|
||||
|
@ -650,7 +658,7 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
"{{'recoveryMethod': 'clone'}})"
|
||||
.format(
|
||||
user=self.cluster_user, pw=self.cluster_password,
|
||||
caddr=self.cluster_address,
|
||||
caddr=_primary or self.cluster_address,
|
||||
name=self.options.cluster_name, addr=address))
|
||||
try:
|
||||
output = self.run_mysqlsh_script(_script)
|
||||
|
@ -711,6 +719,7 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
"""
|
||||
_primary = self.get_cluster_primary_address(nocache=True)
|
||||
ch_core.hookenv.log("Rejoin instance: {}.".format(address))
|
||||
_script = (
|
||||
"shell.connect('{user}:{pw}@{caddr}')\n"
|
||||
|
@ -718,12 +727,12 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
"cluster.rejoin_instance('{user}:{pw}@{addr}')"
|
||||
.format(
|
||||
user=self.cluster_user, pw=self.cluster_password,
|
||||
caddr=self.cluster_address,
|
||||
caddr=_primary or self.cluster_address,
|
||||
name=self.cluster_name, addr=address))
|
||||
try:
|
||||
output = self.run_mysqlsh_script(_script).decode("UTF-8")
|
||||
ch_core.hookenv.log(
|
||||
"Rejoin isntance {} successful: "
|
||||
"Rejoin instance {} successful: "
|
||||
"{}".format(address, output),
|
||||
level="DEBUG")
|
||||
return output
|
||||
|
@ -735,6 +744,107 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
# Reraise for action handling
|
||||
raise e
|
||||
|
||||
def remove_instance(self, address, force=False):
|
||||
"""Remove instance from the cluster
|
||||
|
||||
Execute the cluster.remove_instance(address) to remove an instance from
|
||||
the cluster.
|
||||
|
||||
:param self: Self
|
||||
:type self: MySQLInnoDBClusterCharm instance
|
||||
:side effect: Calls self.run_mysqlsh_script
|
||||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
"""
|
||||
_primary = self.get_cluster_primary_address(nocache=True)
|
||||
ch_core.hookenv.log("Rejoin instance: {}.".format(address))
|
||||
_script = (
|
||||
"shell.connect('{user}:{pw}@{caddr}')\n"
|
||||
"cluster = dba.get_cluster('{name}')\n"
|
||||
"cluster.remove_instance('{user}@{addr}', {{'force': {force}}})"
|
||||
.format(
|
||||
user=self.cluster_user, pw=self.cluster_password,
|
||||
caddr=_primary or self.cluster_address,
|
||||
name=self.cluster_name, addr=address, force=force))
|
||||
try:
|
||||
output = self.run_mysqlsh_script(_script).decode("UTF-8")
|
||||
ch_core.hookenv.log(
|
||||
"Remove instance {} successful: "
|
||||
"{}".format(address, output),
|
||||
level="DEBUG")
|
||||
return output
|
||||
except subprocess.CalledProcessError as e:
|
||||
ch_core.hookenv.log(
|
||||
"Failed removing instance {}: {}"
|
||||
.format(address, e.stderr.decode("UTF-8")),
|
||||
"ERROR")
|
||||
# Reraise for action handling
|
||||
raise e
|
||||
|
||||
def cluster_rescan(self):
|
||||
"""Rescan the cluster
|
||||
|
||||
Execute the cluster.rescan() to cleanup metadata.
|
||||
|
||||
:param self: Self
|
||||
:type self: MySQLInnoDBClusterCharm instance
|
||||
:side effect: Calls self.run_mysqlsh_script
|
||||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
"""
|
||||
_primary = self.get_cluster_primary_address(nocache=True)
|
||||
ch_core.hookenv.log("Rescanning the cluster.")
|
||||
_script = (
|
||||
"shell.connect('{user}:{pw}@{caddr}')\n"
|
||||
"cluster = dba.get_cluster('{name}')\n"
|
||||
"cluster.rescan()"
|
||||
.format(
|
||||
user=self.cluster_user, pw=self.cluster_password,
|
||||
caddr=_primary or self.cluster_address,
|
||||
name=self.cluster_name))
|
||||
try:
|
||||
output = self.run_mysqlsh_script(_script).decode("UTF-8")
|
||||
ch_core.hookenv.log(
|
||||
"Cluster rescan successful",
|
||||
level="DEBUG")
|
||||
return output
|
||||
except subprocess.CalledProcessError as e:
|
||||
ch_core.hookenv.log(
|
||||
"Failed rescanning the cluster.",
|
||||
"ERROR")
|
||||
# Reraise for action handling
|
||||
raise e
|
||||
|
||||
def configure_and_add_instance(self, address):
|
||||
"""Configure and add an instance to the cluster.
|
||||
|
||||
If an instance was not able to be joined to the cluster this method
|
||||
will make sure it is configured and add it to the cluster.
|
||||
|
||||
:param self: Self
|
||||
:type self: MySQLInnoDBClusterCharm instance
|
||||
:side effect: Calls self.create_user, self.configure_instance and
|
||||
self.add_instance_to_cluster.
|
||||
:returns: This function is called for its side effects
|
||||
:rtype: None
|
||||
"""
|
||||
ch_core.hookenv.log(
|
||||
"Configuring and adding instance to the cluster: {}."
|
||||
.format(address))
|
||||
cluster = reactive.endpoint_from_flag("cluster.available")
|
||||
if not cluster:
|
||||
raise Exception(
|
||||
"Cluster relation is not available in order to "
|
||||
"create cluster user for {}.".format(address))
|
||||
# Make sure we have the user in the DB
|
||||
for unit in cluster.all_joined_units:
|
||||
self.create_cluster_user(
|
||||
unit.received['cluster-address'],
|
||||
unit.received['cluster-user'],
|
||||
unit.received['cluster-password'])
|
||||
self.configure_instance(address)
|
||||
self.add_instance_to_cluster(address)
|
||||
|
||||
def get_cluster_status(self, nocache=False):
|
||||
"""Get cluster status
|
||||
|
||||
|
@ -750,6 +860,15 @@ class MySQLInnoDBClusterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
:returns: Dictionary cluster status output
|
||||
:rtype: Union[None, dict]
|
||||
"""
|
||||
# Speed up when we are not yet clustered
|
||||
if not reactive.is_flag_set(
|
||||
"leadership.set.cluster-instance-clustered-{}"
|
||||
.format(self.cluster_address)):
|
||||
ch_core.hookenv.log(
|
||||
"This instance is not yet clustered: cannot determine the "
|
||||
"cluster status.", "WARNING")
|
||||
return
|
||||
|
||||
# Try the cached version first
|
||||
if self._cached_cluster_status and not nocache:
|
||||
return self._cached_cluster_status
|
||||
|
|
|
@ -66,7 +66,7 @@ def create_local_cluster_user():
|
|||
@reactive.when('local.cluster.user-created')
|
||||
@reactive.when('cluster.connected')
|
||||
@reactive.when_not('cluster.available')
|
||||
def send_cluster_connection_info(cluster):
|
||||
def send_cluster_connection_info():
|
||||
"""Send cluster connection information.
|
||||
|
||||
Send cluster user, password and address information over the cluster
|
||||
|
@ -75,6 +75,7 @@ def send_cluster_connection_info(cluster):
|
|||
:param cluster: Cluster interface
|
||||
:type cluster: MySQLInnoDBClusterPeers object
|
||||
"""
|
||||
cluster = reactive.endpoint_from_flag("cluster.connected")
|
||||
ch_core.hookenv.log("Send cluster connection information.", "DEBUG")
|
||||
with charm.provide_charm_instance() as instance:
|
||||
cluster.set_cluster_connection_info(
|
||||
|
@ -86,7 +87,7 @@ def send_cluster_connection_info(cluster):
|
|||
|
||||
@reactive.when_not('local.cluster.all-users-created')
|
||||
@reactive.when('cluster.available')
|
||||
def create_remote_cluster_user(cluster):
|
||||
def create_remote_cluster_user():
|
||||
"""Create remote cluster user.
|
||||
|
||||
Create the remote cluster peer user and grant cluster permissions in the
|
||||
|
@ -95,6 +96,7 @@ def create_remote_cluster_user(cluster):
|
|||
:param cluster: Cluster interface
|
||||
:type cluster: MySQLInnoDBClusterPeers object
|
||||
"""
|
||||
cluster = reactive.endpoint_from_flag("cluster.available")
|
||||
ch_core.hookenv.log("Creating remote users.", "DEBUG")
|
||||
with charm.provide_charm_instance() as instance:
|
||||
for unit in cluster.all_joined_units:
|
||||
|
@ -129,7 +131,7 @@ def initialize_cluster():
|
|||
@reactive.when('local.cluster.all-users-created')
|
||||
@reactive.when('cluster.available')
|
||||
@reactive.when_not('leadership.set.cluster-instances-configured')
|
||||
def configure_instances_for_clustering(cluster):
|
||||
def configure_instances_for_clustering():
|
||||
"""Configure cluster peers for clustering.
|
||||
|
||||
Prepare peers to be added to the cluster.
|
||||
|
@ -137,6 +139,7 @@ def configure_instances_for_clustering(cluster):
|
|||
:param cluster: Cluster interface
|
||||
:type cluster: MySQLInnoDBClusterPeers object
|
||||
"""
|
||||
cluster = reactive.endpoint_from_flag("cluster.available")
|
||||
ch_core.hookenv.log("Configuring instances for clustering.", "DEBUG")
|
||||
with charm.provide_charm_instance() as instance:
|
||||
for unit in cluster.all_joined_units:
|
||||
|
@ -162,12 +165,13 @@ def configure_instances_for_clustering(cluster):
|
|||
@reactive.when('leadership.set.cluster-instances-configured')
|
||||
@reactive.when('cluster.available')
|
||||
@reactive.when_not('leadership.set.cluster-instances-clustered')
|
||||
def add_instances_to_cluster(cluster):
|
||||
def add_instances_to_cluster():
|
||||
"""Add cluster peers to the cluster.
|
||||
|
||||
:param cluster: Cluster interface
|
||||
:type cluster: MySQLInnoDBClusterPeers object
|
||||
"""
|
||||
cluster = reactive.endpoint_from_flag("cluster.available")
|
||||
ch_core.hookenv.log("Adding instances to cluster.", "DEBUG")
|
||||
with charm.provide_charm_instance() as instance:
|
||||
for unit in cluster.all_joined_units:
|
||||
|
@ -189,7 +193,7 @@ def add_instances_to_cluster(cluster):
|
|||
@reactive.when_not('leadership.is_leader')
|
||||
@reactive.when('leadership.set.cluster-created')
|
||||
@reactive.when('cluster.available')
|
||||
def signal_clustered(cluster):
|
||||
def signal_clustered():
|
||||
"""Signal unit clustered to peers.
|
||||
|
||||
Set this unit clustered on the cluster peer relation.
|
||||
|
@ -197,6 +201,7 @@ def signal_clustered(cluster):
|
|||
:param cluster: Cluster interface
|
||||
:type cluster: MySQLInnoDBClusterPeers object
|
||||
"""
|
||||
cluster = reactive.endpoint_from_flag("cluster.available")
|
||||
# Optimize clustering by causing a cluster relation changed
|
||||
with charm.provide_charm_instance() as instance:
|
||||
if reactive.is_flag_set(
|
||||
|
@ -271,3 +276,28 @@ def db_router_respond():
|
|||
"DB Router relation created DBs and users.", "DEBUG")
|
||||
reactive.clear_flag('endpoint.db-router.changed')
|
||||
instance.assess_status()
|
||||
|
||||
|
||||
@reactive.when('endpoint.cluster.changed.unit-configure-ready')
|
||||
@reactive.when('leadership.set.cluster-instances-clustered')
|
||||
@reactive.when('leadership.is_leader')
|
||||
def scale_out():
|
||||
"""Handle scale-out adding new nodes to an existing cluster."""
|
||||
|
||||
ch_core.hookenv.log("Scale out: add new nodes.", "DEBUG")
|
||||
with charm.provide_charm_instance() as instance:
|
||||
if not reactive.is_flag_set(
|
||||
"leadership.set.cluster-instance-clustered-{}"
|
||||
.format(instance.cluster_address)):
|
||||
ch_core.hookenv.log(
|
||||
"Unexpected edge case. This node is the leader but it is "
|
||||
"not yet clustered. As a non-cluster member it will not be "
|
||||
"able to join itself to the cluster. Run the 'add_instance' "
|
||||
"action on a member node with this unit's IP address to join "
|
||||
"this instance to the cluster.",
|
||||
"WARNING")
|
||||
return
|
||||
create_remote_cluster_user()
|
||||
configure_instances_for_clustering()
|
||||
add_instances_to_cluster()
|
||||
reactive.clear_flag('endpoint.cluster.changed.unit-configure-ready')
|
||||
|
|
|
@ -12,27 +12,31 @@ configure:
|
|||
- zaza.openstack.charm_tests.nova.setup.create_flavors
|
||||
- zaza.openstack.charm_tests.nova.setup.manage_ssh_key
|
||||
- zaza.openstack.charm_tests.keystone.setup.add_demo_user
|
||||
- scale_in_out:
|
||||
- zaza.openstack.charm_tests.keystone.setup.add_demo_user
|
||||
# The pause and resume test validates changing the R/W primary.
|
||||
# Running the keystone tests after the MySQLInnoDBClusterTests
|
||||
# validates DB functionality after a change in the R/W primary.
|
||||
tests:
|
||||
# Temporarily disabling the cold start tests due to
|
||||
# https://bugs.mysql.com/bug.php?id=97279
|
||||
#- zaza.openstack.charm_tests.mysql.tests.MySQLInnoDBClusterColdStartTest
|
||||
- zaza.openstack.charm_tests.mysql.tests.MySQLInnoDBClusterTests
|
||||
- zaza.openstack.charm_tests.keystone.tests.AuthenticationAuthorizationTest
|
||||
- eoan_full_model:
|
||||
#- zaza.openstack.charm_tests.mysql.tests.MySQLInnoDBClusterColdStartTest
|
||||
- zaza.openstack.charm_tests.mysql.tests.MySQLInnoDBClusterTests
|
||||
- zaza.openstack.charm_tests.keystone.tests.AuthenticationAuthorizationTest
|
||||
- focal_full_model:
|
||||
#- zaza.openstack.charm_tests.mysql.tests.MySQLInnoDBClusterColdStartTest
|
||||
- zaza.openstack.charm_tests.mysql.tests.MySQLInnoDBClusterTests
|
||||
- zaza.openstack.charm_tests.keystone.tests.AuthenticationAuthorizationTest
|
||||
- scale_in_out:
|
||||
# Temporarily disabling the cold start tests due to
|
||||
# https://bugs.mysql.com/bug.php?id=97279
|
||||
# - zaza.openstack.charm_tests.mysql.tests.MySQLInnoDBClusterColdStartTest
|
||||
- zaza.openstack.charm_tests.mysql.tests.MySQLInnoDBClusterScaleTest
|
||||
- zaza.openstack.charm_tests.keystone.tests.AuthenticationAuthorizationTest
|
||||
dev_bundles:
|
||||
gate_bundles:
|
||||
- focal_full_model: focal-full-ha
|
||||
- eoan_full_model: eoan-full-ha
|
||||
- scale_in_out: focal
|
||||
smoke_bundles:
|
||||
- focal
|
||||
tests_options:
|
||||
|
|
|
@ -202,6 +202,11 @@ class TestMySQLInnoDBClusterCharm(test_utils.PatchHelper):
|
|||
"mysqlrouter_username": "mysqlrouteruser",
|
||||
"mysqlrouter_hostname": self.nmr_unit7_ip}
|
||||
|
||||
self.unit1 = mock.MagicMock(name="FakeUnit")
|
||||
self.unit1.received.__getitem__.side_effect = self._fake_data
|
||||
self.cluster = mock.MagicMock()
|
||||
self.cluster.all_joined_units = [self.unit1]
|
||||
|
||||
# Generic interface
|
||||
self.interface = mock.MagicMock()
|
||||
|
||||
|
@ -427,6 +432,7 @@ class TestMySQLInnoDBClusterCharm(test_utils.PatchHelper):
|
|||
midbc = mysql_innodb_cluster.MySQLInnoDBClusterCharm()
|
||||
midbc.get_db_helper = mock.MagicMock()
|
||||
midbc.get_db_helper.return_value = _helper
|
||||
midbc.get_cluster_rw_db_helper = mock.MagicMock(return_value=None)
|
||||
# Non-local
|
||||
midbc.create_cluster_user(_addr, _user, _pass)
|
||||
_calls = [
|
||||
|
@ -533,10 +539,13 @@ class TestMySQLInnoDBClusterCharm(test_utils.PatchHelper):
|
|||
_remote_addr = "10.10.60.60"
|
||||
_name = "theCluster"
|
||||
self.get_relation_ip.return_value = _local_addr
|
||||
self.get_relation_ip.return_value = _local_addr
|
||||
self.data = {"cluster-password": _pass}
|
||||
self.is_flag_set.return_value = False
|
||||
|
||||
midbc = mysql_innodb_cluster.MySQLInnoDBClusterCharm()
|
||||
midbc.get_cluster_primary_address = mock.MagicMock(
|
||||
return_value=_local_addr)
|
||||
midbc._get_password = mock.MagicMock()
|
||||
midbc._get_password.side_effect = self._fake_data
|
||||
midbc.wait_until_connectable = mock.MagicMock()
|
||||
|
@ -1235,6 +1244,8 @@ class TestMySQLInnoDBClusterCharm(test_utils.PatchHelper):
|
|||
self.get_relation_ip.return_value = _local_addr
|
||||
|
||||
midbc = mysql_innodb_cluster.MySQLInnoDBClusterCharm()
|
||||
midbc.get_cluster_primary_address = mock.MagicMock(
|
||||
return_value=_local_addr)
|
||||
midbc.options.cluster_name = _name
|
||||
midbc.run_mysqlsh_script = mock.MagicMock()
|
||||
midbc.run_mysqlsh_script.return_value = _string.encode("UTF-8")
|
||||
|
@ -1269,7 +1280,7 @@ class TestMySQLInnoDBClusterCharm(test_utils.PatchHelper):
|
|||
"dba.reboot_cluster_from_complete_outage()"
|
||||
.format(
|
||||
midbc.cluster_user, midbc.cluster_password,
|
||||
midbc.cluster_address, midbc.options.cluster_name))
|
||||
midbc.cluster_address))
|
||||
self.assertEqual(_string, midbc.reboot_cluster_from_complete_outage())
|
||||
midbc.run_mysqlsh_script.assert_called_once_with(_script)
|
||||
|
||||
|
@ -1282,6 +1293,8 @@ class TestMySQLInnoDBClusterCharm(test_utils.PatchHelper):
|
|||
self.get_relation_ip.return_value = _local_addr
|
||||
|
||||
midbc = mysql_innodb_cluster.MySQLInnoDBClusterCharm()
|
||||
midbc.get_cluster_primary_address = mock.MagicMock(
|
||||
return_value=_local_addr)
|
||||
midbc.options.cluster_name = _name
|
||||
midbc.run_mysqlsh_script = mock.MagicMock()
|
||||
midbc.run_mysqlsh_script.return_value = _string.encode("UTF-8")
|
||||
|
@ -1298,3 +1311,95 @@ class TestMySQLInnoDBClusterCharm(test_utils.PatchHelper):
|
|||
midbc.cluster_user, midbc.cluster_password, _remote_addr))
|
||||
self.assertEqual(_string, midbc.rejoin_instance(_remote_addr))
|
||||
midbc.run_mysqlsh_script.assert_called_once_with(_script)
|
||||
|
||||
def test_remove_instance(self):
|
||||
_pass = "clusterpass"
|
||||
_name = "theCluster"
|
||||
_string = "status output"
|
||||
_local_addr = "10.10.50.50"
|
||||
_remote_addr = "10.10.50.70"
|
||||
self.get_relation_ip.return_value = _local_addr
|
||||
|
||||
midbc = mysql_innodb_cluster.MySQLInnoDBClusterCharm()
|
||||
midbc.get_cluster_primary_address = mock.MagicMock(
|
||||
return_value=_local_addr)
|
||||
midbc.options.cluster_name = _name
|
||||
midbc.run_mysqlsh_script = mock.MagicMock()
|
||||
midbc.run_mysqlsh_script.return_value = _string.encode("UTF-8")
|
||||
midbc._get_password = mock.MagicMock()
|
||||
midbc._get_password.return_value = _pass
|
||||
|
||||
_script = (
|
||||
"shell.connect('{}:{}@{}')\n"
|
||||
"cluster = dba.get_cluster('{}')\n"
|
||||
"cluster.remove_instance('{}@{}', {{'force': False}})"
|
||||
.format(
|
||||
midbc.cluster_user, midbc.cluster_password,
|
||||
midbc.cluster_address, midbc.options.cluster_name,
|
||||
midbc.cluster_user, _remote_addr))
|
||||
self.assertEqual(_string, midbc.remove_instance(_remote_addr))
|
||||
midbc.run_mysqlsh_script.assert_called_once_with(_script)
|
||||
|
||||
def test_cluster_rescan(self):
|
||||
_pass = "clusterpass"
|
||||
_name = "theCluster"
|
||||
_string = "status output"
|
||||
_local_addr = "10.10.50.50"
|
||||
self.get_relation_ip.return_value = _local_addr
|
||||
|
||||
midbc = mysql_innodb_cluster.MySQLInnoDBClusterCharm()
|
||||
midbc.get_cluster_primary_address = mock.MagicMock(
|
||||
return_value=_local_addr)
|
||||
midbc.options.cluster_name = _name
|
||||
midbc.run_mysqlsh_script = mock.MagicMock()
|
||||
midbc.run_mysqlsh_script.return_value = _string.encode("UTF-8")
|
||||
midbc._get_password = mock.MagicMock()
|
||||
midbc._get_password.return_value = _pass
|
||||
|
||||
_script = (
|
||||
"shell.connect('{}:{}@{}')\n"
|
||||
"cluster = dba.get_cluster('{}')\n"
|
||||
"cluster.rescan()"
|
||||
.format(
|
||||
midbc.cluster_user, midbc.cluster_password,
|
||||
midbc.cluster_address, midbc.options.cluster_name))
|
||||
self.assertEqual(_string, midbc.cluster_rescan())
|
||||
midbc.run_mysqlsh_script.assert_called_once_with(_script)
|
||||
|
||||
def test_configure_and_add_instance(self):
|
||||
_pass = "clusterpass"
|
||||
_name = "theCluster"
|
||||
_string = "status output"
|
||||
_local_addr = "10.10.50.50"
|
||||
_remote_addr = "10.10.50.70"
|
||||
_user = "user"
|
||||
self.get_relation_ip.return_value = _local_addr
|
||||
self.patch_object(
|
||||
mysql_innodb_cluster.reactive, "endpoint_from_flag",
|
||||
return_value=self.cluster)
|
||||
|
||||
midbc = mysql_innodb_cluster.MySQLInnoDBClusterCharm()
|
||||
midbc.get_cluster_primary_address = mock.MagicMock(
|
||||
return_value=_local_addr)
|
||||
midbc.options.cluster_name = _name
|
||||
midbc.run_mysqlsh_script = mock.MagicMock()
|
||||
midbc.run_mysqlsh_script.return_value = _string.encode("UTF-8")
|
||||
midbc._get_password = mock.MagicMock()
|
||||
midbc._get_password.return_value = _pass
|
||||
self.data = {
|
||||
"cluster-address": _remote_addr,
|
||||
"cluster-user": _user,
|
||||
"cluster-password": _pass,
|
||||
}
|
||||
_create_cluster_user = mock.MagicMock()
|
||||
midbc.create_cluster_user = _create_cluster_user
|
||||
_configure_instance = mock.MagicMock()
|
||||
midbc.configure_instance = _configure_instance
|
||||
_add_instance_to_cluster = mock.MagicMock()
|
||||
midbc.add_instance_to_cluster = _add_instance_to_cluster
|
||||
|
||||
midbc.configure_and_add_instance(address=_remote_addr)
|
||||
_create_cluster_user.assert_called_once_with(
|
||||
_remote_addr, _user, _pass)
|
||||
_configure_instance.assert_called_once_with(_remote_addr)
|
||||
_add_instance_to_cluster.assert_called_once_with(_remote_addr)
|
||||
|
|
|
@ -63,6 +63,10 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks):
|
|||
"leadership.set.cluster-instances-clustered",
|
||||
"endpoint.db-router.changed",
|
||||
"db-router.available",),
|
||||
"scale_out": (
|
||||
"endpoint.cluster.changed.unit-configure-ready",
|
||||
"leadership.set.cluster-instances-clustered",
|
||||
"leadership.is_leader",),
|
||||
},
|
||||
"when_not": {
|
||||
"leader_install": ("charm.installed",),
|
||||
|
@ -141,7 +145,8 @@ class TestMySQLInnoDBClusterHandlers(test_utils.PatchHelper):
|
|||
self.set_flag.assert_called_once_with("local.cluster.user-created")
|
||||
|
||||
def test_send_cluster_connection_info(self):
|
||||
handlers.send_cluster_connection_info(self.cluster)
|
||||
self.endpoint_from_flag.return_value = self.cluster
|
||||
handlers.send_cluster_connection_info()
|
||||
self.cluster.set_cluster_connection_info.assert_called_once_with(
|
||||
self.midbc.cluster_address,
|
||||
self.midbc.cluster_user,
|
||||
|
@ -154,7 +159,8 @@ class TestMySQLInnoDBClusterHandlers(test_utils.PatchHelper):
|
|||
self.data = {"cluster-address": _addr,
|
||||
"cluster-user": _user,
|
||||
"cluster-password": _pass}
|
||||
handlers.create_remote_cluster_user(self.cluster)
|
||||
self.endpoint_from_flag.return_value = self.cluster
|
||||
handlers.create_remote_cluster_user()
|
||||
self.midbc.create_cluster_user.assert_called_once_with(
|
||||
_addr, _user, _pass)
|
||||
self.cluster.set_unit_configure_ready.assert_called_once()
|
||||
|
@ -169,10 +175,11 @@ class TestMySQLInnoDBClusterHandlers(test_utils.PatchHelper):
|
|||
|
||||
def test_configure_instances_for_clustering(self):
|
||||
_addr = "10.10.10.30"
|
||||
self.endpoint_from_flag.return_value = self.cluster
|
||||
# Not ready
|
||||
self.is_flag_set.return_value = False
|
||||
self.data = {"cluster-address": _addr}
|
||||
handlers.configure_instances_for_clustering(self.cluster)
|
||||
handlers.configure_instances_for_clustering()
|
||||
self.midbc.configure_instance.assert_not_called()
|
||||
self.midbc.add_instance_to_cluster.assert_not_called()
|
||||
self.leader_set.assert_not_called()
|
||||
|
@ -181,7 +188,7 @@ class TestMySQLInnoDBClusterHandlers(test_utils.PatchHelper):
|
|||
self.midbc.reset_mock()
|
||||
self.is_flag_set.return_value = False
|
||||
self.data = {"cluster-address": _addr, "unit-configure-ready": True}
|
||||
handlers.configure_instances_for_clustering(self.cluster)
|
||||
handlers.configure_instances_for_clustering()
|
||||
self.midbc.configure_instance.assert_called_once_with(_addr)
|
||||
self.midbc.add_instance_to_cluster.assert_called_once_with(_addr)
|
||||
self.leader_set.assert_not_called()
|
||||
|
@ -189,7 +196,7 @@ class TestMySQLInnoDBClusterHandlers(test_utils.PatchHelper):
|
|||
# All ready
|
||||
self.midbc.reset_mock()
|
||||
self.is_flag_set.return_value = True
|
||||
handlers.configure_instances_for_clustering(self.cluster)
|
||||
handlers.configure_instances_for_clustering()
|
||||
self.midbc.configure_instance.assert_called_once_with(_addr)
|
||||
self.midbc.add_instance_to_cluster.assert_called_once_with(_addr)
|
||||
self.leader_set.assert_called_once_with(
|
||||
|
@ -197,32 +204,34 @@ class TestMySQLInnoDBClusterHandlers(test_utils.PatchHelper):
|
|||
|
||||
def test_add_instances_to_cluster(self):
|
||||
_addr = "10.10.10.30"
|
||||
self.endpoint_from_flag.return_value = self.cluster
|
||||
|
||||
# Some but not all
|
||||
self.is_flag_set.return_value = False
|
||||
self.data = {"cluster-address": _addr}
|
||||
handlers.add_instances_to_cluster(self.cluster)
|
||||
handlers.add_instances_to_cluster()
|
||||
self.midbc.add_instance_to_cluster.assert_called_once_with(_addr)
|
||||
self.leader_set.assert_not_called()
|
||||
|
||||
# All ready
|
||||
self.midbc.reset_mock()
|
||||
self.is_flag_set.return_value = True
|
||||
handlers.add_instances_to_cluster(self.cluster)
|
||||
handlers.add_instances_to_cluster()
|
||||
self.midbc.add_instance_to_cluster.assert_called_once_with(_addr)
|
||||
self.leader_set.assert_called_once_with(
|
||||
{"cluster-instances-clustered": True})
|
||||
|
||||
def test_signal_clustered(self):
|
||||
# Unit not clustered
|
||||
self.endpoint_from_flag.return_value = self.cluster
|
||||
self.is_flag_set.return_value = False
|
||||
handlers.signal_clustered(self.cluster)
|
||||
handlers.signal_clustered()
|
||||
self.cluster.set_unit_clustered.assert_not_called()
|
||||
|
||||
# Unit Clustered
|
||||
self.midbc.reset_mock()
|
||||
self.is_flag_set.return_value = True
|
||||
handlers.signal_clustered(self.cluster)
|
||||
handlers.signal_clustered()
|
||||
self.cluster.set_unit_clustered.assert_called_once()
|
||||
|
||||
def test_config_changed(self):
|
||||
|
|
Loading…
Reference in New Issue