aboutsummaryrefslogtreecommitdiff
path: root/ansible/module_utils
diff options
context:
space:
mode:
authorMartin Hiner <martin.hiner@tietoevry.com>2021-11-04 17:29:16 +0100
committerIvan Halomi <i.halomi@partner.samsung.com>2023-01-19 10:38:41 +0100
commit4866017e5256623fe1838817260ce5836cc26ef1 (patch)
tree23f2631fc9a175f7be1a72031adaeb123df86950 /ansible/module_utils
parent08267a59ce49c63cf7d9d61276154d2b088ec5e8 (diff)
downloadkolla-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.py51
-rw-r--r--ansible/module_utils/kolla_systemd_worker.py204
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