diff options
| author | Martin Hiner <martin.hiner@tietoevry.com> | 2021-11-04 17:29:16 +0100 |
|---|---|---|
| committer | Ivan Halomi <i.halomi@partner.samsung.com> | 2023-01-19 10:38:41 +0100 |
| commit | 4866017e5256623fe1838817260ce5836cc26ef1 (patch) | |
| tree | 23f2631fc9a175f7be1a72031adaeb123df86950 /ansible/module_utils | |
| parent | 08267a59ce49c63cf7d9d61276154d2b088ec5e8 (diff) | |
| download | kolla-ansible-4866017e5256623fe1838817260ce5836cc26ef1.tar.gz kolla-ansible-4866017e5256623fe1838817260ce5836cc26ef1.zip | |
Add systemd container control
This commit adds SystemdWorker class to kolla_docker ansible module.
It is used to manage container state via systemd calls.
Change-Id: I20e65a6771ebeee462a3aaaabaa5f0596bdd0581
Signed-off-by: Ivan Halomi <i.halomi@partner.samsung.com>
Signed-off-by: Martin Hiner <m.hiner@partner.samsung.com>
Diffstat (limited to 'ansible/module_utils')
| -rw-r--r-- | ansible/module_utils/kolla_docker_worker.py | 51 | ||||
| -rw-r--r-- | ansible/module_utils/kolla_systemd_worker.py | 204 |
2 files changed, 238 insertions, 17 deletions
diff --git a/ansible/module_utils/kolla_docker_worker.py b/ansible/module_utils/kolla_docker_worker.py index ebff465c1..8c365ca45 100644 --- a/ansible/module_utils/kolla_docker_worker.py +++ b/ansible/module_utils/kolla_docker_worker.py @@ -19,6 +19,7 @@ import json import os import shlex +from ansible.module_utils.kolla_systemd_worker import SystemdWorker from distutils.version import StrictVersion COMPARE_CONFIG_CMD = ['/usr/local/bin/kolla_set_configs', '--check'] @@ -50,6 +51,8 @@ class DockerWorker(object): self._cgroupns_mode_supported = ( StrictVersion(self.dc._version) >= StrictVersion('1.41')) + self.systemd = SystemdWorker(self.params) + def generate_tls(self): tls = {'verify': self.params.get('tls_verify')} tls_cert = self.params.get('tls_cert'), @@ -110,7 +113,8 @@ class DockerWorker(object): container = self.check_container() if (not container or self.check_container_differs() or - self.compare_config()): + self.compare_config() or + self.systemd.check_unit_change()): self.changed = True return self.changed @@ -469,6 +473,7 @@ class DockerWorker(object): self.changed = old_image_id != new_image_id def remove_container(self): + self.changed |= self.systemd.remove_unit_file() if self.check_container(): self.changed = True # NOTE(jeffrey4l): in some case, docker failed to remove container @@ -575,18 +580,6 @@ class DockerWorker(object): dimensions = self.parse_dimensions(dimensions) options.update(dimensions) - restart_policy = self.params.get('restart_policy') - - if restart_policy is not None: - restart_policy = {'Name': restart_policy} - # NOTE(Jeffrey4l): MaximumRetryCount is only needed for on-failure - # policy - if restart_policy['Name'] == 'on-failure': - retries = self.params.get('restart_retries') - if retries is not None: - restart_policy['MaximumRetryCount'] = retries - options['restart_policy'] = restart_policy - if binds: options['binds'] = binds @@ -599,6 +592,11 @@ class DockerWorker(object): if cgroupns_mode is not None: host_config['CgroupnsMode'] = cgroupns_mode + # detached containers should only log to journald + if self.params.get('detach'): + options['log_config'] = docker.types.LogConfig( + type=docker.types.LogConfig.types.NONE) + return host_config def _inject_env_var(self, environment_info): @@ -637,6 +635,8 @@ class DockerWorker(object): self.changed = True options = self.build_container_options() self.dc.create_container(**options) + if self.params.get('restart_policy') != 'no': + self.changed |= self.systemd.create_unit_file() def recreate_or_restart_container(self): self.changed = True @@ -678,7 +678,15 @@ class DockerWorker(object): if not container['Status'].startswith('Up '): self.changed = True - self.dc.start(container=self.params.get('name')) + if self.params.get('restart_policy') == 'no': + self.dc.start(container=self.params.get('name')) + else: + self.systemd.create_unit_file() + if not self.systemd.start(): + self.module.fail_json( + changed=True, + msg="Container timed out", + **self.check_container()) # We do not want to detach so we wait around for container to exit if not self.params.get('detach'): @@ -806,7 +814,11 @@ class DockerWorker(object): msg="No such container: {} to stop".format(name)) elif not container['Status'].startswith('Exited '): self.changed = True - self.dc.stop(name, timeout=graceful_timeout) + if self.params.get('restart_policy') != 'no': + self.systemd.create_unit_file() + self.systemd.stop() + else: + self.dc.stop(name, timeout=graceful_timeout) def stop_and_remove_container(self): container = self.check_container() @@ -825,8 +837,13 @@ class DockerWorker(object): msg="No such container: {}".format(name)) else: self.changed = True - self.dc.stop(name, timeout=graceful_timeout) - self.dc.start(name) + self.systemd.create_unit_file() + + if not self.systemd.restart(): + self.module.fail_json( + changed=True, + msg="Container timed out", + **self.check_container()) def create_volume(self): if not self.check_volume(): diff --git a/ansible/module_utils/kolla_systemd_worker.py b/ansible/module_utils/kolla_systemd_worker.py new file mode 100644 index 000000000..2d8a59d1c --- /dev/null +++ b/ansible/module_utils/kolla_systemd_worker.py @@ -0,0 +1,204 @@ +# 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 +from string import Template +from time import sleep + +import dbus + + +TEMPLATE = '''# ${service_name} +# autogenerated by Kolla-Ansible + +[Unit] +Description=docker ${service_name} +After=docker.service +Requires=docker.service +StartLimitIntervalSec=${restart_timeout} +StartLimitBurst=${restart_retries} + +[Service] +ExecStart=/usr/bin/docker start -a ${name} +ExecStop=/usr/bin/docker stop ${name} -t ${graceful_timeout} +Restart=${restart_policy} +RestartSec=${restart_duration} + +[Install] +WantedBy=multi-user.target +''' + + +class SystemdWorker(object): + def __init__(self, params): + name = params.get('name', None) + + # systemd is not needed + if not name: + return None + + restart_policy = params.get('restart_policy', 'no') + if restart_policy == 'unless-stopped': + restart_policy = 'always' + + # NOTE(hinermar): duration * retries should be less than timeout + # otherwise service will indefinitely try to restart. + # Also, correct timeout and retries values should probably be + # checked at the module level inside kolla_docker.py + restart_timeout = params.get('client_timeout', 120) + restart_retries = params.get('restart_retries', 10) + restart_duration = (restart_timeout // restart_retries) - 1 + + # container info + self.container_dict = dict( + name=name, + service_name='kolla-' + name + '-container.service', + engine='docker', + deps='docker.service', + graceful_timeout=params.get('graceful_timeout'), + restart_policy=restart_policy, + restart_timeout=restart_timeout, + restart_retries=restart_retries, + restart_duration=restart_duration + ) + + # systemd + self.manager = self.get_manager() + self.job_mode = 'replace' + self.sysdir = '/etc/systemd/system/' + + # templating + self.template = Template(TEMPLATE) + + def get_manager(self): + sysbus = dbus.SystemBus() + systemd1 = sysbus.get_object( + 'org.freedesktop.systemd1', + '/org/freedesktop/systemd1' + ) + return dbus.Interface(systemd1, 'org.freedesktop.systemd1.Manager') + + def start(self): + if self.perform_action( + 'StartUnit', + self.container_dict['service_name'], + self.job_mode + ): + return self.wait_for_unit(self.container_dict['restart_timeout']) + return False + + def restart(self): + if self.perform_action( + 'RestartUnit', + self.container_dict['service_name'], + self.job_mode + ): + return self.wait_for_unit(self.container_dict['restart_timeout']) + return False + + def stop(self): + return self.perform_action( + 'StopUnit', + self.container_dict['service_name'], + self.job_mode + ) + + def reload(self): + return self.perform_action( + 'Reload', + self.container_dict['service_name'], + self.job_mode + ) + + def enable(self): + return self.perform_action( + 'EnableUnitFiles', + [self.container_dict['service_name']], + False, + True + ) + + def perform_action(self, function, *args): + try: + getattr(self.manager, function)(*args) + return True + except Exception: + return False + + def check_unit_file(self): + return os.path.isfile( + self.sysdir + self.container_dict['service_name'] + ) + + def check_unit_change(self, new_content=''): + if not new_content: + new_content = self.generate_unit_file() + + if self.check_unit_file(): + with open( + self.sysdir + self.container_dict['service_name'], 'r' + ) as f: + curr_content = f.read() + + # return whether there was change in the unit file + return curr_content != new_content + + return True + + def generate_unit_file(self): + return self.template.substitute(self.container_dict) + + def create_unit_file(self): + file_content = self.generate_unit_file() + + if self.check_unit_change(file_content): + with open( + self.sysdir + self.container_dict['service_name'], 'w' + ) as f: + f.write(file_content) + + self.reload() + self.enable() + return True + + return False + + def remove_unit_file(self): + if self.check_unit_file(): + os.remove(self.sysdir + self.container_dict['service_name']) + self.reload() + + return True + else: + return False + + def get_unit_state(self): + unit_list = self.manager.ListUnits() + + for service in unit_list: + if str(service[0]) == self.container_dict['service_name']: + return str(service[4]) + + return None + + def wait_for_unit(self, timeout): + delay = 5 + elapsed = 0 + + while True: + if self.get_unit_state() == 'running': + return True + elif elapsed > timeout: + return False + else: + sleep(delay) + elapsed += delay |
