From c23384a6d4c85ac98f482a7655da2971b5e1e3ed Mon Sep 17 00:00:00 2001 From: Jill Rouleau Date: Tue, 27 Nov 2018 10:35:10 -0700 Subject: [PATCH] Add systemd healthchecks to podman Add systemd managed healthchecks and timers to podman containers. Change-Id: I1949b7ba9e06110a2e91c9cb3359605cae3638e5 --- paunch/builder/base.py | 7 +++ paunch/builder/podman.py | 1 + paunch/tests/test_utils_systemd.py | 42 +++++++++++++++ paunch/utils/systemd.py | 83 +++++++++++++++++++++++++++++- 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/paunch/builder/base.py b/paunch/builder/base.py index 0eff278..0442511 100644 --- a/paunch/builder/base.py +++ b/paunch/builder/base.py @@ -104,6 +104,13 @@ class BaseBuilder(object): systemd.service_create(container=container_name, cconfig=cconfig, log=self.log) + if 'healthcheck' in cconfig: + systemd.healthcheck_create(container=container_name, + log=self.log) + systemd.healthcheck_timer_create( + container=container_name, + cconfig=cconfig, + log=self.log) return stdout, stderr, deploy_status_code def delete_missing_and_updated(self): diff --git a/paunch/builder/podman.py b/paunch/builder/podman.py index fb3bf5c..96e04f1 100644 --- a/paunch/builder/podman.py +++ b/paunch/builder/podman.py @@ -63,6 +63,7 @@ class PodmanBuilder(base.BaseBuilder): self.list_arg(cconfig, cmd, 'cap_add', '--cap-add') self.list_arg(cconfig, cmd, 'cap_drop', '--cap-drop') + self.string_arg(cconfig, cmd, 'check_interval', '--check-interval') cmd.append(cconfig.get('image', '')) cmd.extend(self.command_argument(cconfig.get('command'))) diff --git a/paunch/tests/test_utils_systemd.py b/paunch/tests/test_utils_systemd.py index 0b805ac..84a741d 100644 --- a/paunch/tests/test_utils_systemd.py +++ b/paunch/tests/test_utils_systemd.py @@ -60,3 +60,45 @@ class TestUtilsSystemd(base.TestCase): mock.call(['systemctl', 'disable', service]), mock.call(['systemctl', 'daemon-reload']), ]) + + @mock.patch('subprocess.call', autospec=True) + @mock.patch('os.chmod') + def test_healthcheck_create(self, mock_chmod, mock_subprocess_call): + container = 'my_app' + service = 'tripleo_' + container + tempdir = tempfile.mkdtemp() + healthcheck = service + '_healthcheck.service' + sysd_unit_f = tempdir + healthcheck + + systemd.healthcheck_create(container, tempdir) + unit = open(sysd_unit_f, 'rt').read() + + self.assertIn('ExecStart=/usr/bin/podman exec my_app ' + '/openstack/healthcheck', unit) + mock_chmod.assert_has_calls([mock.call(sysd_unit_f, 420)]) + mock_subprocess_call.assert_has_calls([ + mock.call(['systemctl', 'enable', '--now', + healthcheck]), + mock.call(['systemctl', 'daemon-reload']), + ]) + + @mock.patch('subprocess.call', autospec=True) + @mock.patch('os.chmod') + def test_healthcheck_timer_create(self, mock_chmod, mock_subprocess_call): + container = 'my_app' + service = 'tripleo_' + container + cconfig = {'check_interval': '15'} + tempdir = tempfile.mkdtemp() + healthcheck_timer = service + '_healthcheck.timer' + sysd_unit_f = tempdir + healthcheck_timer + + systemd.healthcheck_timer_create(container, cconfig, tempdir) + unit = open(sysd_unit_f, 'rt').read() + + self.assertIn('Requires=my_app_healthcheck.service', unit) + self.assertIn('OnCalendar=*-*-* *:*:00/15', unit) + mock_chmod.assert_has_calls([mock.call(sysd_unit_f, 420)]) + mock_subprocess_call.assert_has_calls([ + mock.call(['systemctl', 'enable', '--now', healthcheck_timer]), + mock.call(['systemctl', 'daemon-reload']), + ]) diff --git a/paunch/utils/systemd.py b/paunch/utils/systemd.py index bf1b4cc..38c896d 100644 --- a/paunch/utils/systemd.py +++ b/paunch/utils/systemd.py @@ -30,7 +30,7 @@ def service_create(container, cconfig, sysdir='/etc/systemd/system/', :type cconfig: Dictionary :param sysdir: systemd unit files directory - :type sysdir: string + :type sysdir: String :param log: optional pre-defined logger for messages :type log: logging.RootLogger @@ -105,3 +105,84 @@ def service_delete(container, log=None): subprocess.call(['systemctl', 'daemon-reload']) else: log.warning('No systemd unit file was found for %s' % service) + + +def healthcheck_create(container, sysdir='/etc/systemd/system/', log=None): + """Create a healthcheck for a service in systemd + + :param container: container name + :type container: String + + :param sysdir: systemd unit files directory + :type sysdir: String + + :param log: optional pre-defined logger for messages + :type log: logging.RootLogger + """ + + log = log or common.configure_logging(__name__) + + service = 'tripleo_' + container + healthcheck = service + '_healthcheck.service' + sysd_unit_f = sysdir + healthcheck + log.debug('Creating systemd unit file: %s' % sysd_unit_f) + s_config = { + 'name': container, + 'restart': 'restart', + } + with open(sysd_unit_f, 'w') as unit_file: + os.chmod(unit_file.name, 0o644) + unit_file.write("""[Unit] +Description=%(name)s healthcheck +After=paunch-container-shutdown.service +Requisite=%(name)s.service +[Service] +Type=oneshot +ExecStart=/usr/bin/podman exec %(name)s /openstack/healthcheck +[Install] +WantedBy=multi-user.target +""" % s_config) + subprocess.call(['systemctl', 'enable', '--now', healthcheck]) + subprocess.call(['systemctl', 'daemon-reload']) + + +def healthcheck_timer_create(container, cconfig, sysdir='/etc/systemd/system/', + log=None): + """Create a systemd timer for a healthcheck + + :param container: container name + :type container: String + + :param cconfig: container configuration + :type cconfig: Dictionary + + :param sysdir: systemd unit files directory + :type sysdir: string + + :param log: optional pre-defined logger for messages + :type log: logging.RootLogger + """ + + log = log or common.configure_logging(__name__) + + service = 'tripleo_' + container + healthcheck_timer = service + '_healthcheck.timer' + sysd_timer_f = sysdir + healthcheck_timer + log.debug('Creating systemd timer file: %s' % sysd_timer_f) + interval = cconfig.get('check_interval', 30) + s_config = { + 'name': container, + 'interval': interval + } + with open(sysd_timer_f, 'w') as timer_file: + os.chmod(timer_file.name, 0o644) + timer_file.write("""[Unit] +Description=%(name)s container healthcheck +Requires=%(name)s_healthcheck.service +[Timer] +OnUnitActiveSec=90 +OnCalendar=*-*-* *:*:00/%(interval)s +[Install] +WantedBy=timers.target""" % s_config) + subprocess.call(['systemctl', 'enable', '--now', healthcheck_timer]) + subprocess.call(['systemctl', 'daemon-reload'])