Use systemd to start/stop mysqlrouter
Systemd control of mysqlrouter Actions for start/stop/restart Change-Id: I05b0a5a5f3bab75f3c9c8ff88e12aa3e8ecb9ebb
This commit is contained in:
parent
454f6e9f8b
commit
357070620c
|
@ -0,0 +1,9 @@
|
|||
stop-mysqlrouter:
|
||||
description: |
|
||||
Stop the mysqlrouter daemon
|
||||
start-mysqlrouter:
|
||||
description: |
|
||||
Start the mysqlrouter daemon
|
||||
restart-mysqlrouter:
|
||||
description: |
|
||||
Restart the mysqlrouter daemon
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/local/sbin/charm-env python3
|
||||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
# Load modules from $CHARM_DIR/lib
|
||||
_path = os.path.dirname(os.path.realpath(__file__))
|
||||
_lib = os.path.abspath(os.path.join(_path, "../lib"))
|
||||
_reactive = os.path.abspath(os.path.join(_path, "../reactive"))
|
||||
|
||||
|
||||
def _add_path(path):
|
||||
if path not in sys.path:
|
||||
sys.path.insert(1, path)
|
||||
|
||||
|
||||
_add_path(_lib)
|
||||
_add_path(_reactive)
|
||||
|
||||
import charms_openstack.charm as charm
|
||||
import charmhelpers.core as ch_core
|
||||
import charms_openstack.bus
|
||||
charms_openstack.bus.discover()
|
||||
|
||||
|
||||
def stop_mysqlrouter(args):
|
||||
"""Display cluster status
|
||||
|
||||
Return cluster.status() as a JSON encoded dictionary
|
||||
|
||||
:param args: sys.argv
|
||||
:type args: sys.argv
|
||||
:side effect: Calls instance.get_cluster_status
|
||||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
:action return: Dictionary with command output
|
||||
"""
|
||||
with charm.provide_charm_instance() as instance:
|
||||
try:
|
||||
instance.stop_mysqlrouter()
|
||||
instance.assess_status()
|
||||
ch_core.hookenv.action_set({"outcome": "Success"})
|
||||
except subprocess.CalledProcessError as e:
|
||||
ch_core.hookenv.action_set({
|
||||
"output": e.output,
|
||||
"return-code": e.returncode,
|
||||
"traceback": traceback.format_exc()})
|
||||
ch_core.hookenv.action_fail("Stop MySQLRouter failed.")
|
||||
|
||||
|
||||
def start_mysqlrouter(args):
|
||||
"""Display cluster status
|
||||
|
||||
Return cluster.status() as a JSON encoded dictionary
|
||||
|
||||
:param args: sys.argv
|
||||
:type args: sys.argv
|
||||
:side effect: Calls instance.get_cluster_status
|
||||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
:action return: Dictionary with command output
|
||||
"""
|
||||
with charm.provide_charm_instance() as instance:
|
||||
try:
|
||||
instance.start_mysqlrouter()
|
||||
instance.assess_status()
|
||||
ch_core.hookenv.action_set({"outcome": "Success"})
|
||||
except subprocess.CalledProcessError as e:
|
||||
ch_core.hookenv.action_set({
|
||||
"output": e.output,
|
||||
"return-code": e.returncode,
|
||||
"traceback": traceback.format_exc()})
|
||||
ch_core.hookenv.action_fail("Start MySQLRouter failed.")
|
||||
|
||||
|
||||
def restart_mysqlrouter(args):
|
||||
"""Display cluster status
|
||||
|
||||
Return cluster.status() as a JSON encoded dictionary
|
||||
|
||||
:param args: sys.argv
|
||||
:type args: sys.argv
|
||||
:side effect: Calls instance.get_cluster_status
|
||||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
:action return: Dictionary with command output
|
||||
"""
|
||||
with charm.provide_charm_instance() as instance:
|
||||
try:
|
||||
instance.restart_mysqlrouter()
|
||||
instance.assess_status()
|
||||
ch_core.hookenv.action_set({"outcome": "Success"})
|
||||
except subprocess.CalledProcessError as e:
|
||||
ch_core.hookenv.action_set({
|
||||
"output": e.output,
|
||||
"return-code": e.returncode,
|
||||
"traceback": traceback.format_exc()})
|
||||
ch_core.hookenv.action_fail("Retart MySQLRouter failed.")
|
||||
|
||||
|
||||
# A dictionary of all the defined actions to callables (which take
|
||||
# parsed arguments).
|
||||
ACTIONS = {"stop-mysqlrouter": stop_mysqlrouter,
|
||||
"start-mysqlrouter": start_mysqlrouter,
|
||||
"restart-mysqlrouter": restart_mysqlrouter}
|
||||
|
||||
|
||||
def main(args):
|
||||
action_name = os.path.basename(args[0])
|
||||
try:
|
||||
action = ACTIONS[action_name]
|
||||
except KeyError:
|
||||
return "Action {} undefined".format(action_name)
|
||||
else:
|
||||
try:
|
||||
action(args)
|
||||
except Exception as e:
|
||||
ch_core.hookenv.action_set({
|
||||
"output": e.output.decode("UTF-8"),
|
||||
"return-code": e.returncode,
|
||||
"traceback": traceback.format_exc()})
|
||||
ch_core.hookenv.action_fail(
|
||||
"{} action failed.".format(action_name))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
|
@ -0,0 +1 @@
|
|||
actions.py
|
|
@ -0,0 +1 @@
|
|||
actions.py
|
|
@ -0,0 +1 @@
|
|||
actions.py
|
|
@ -0,0 +1,15 @@
|
|||
# MySQL Router systemd service file
|
||||
|
||||
[Unit]
|
||||
Description=MySQL Router
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=exec
|
||||
ExecStart=/var/lib/mysql/mysqlrouter/start.sh
|
||||
ExecStop=/var/lib/mysql/mysqlrouter/stop.sh
|
||||
RemainAfterExit=yes
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2019 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import charms_openstack.charm
|
||||
|
@ -27,7 +28,7 @@ import charmhelpers.contrib.network.ip as ch_net_ip
|
|||
import charmhelpers.contrib.database.mysql as mysql
|
||||
|
||||
|
||||
MYSQLD_CNF = "/etc/mysql/mysql.conf.d/mysqld.cnf"
|
||||
MYSQLROUTER_CNF = "/var/lib/mysql/mysqlrouter/mysqlrouter.conf"
|
||||
|
||||
# Flag Strings
|
||||
MYSQL_ROUTER_BOOTSTRAPPED = "charm.mysqlrouter.bootstrapped"
|
||||
|
@ -58,12 +59,10 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
required_relations = ["db-router", "shared-db"]
|
||||
source_config_key = "source"
|
||||
|
||||
# FIXME Can we have a non-systemd services?
|
||||
# Create a systemd shim?
|
||||
# services = ["mysqlrouter"]
|
||||
services = []
|
||||
# TODO Post bootstrap config management and restarts
|
||||
restart_map = {}
|
||||
services = ["mysqlrouter"]
|
||||
restart_map = {
|
||||
MYSQLROUTER_CNF: services,
|
||||
}
|
||||
# TODO Pick group owner
|
||||
group = "mysql"
|
||||
|
||||
|
@ -236,6 +235,15 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
group=self.mysqlrouter_group,
|
||||
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"]
|
||||
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
|
||||
def get_db_helper(self):
|
||||
"""Get an instance of the MySQLDB8Helper class.
|
||||
|
||||
|
@ -377,19 +385,7 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
"""
|
||||
cmd = ["{}/start.sh".format(self.mysqlrouter_working_dir)]
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, bufsize=1,
|
||||
universal_newlines=True)
|
||||
proc.wait()
|
||||
ch_core.hookenv.log("MySQL router started", "DEBUG")
|
||||
except subprocess.CalledProcessError as e:
|
||||
ch_core.hookenv.log(
|
||||
"Failed to start mysqlrouter: {}"
|
||||
.format(e.output.decode("UTF-8")), "ERROR")
|
||||
return
|
||||
ch_core.host.service_start("mysqlrouter")
|
||||
reactive.flags.set_flag(MYSQL_ROUTER_STARTED)
|
||||
|
||||
def stop_mysqlrouter(self):
|
||||
|
@ -403,20 +399,7 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
"""
|
||||
cmd = ["{}/stop.sh".format(self.mysqlrouter_working_dir)]
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, bufsize=1,
|
||||
universal_newlines=True)
|
||||
proc.wait()
|
||||
ch_core.hookenv.log("MySQL router stopped", "DEBUG")
|
||||
except subprocess.CalledProcessError as e:
|
||||
ch_core.hookenv.log(
|
||||
"Failed to start mysqlrouter: {}"
|
||||
.format(e.output.decode("UTF-8")), "ERROR")
|
||||
return
|
||||
reactive.flags.clear_flag(MYSQL_ROUTER_STARTED)
|
||||
ch_core.host.service_stop("mysqlrouter")
|
||||
|
||||
def restart_mysqlrouter(self):
|
||||
"""Restart MySQL Router.
|
||||
|
@ -430,8 +413,7 @@ class MySQLRouterCharm(charms_openstack.charm.OpenStackCharm):
|
|||
:returns: This function is called for its side effect
|
||||
:rtype: None
|
||||
"""
|
||||
self.stop_mysqlrouter()
|
||||
self.start_mysqlrouter()
|
||||
ch_core.host.service_restart("mysqlrouter")
|
||||
|
||||
def proxy_db_and_user_requests(
|
||||
self, receiving_interface, sending_interface):
|
|
@ -3,7 +3,7 @@ import charms.reactive as reactive
|
|||
import charms_openstack.bus
|
||||
import charms_openstack.charm as charm
|
||||
|
||||
import charm.mysql_router as mysql_router # noqa
|
||||
import charm.openstack.mysql_router as mysql_router # noqa
|
||||
|
||||
charms_openstack.bus.discover()
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import mock
|
|||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
import charm.mysql_router as mysql_router
|
||||
import charm.openstack.mysql_router as mysql_router
|
||||
|
||||
|
||||
class TestMySQLRouterProperties(test_utils.PatchHelper):
|
||||
|
@ -244,6 +244,7 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
|
|||
self.patch_object(
|
||||
mysql_router.charms_openstack.charm.OpenStackCharm,
|
||||
"install", "super_install")
|
||||
self.patch_object(mysql_router.shutil, "copy")
|
||||
self.os.path.exists.return_value = False
|
||||
self.group_exists.return_value = False
|
||||
self.user_exists.return_value = False
|
||||
|
@ -259,6 +260,11 @@ 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.subprocess.check_output.assert_called_once_with(
|
||||
['/usr/bin/systemctl', 'enable', 'mysqlrouter'],
|
||||
stderr=self.subprocess.STDOUT)
|
||||
|
||||
def test_get_db_helper(self):
|
||||
self.patch_object(
|
||||
mysql_router.mysql, "MySQL8Helper")
|
||||
|
@ -387,66 +393,27 @@ class TestMySQLRouterCharm(test_utils.PatchHelper):
|
|||
self.set_flag.assert_not_called()
|
||||
|
||||
def test_start_mysqlrouter(self):
|
||||
_user = "mysql"
|
||||
_port = "3006"
|
||||
self.patch_object(mysql_router.ch_core.host, "service_start")
|
||||
mrc = mysql_router.MySQLRouterCharm()
|
||||
mrc.options.system_user = _user
|
||||
mrc.options.base_port = _port
|
||||
|
||||
# Successful
|
||||
mrc.start_mysqlrouter()
|
||||
self.subprocess.Popen.assert_called_once_with(
|
||||
["/var/lib/mysql/mysqlrouter/start.sh"],
|
||||
bufsize=1,
|
||||
stdout=self.stdout,
|
||||
stderr=self.stdout,
|
||||
universal_newlines=True)
|
||||
self.service_start.assert_called_once_with("mysqlrouter")
|
||||
self.set_flag.assert_called_once_with(
|
||||
mysql_router.MYSQL_ROUTER_STARTED)
|
||||
|
||||
# Fail
|
||||
self.subprocess.reset_mock()
|
||||
self.set_flag.reset_mock()
|
||||
self.subprocess.CalledProcessError = FakeException
|
||||
self.subprocess.Popen.side_effect = self.subprocess.CalledProcessError
|
||||
mrc.start_mysqlrouter()
|
||||
self.set_flag.assert_not_called()
|
||||
|
||||
def test_stop_mysqlrouter(self):
|
||||
_user = "ubuntu"
|
||||
_port = "3306"
|
||||
self.patch_object(mysql_router.ch_core.host, "service_stop")
|
||||
mrc = mysql_router.MySQLRouterCharm()
|
||||
mrc.options.system_user = _user
|
||||
mrc.options.base_port = _port
|
||||
|
||||
# Successful
|
||||
mrc.stop_mysqlrouter()
|
||||
self.subprocess.Popen.assert_called_once_with(
|
||||
["/var/lib/mysql/mysqlrouter/stop.sh"],
|
||||
bufsize=1,
|
||||
stdout=self.stdout,
|
||||
stderr=self.stdout,
|
||||
universal_newlines=True)
|
||||
|
||||
self.clear_flag.assert_called_once_with(
|
||||
mysql_router.MYSQL_ROUTER_STARTED)
|
||||
|
||||
# Fail
|
||||
self.subprocess.reset_mock()
|
||||
self.clear_flag.reset_mock()
|
||||
self.subprocess.CalledProcessError = FakeException
|
||||
self.subprocess.Popen.side_effect = self.subprocess.CalledProcessError
|
||||
mrc.stop_mysqlrouter()
|
||||
self.clear_flag.assert_not_called()
|
||||
self.service_stop.assert_called_once_with("mysqlrouter")
|
||||
|
||||
def test_restart_mysqlrouter(self):
|
||||
mrc = mysql_router.MySQLRouterCharm()
|
||||
mrc.stop_mysqlrouter = mock.MagicMock()
|
||||
mrc.start_mysqlrouter = mock.MagicMock()
|
||||
self.patch_object(mysql_router.ch_core.host, "service_restart")
|
||||
|
||||
mrc.restart_mysqlrouter()
|
||||
mrc.stop_mysqlrouter.assert_called_once()
|
||||
mrc.start_mysqlrouter.assert_called_once()
|
||||
self.service_restart.assert_called_once_with("mysqlrouter")
|
||||
|
||||
def test_proxy_db_and_user_requests_no_prefix(self):
|
||||
mrc = mysql_router.MySQLRouterCharm()
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import mock
|
||||
|
||||
import charm.mysql_router as mysql_router
|
||||
import charm.openstack.mysql_router as mysql_router
|
||||
import reactive.mysql_router_handlers as handlers
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
|
Loading…
Reference in New Issue