Allow for multiple instantiations of mysql-router

Before this change it was not possible to run more than one
instantiation of mysql-router on a single unit. The Nova cells
deployments require nova-cloud-controller to be connected to two
different databases, therefore, requiring two instantiations of
mysql-router.

This change includes all the necessary steps to run multiple
instantiations of mysql-router on a single unit.

Review and merge charm-helpers PR:
https://github.com/juju/charm-helpers/pull/469

Closes-Bug: #1876188
Depends-On: Iafcc106fca44479e89b4b66a0a3988ffeee01f04
Change-Id: I76d1bfdccf9a551188c9f86c06ee0a22c07d47cf
This commit is contained in:
David Ames 2020-05-04 14:39:20 -07:00
parent a3457b978f
commit 4032165915
4 changed files with 67 additions and 35 deletions

View File

@ -14,7 +14,6 @@
import json
import os
import shutil
import subprocess
import charms_openstack.charm
@ -27,6 +26,8 @@ import charmhelpers.contrib.network.ip as ch_net_ip
import charmhelpers.contrib.database.mysql as mysql
import charmhelpers.contrib.openstack.templating as os_templating
MYSQLROUTER_CNF = "/var/lib/mysql/mysqlrouter/mysqlrouter.conf"
@ -52,14 +53,18 @@ def shared_db_address(cls):
class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
"""Charm class for the MySQLRouter charm."""
name = "mysqlrouter"
name = ch_core.hookenv.service_name()
packages = ["mysql-router"]
release = "stein"
release_pkg = "mysql-router"
required_relations = ["db-router", "shared-db"]
source_config_key = "source"
services = ["mysqlrouter"]
systemd_file = os.path.join(
"/etc/systemd/system",
"{}.service".format(name))
services = [name]
restart_map = {
MYSQLROUTER_CNF: services,
}
@ -170,6 +175,10 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
"""
return self.options.shared_db_address
@property
def mysqlrouter_port(self):
return self.options.base_port
@property
def mysqlrouter_working_dir(self):
"""Determine the path to the mysqlrouter working directory.
@ -179,7 +188,7 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
:returns: Path to the directory
:rtype: str
"""
return "{}/mysqlrouter".format(self.mysqlrouter_home_dir)
return "{}/{}".format(self.mysqlrouter_home_dir, self.name)
@property
def mysqlrouter_home_dir(self):
@ -236,12 +245,16 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
perms=0o755)
# Systemd File
_systemd_filename = "mysqlrouter.service"
_src_systemd = os.path.join(
ch_core.hookenv.charm_dir(), "files", _systemd_filename)
_dst_systemd = os.path.join("/etc/systemd/system", _systemd_filename)
shutil.copy(_src_systemd, _dst_systemd)
cmd = ["/usr/bin/systemctl", "enable", "mysqlrouter"]
ch_core.templating.render(
source="mysqlrouter.service",
template_loader=os_templating.get_loader(
'templates/', self.release),
target=self.systemd_file,
context=self.adapters_instance,
group=self.group,
perms=0o755,
)
cmd = ["/usr/bin/systemctl", "enable", self.name]
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
def get_db_helper(self):
@ -305,7 +318,8 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
try:
m_helper.connect(self.db_router_user,
self.db_router_password,
self.shared_db_address)
self.shared_db_address,
port=self.mysqlrouter_port)
return True
except mysql.MySQLdb._exceptions.OperationalError:
ch_core.hookenv.log("Could not connect to db", "DEBUG")
@ -363,7 +377,7 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
self.cluster_address),
"--directory", self.mysqlrouter_working_dir,
"--conf-use-sockets",
"--conf-base-port", str(self.options.base_port)]
"--conf-base-port", str(self.mysqlrouter_port)]
try:
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
ch_core.hookenv.log(output, "DEBUG")
@ -385,7 +399,7 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
:returns: This function is called for its side effect
:rtype: None
"""
ch_core.host.service_start("mysqlrouter")
ch_core.host.service_start(self.name)
reactive.flags.set_flag(MYSQL_ROUTER_STARTED)
def stop_mysqlrouter(self):
@ -399,7 +413,7 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
:returns: This function is called for its side effect
:rtype: None
"""
ch_core.host.service_stop("mysqlrouter")
ch_core.host.service_stop(self.name)
def restart_mysqlrouter(self):
"""Restart MySQL Router.
@ -413,7 +427,7 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
:returns: This function is called for its side effect
:rtype: None
"""
ch_core.host.service_restart("mysqlrouter")
ch_core.host.service_restart(self.name)
def proxy_db_and_user_requests(
self, receiving_interface, sending_interface):
@ -492,4 +506,5 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
_password,
allowed_units=_allowed_hosts,
prefix=prefix,
wait_timeout=_wait_timeout)
wait_timeout=_wait_timeout,
db_port=self.mysqlrouter_port)

View File

@ -6,8 +6,8 @@ After=network.target
[Service]
Type=exec
ExecStart=/var/lib/mysql/mysqlrouter/start.sh
ExecStop=/var/lib/mysql/mysqlrouter/stop.sh
ExecStart=/var/lib/mysql/{{ options.charm_instance.name }}/start.sh
ExecStop=/var/lib/mysql/{{ options.charm_instance.name }}/stop.sh
RemainAfterExit=yes
Restart=on-failure

View File

@ -218,9 +218,11 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
def test_mysqlrouter_working_dir(self):
mrc = mysql_router.MySQLRouterCharm()
_name = "keystone-mysql-router"
mrc.name = _name
self.assertEqual(
mrc.mysqlrouter_working_dir,
"/var/lib/mysql/mysqlrouter")
"/var/lib/mysql/{}".format(_name))
def test_mysqlrouter_home_dir(self):
mrc = mysql_router.MySQLRouterCharm()
@ -244,12 +246,14 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
self.patch_object(
mysql_router.charms_openstack.charm.OpenStackCharm,
"install", "super_install")
self.patch_object(mysql_router.shutil, "copy")
_name = "keystone-mysql-router"
self.patch_object(mysql_router.ch_core.templating, "render")
self.os.path.exists.return_value = False
self.group_exists.return_value = False
self.user_exists.return_value = False
mrc = mysql_router.MySQLRouterCharm()
mrc.configure_source = mock.MagicMock()
mrc.name = _name
mrc.install()
self.super_install.assert_called_once()
mrc.configure_source.assert_called_once()
@ -260,9 +264,9 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
self.mkdir.assert_called_once_with(
"/var/lib/mysql", group="mysql", owner="mysql", perms=0o755)
self.copy.assert_called_once()
self.render.assert_called_once()
self.subprocess.check_output.assert_called_once_with(
['/usr/bin/systemctl', 'enable', 'mysqlrouter'],
['/usr/bin/systemctl', 'enable', _name],
stderr=self.subprocess.STDOUT)
def test_get_db_helper(self):
@ -304,6 +308,7 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
_pass = "clusterpass"
_user = "mysqlrouteruser"
_addr = "127.0.0.1"
_port = 3316
self.endpoint_from_flag.return_value = self.db_router
self.db_router.password.return_value = _json_pass
@ -312,20 +317,21 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
self._exceptions.OperationalError = Exception
_helper = mock.MagicMock()
mrc = mysql_router.MySQLRouterCharm()
mrc.options.base_port = _port
mrc.get_db_helper = mock.MagicMock()
mrc.get_db_helper.return_value = _helper
# Connects
self.assertTrue(mrc.check_mysql_connection())
_helper.connect.assert_called_once_with(
_user, _pass, _addr)
_user, _pass, _addr, port=_port)
# Fails
_helper.reset_mock()
_helper.connect.side_effect = self._exceptions.OperationalError
self.assertFalse(mrc.check_mysql_connection())
_helper.connect.assert_called_once_with(
_user, _pass, _addr)
_user, _pass, _addr, port=_port)
def test_custom_assess_status_check(self):
_check = mock.MagicMock()
@ -394,26 +400,32 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
def test_start_mysqlrouter(self):
self.patch_object(mysql_router.ch_core.host, "service_start")
_name = "keystone-mysql-router"
mrc = mysql_router.MySQLRouterCharm()
mrc.name = _name
mrc.start_mysqlrouter()
self.service_start.assert_called_once_with("mysqlrouter")
self.service_start.assert_called_once_with(_name)
self.set_flag.assert_called_once_with(
mysql_router.MYSQL_ROUTER_STARTED)
def test_stop_mysqlrouter(self):
_name = "keystone-mysql-router"
self.patch_object(mysql_router.ch_core.host, "service_stop")
mrc = mysql_router.MySQLRouterCharm()
mrc.name = _name
mrc.stop_mysqlrouter()
self.service_stop.assert_called_once_with("mysqlrouter")
self.service_stop.assert_called_once_with(_name)
def test_restart_mysqlrouter(self):
_name = "keystone-mysql-router"
mrc = mysql_router.MySQLRouterCharm()
mrc.name = _name
self.patch_object(mysql_router.ch_core.host, "service_restart")
mrc.restart_mysqlrouter()
self.service_restart.assert_called_once_with("mysqlrouter")
self.service_restart.assert_called_once_with(_name)
def test_proxy_db_and_user_requests_no_prefix(self):
mrc = mysql_router.MySQLRouterCharm()
@ -440,10 +452,12 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
_json_pass = '"pass"'
_pass = json.loads(_json_pass)
_local_unit = "kmr/5"
_port = 3316
self.db_router.password.return_value = _json_pass
self.local_unit.return_value = _local_unit
mrc = mysql_router.MySQLRouterCharm()
mrc.options.base_port = _port
self.db_router.get_prefixes.return_value = [
mrc._unprefixed, mrc.db_prefix]
@ -455,7 +469,8 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
self.db_router, self.keystone_shared_db)
self.keystone_shared_db.set_db_connection_info.assert_called_once_with(
self.keystone_shared_db.relation_id, mrc.shared_db_address,
_pass, allowed_units=None, prefix=None, wait_timeout=None)
_pass, allowed_units=None, prefix=None, wait_timeout=None,
db_port=_port)
# Allowed Units and wait time set correctly
self.db_router.wait_timeout.return_value = _json_wait_time
@ -467,7 +482,7 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
self.keystone_shared_db.set_db_connection_info.assert_called_once_with(
self.keystone_shared_db.relation_id, mrc.shared_db_address,
_pass, allowed_units=self.keystone_unit_name, prefix=None,
wait_timeout=_wait_time)
wait_timeout=_wait_time, db_port=_port)
# Confirm msyqlrouter credentials are not sent over the shared-db
# relation
@ -483,10 +498,12 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
_nova = "nova"
_novaapi = "novaapi"
_novacell0 = "novacell0"
_port = 3316
self.db_router.password.return_value = _json_pass
self.local_unit.return_value = _local_unit
mrc = mysql_router.MySQLRouterCharm()
mrc.options.base_port = _port
self.db_router.get_prefixes.return_value = [
mrc.db_prefix, _nova, _novaapi, _novacell0]
@ -498,15 +515,15 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
mock.call(
self.nova_shared_db.relation_id, mrc.shared_db_address, _pass,
allowed_units=None, prefix=_nova,
wait_timeout=None),
wait_timeout=None, db_port=_port),
mock.call(
self.nova_shared_db.relation_id, mrc.shared_db_address, _pass,
allowed_units=None, prefix=_novaapi,
wait_timeout=None),
wait_timeout=None, db_port=_port),
mock.call(
self.nova_shared_db.relation_id, mrc.shared_db_address, _pass,
allowed_units=None, prefix=_novacell0,
wait_timeout=None),
wait_timeout=None, db_port=_port),
]
self.nova_shared_db.set_db_connection_info.assert_has_calls(
_calls, any_order=True)
@ -520,15 +537,15 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
mock.call(
self.nova_shared_db.relation_id, mrc.shared_db_address, _pass,
allowed_units=self.nova_unit_name, prefix=_nova,
wait_timeout=_wait_time),
wait_timeout=_wait_time, db_port=_port),
mock.call(
self.nova_shared_db.relation_id, mrc.shared_db_address, _pass,
allowed_units=self.nova_unit_name, prefix=_novaapi,
wait_timeout=_wait_time),
wait_timeout=_wait_time, db_port=_port),
mock.call(
self.nova_shared_db.relation_id, mrc.shared_db_address, _pass,
allowed_units=self.nova_unit_name, prefix=_novacell0,
wait_timeout=_wait_time),
wait_timeout=_wait_time, db_port=_port),
]
self.nova_shared_db.set_db_connection_info.assert_has_calls(
_calls, any_order=True)