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>
This commit is contained in:
parent
08267a59ce
commit
4866017e52
@ -15,6 +15,13 @@
|
||||
when:
|
||||
- not ansible_facts
|
||||
|
||||
- name: Gather package facts
|
||||
package_facts:
|
||||
when:
|
||||
- "'packages' not in ansible_facts"
|
||||
- kolla_action is defined
|
||||
- kolla_action == "precheck"
|
||||
|
||||
- name: Group hosts to determine when using --limit
|
||||
group_by:
|
||||
key: "all_using_limit_{{ (ansible_play_batch | length) != (groups['all'] | length) }}"
|
||||
@ -49,4 +56,14 @@
|
||||
# We gathered facts for all hosts in the batch during the first play.
|
||||
when:
|
||||
- not hostvars[item].ansible_facts
|
||||
|
||||
- name: Gather package facts
|
||||
package_facts:
|
||||
delegate_facts: True
|
||||
delegate_to: "{{ item }}"
|
||||
with_items: "{{ delegate_hosts }}"
|
||||
when:
|
||||
- "'packages' not in hostvars[item].ansible_facts"
|
||||
- kolla_action is defined
|
||||
- "kolla_action == 'precheck'"
|
||||
tags: always
|
||||
|
@ -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():
|
||||
|
204
ansible/module_utils/kolla_systemd_worker.py
Normal file
204
ansible/module_utils/kolla_systemd_worker.py
Normal file
@ -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
|
@ -9,6 +9,13 @@
|
||||
- kolla_container_engine == 'docker'
|
||||
failed_when: result is failed or result.stdout is version(docker_py_version_min, '<')
|
||||
|
||||
- name: Checking dbus-python package
|
||||
command: "{{ ansible_facts.python.executable }} -c \"import dbus\""
|
||||
register: dbus_present
|
||||
changed_when: false
|
||||
when: inventory_hostname in groups['baremetal']
|
||||
failed_when: dbus_present is failed
|
||||
|
||||
# NOTE(osmanlicilegi): ansible_version.full includes patch number that's useless
|
||||
# to check. as ansible_version does not provide major.minor in dict, we need to
|
||||
# set it as variable.
|
||||
|
@ -1,4 +1,11 @@
|
||||
---
|
||||
- name: Checking if system uses systemd
|
||||
become: true
|
||||
assert:
|
||||
that:
|
||||
- "ansible_facts.service_mgr == 'systemd'"
|
||||
when: inventory_hostname in groups['baremetal']
|
||||
|
||||
- name: Checking Docker version
|
||||
become: true
|
||||
command: "{{ kolla_container_engine }} --version"
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for container state control through systemd in kolla_docker.
|
||||
Every container logs only to journald and has it's own unit file in
|
||||
``/etc/systemd/system`` named **kolla-<container name>-container.service**.
|
||||
Systemd control is implemented in new file
|
||||
``ansible/module_utils/kolla_systemd_worker.py``.
|
@ -25,14 +25,19 @@ from docker import errors as docker_error
|
||||
from docker.types import Ulimit
|
||||
from oslotest import base
|
||||
|
||||
sys.modules['dbus'] = mock.MagicMock()
|
||||
|
||||
this_dir = os.path.dirname(sys.modules[__name__].__file__)
|
||||
ansible_dir = os.path.join(this_dir, '..', 'ansible')
|
||||
kolla_docker_file = os.path.join(ansible_dir,
|
||||
'library', 'kolla_docker.py')
|
||||
docker_worker_file = os.path.join(ansible_dir,
|
||||
'module_utils', 'kolla_docker_worker.py')
|
||||
systemd_worker_file = os.path.join(ansible_dir,
|
||||
'module_utils', 'kolla_systemd_worker.py')
|
||||
kd = imp.load_source('kolla_docker', kolla_docker_file)
|
||||
dwm = imp.load_source('kolla_docker_worker', docker_worker_file)
|
||||
swm = imp.load_source('kolla_systemd_worker', systemd_worker_file)
|
||||
|
||||
|
||||
class ModuleArgsTest(base.BaseTestCase):
|
||||
@ -157,6 +162,7 @@ FAKE_DATA = {
|
||||
'remove_on_exit': True,
|
||||
'volumes': None,
|
||||
'tty': False,
|
||||
'client_timeout': 120,
|
||||
},
|
||||
|
||||
'images': [
|
||||
@ -213,7 +219,8 @@ def get_DockerWorker(mod_param, docker_api_version='1.40'):
|
||||
module.params = mod_param
|
||||
with mock.patch("docker.APIClient") as MockedDockerClientClass:
|
||||
MockedDockerClientClass.return_value._version = docker_api_version
|
||||
dw = kd.DockerWorker(module)
|
||||
dw = dwm.DockerWorker(module)
|
||||
dw.systemd = mock.MagicMock()
|
||||
return dw
|
||||
|
||||
|
||||
@ -400,11 +407,10 @@ class TestContainer(base.BaseTestCase):
|
||||
self.dw.dc.containers = mock.MagicMock(
|
||||
return_value=self.fake_data['containers'])
|
||||
self.dw.check_container_differs = mock.MagicMock(return_value=False)
|
||||
self.dw.dc.start = mock.MagicMock()
|
||||
self.dw.start_container()
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.dw.dc.start.assert_called_once_with(
|
||||
container=self.fake_data['params'].get('name'))
|
||||
self.dw.dc.start.assert_not_called()
|
||||
self.dw.systemd.start.assert_called_once()
|
||||
|
||||
def test_start_container_no_detach(self):
|
||||
self.fake_data['params'].update({'name': 'my_container',
|
||||
@ -425,12 +431,58 @@ class TestContainer(base.BaseTestCase):
|
||||
self.dw.dc.logs.assert_has_calls([
|
||||
mock.call(name, stdout=True, stderr=False),
|
||||
mock.call(name, stdout=False, stderr=True)])
|
||||
self.dw.dc.stop.assert_called_once_with(name, timeout=10)
|
||||
self.dw.systemd.stop.assert_called_once_with()
|
||||
self.dw.dc.remove_container.assert_called_once_with(
|
||||
container=name, force=True)
|
||||
expected = {'rc': 0, 'stdout': 'fake stdout', 'stderr': 'fake stderr'}
|
||||
self.assertEqual(expected, self.dw.result)
|
||||
|
||||
def test_start_container_no_systemd(self):
|
||||
self.fake_data['params'].update({'name': 'my_container',
|
||||
'restart_policy': 'no',
|
||||
'auth_username': 'fake_user',
|
||||
'auth_password': 'fake_psw',
|
||||
'auth_registry': 'myrepo/myapp',
|
||||
'auth_email': 'fake_mail@foogle.com'})
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.dw.dc.images = mock.MagicMock(
|
||||
return_value=self.fake_data['images'])
|
||||
self.fake_data['containers'][0].update(
|
||||
{'Status': 'Exited 2 days ago'})
|
||||
self.dw.dc.containers = mock.MagicMock(
|
||||
return_value=self.fake_data['containers'])
|
||||
self.dw.check_container_differs = mock.MagicMock(return_value=False)
|
||||
self.dw.dc.start = mock.MagicMock()
|
||||
self.dw.start_container()
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.dw.dc.start.assert_called_once_with(
|
||||
container=self.fake_data['params']['name']
|
||||
)
|
||||
self.dw.systemd.start.assert_not_called()
|
||||
|
||||
def test_start_container_systemd_start_fail(self):
|
||||
self.fake_data['params'].update({'name': 'my_container',
|
||||
'auth_username': 'fake_user',
|
||||
'auth_password': 'fake_psw',
|
||||
'auth_registry': 'myrepo/myapp',
|
||||
'auth_email': 'fake_mail@foogle.com'})
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.dw.dc.images = mock.MagicMock(
|
||||
return_value=self.fake_data['images'])
|
||||
self.fake_data['containers'][0].update(
|
||||
{'Status': 'Exited 2 days ago'})
|
||||
self.dw.dc.containers = mock.MagicMock(
|
||||
return_value=self.fake_data['containers'])
|
||||
self.dw.check_container_differs = mock.MagicMock(return_value=False)
|
||||
self.dw.systemd.start = mock.Mock(return_value=False)
|
||||
self.dw.start_container()
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.dw.dc.start.assert_not_called()
|
||||
self.dw.systemd.start.assert_called_once()
|
||||
self.dw.module.fail_json.assert_called_once_with(
|
||||
changed=True, msg='Container timed out',
|
||||
**self.fake_data['containers'][0])
|
||||
|
||||
def test_stop_container(self):
|
||||
self.dw = get_DockerWorker({'name': 'my_container',
|
||||
'action': 'stop_container'})
|
||||
@ -439,7 +491,22 @@ class TestContainer(base.BaseTestCase):
|
||||
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.dw.dc.containers.assert_called_once_with(all=True)
|
||||
self.dw.dc.stop.assert_called_once_with('my_container', timeout=10)
|
||||
self.dw.systemd.stop.assert_called_once()
|
||||
self.dw.dc.stop.assert_not_called()
|
||||
self.dw.module.fail_json.assert_not_called()
|
||||
|
||||
def test_stop_container_no_systemd(self):
|
||||
self.dw = get_DockerWorker({'name': 'my_container',
|
||||
'action': 'stop_container',
|
||||
'restart_policy': 'no'})
|
||||
self.dw.dc.containers.return_value = self.fake_data['containers']
|
||||
self.dw.stop_container()
|
||||
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.dw.dc.containers.assert_called_once_with(all=True)
|
||||
self.dw.systemd.stop.assert_not_called()
|
||||
self.dw.dc.stop.assert_called_once_with(
|
||||
'my_container', timeout=10)
|
||||
self.dw.module.fail_json.assert_not_called()
|
||||
|
||||
def test_stop_container_already_stopped(self):
|
||||
@ -485,7 +552,7 @@ class TestContainer(base.BaseTestCase):
|
||||
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.dw.dc.containers.assert_called_with(all=True)
|
||||
self.dw.dc.stop.assert_called_once_with('my_container', timeout=10)
|
||||
self.dw.systemd.stop.assert_called_once()
|
||||
self.dw.dc.remove_container.assert_called_once_with(
|
||||
container='my_container', force=True)
|
||||
|
||||
@ -497,7 +564,7 @@ class TestContainer(base.BaseTestCase):
|
||||
|
||||
self.assertFalse(self.dw.changed)
|
||||
self.dw.dc.containers.assert_called_with(all=True)
|
||||
self.assertFalse(self.dw.dc.stop.called)
|
||||
self.assertFalse(self.dw.systemd.stop.called)
|
||||
self.assertFalse(self.dw.dc.remove_container.called)
|
||||
|
||||
def test_restart_container(self):
|
||||
@ -512,9 +579,7 @@ class TestContainer(base.BaseTestCase):
|
||||
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.dw.dc.containers.assert_called_once_with(all=True)
|
||||
self.dw.dc.inspect_container.assert_called_once_with('my_container')
|
||||
self.dw.dc.stop.assert_called_once_with('my_container', timeout=10)
|
||||
self.dw.dc.start.assert_called_once_with('my_container')
|
||||
self.dw.systemd.restart.assert_called_once_with()
|
||||
|
||||
def test_restart_container_not_exists(self):
|
||||
self.dw = get_DockerWorker({'name': 'fake-container',
|
||||
@ -527,6 +592,24 @@ class TestContainer(base.BaseTestCase):
|
||||
self.dw.module.fail_json.assert_called_once_with(
|
||||
msg="No such container: fake-container")
|
||||
|
||||
def test_restart_container_systemd_timeout(self):
|
||||
self.dw = get_DockerWorker({'name': 'my_container',
|
||||
'action': 'restart_container'})
|
||||
self.dw.dc.containers.return_value = self.fake_data['containers']
|
||||
self.fake_data['container_inspect'].update(
|
||||
self.fake_data['containers'][0])
|
||||
self.dw.dc.inspect_container.return_value = (
|
||||
self.fake_data['container_inspect'])
|
||||
self.dw.systemd.restart = mock.Mock(return_value=False)
|
||||
self.dw.restart_container()
|
||||
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.dw.dc.containers.assert_called_with(all=True)
|
||||
self.dw.systemd.restart.assert_called_once_with()
|
||||
self.dw.module.fail_json.assert_called_once_with(
|
||||
changed=True, msg="Container timed out",
|
||||
**self.fake_data['containers'][0])
|
||||
|
||||
def test_remove_container(self):
|
||||
self.dw = get_DockerWorker({'name': 'my_container',
|
||||
'action': 'remove_container'})
|
||||
@ -1576,3 +1659,213 @@ class TestAttrComp(base.BaseTestCase):
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertIsNone(self.dw.parse_healthcheck(
|
||||
self.fake_data['params']['healthcheck']))
|
||||
|
||||
|
||||
class TestSystemd(base.BaseTestCase):
|
||||
def setUp(self) -> None:
|
||||
super(TestSystemd, self).setUp()
|
||||
self.params_dict = dict(
|
||||
name='test',
|
||||
restart_policy='no',
|
||||
client_timeout=120,
|
||||
restart_retries=10,
|
||||
graceful_timeout=15
|
||||
)
|
||||
swm.sleep = mock.Mock()
|
||||
self.sw = swm.SystemdWorker(self.params_dict)
|
||||
|
||||
def test_manager(self):
|
||||
self.assertIsNotNone(self.sw)
|
||||
self.assertIsNotNone(self.sw.manager)
|
||||
|
||||
def test_start(self):
|
||||
self.sw.perform_action = mock.Mock(return_value=True)
|
||||
self.sw.wait_for_unit = mock.Mock(return_value=True)
|
||||
|
||||
self.sw.start()
|
||||
self.sw.perform_action.assert_called_once_with(
|
||||
'StartUnit',
|
||||
'kolla-test-container.service',
|
||||
'replace'
|
||||
)
|
||||
|
||||
def test_restart(self):
|
||||
self.sw.perform_action = mock.Mock(return_value=True)
|
||||
self.sw.wait_for_unit = mock.Mock(return_value=True)
|
||||
|
||||
self.sw.restart()
|
||||
self.sw.perform_action.assert_called_once_with(
|
||||
'RestartUnit',
|
||||
'kolla-test-container.service',
|
||||
'replace'
|
||||
)
|
||||
|
||||
def test_stop(self):
|
||||
self.sw.perform_action = mock.Mock(return_value=True)
|
||||
|
||||
self.sw.stop()
|
||||
self.sw.perform_action.assert_called_once_with(
|
||||
'StopUnit',
|
||||
'kolla-test-container.service',
|
||||
'replace'
|
||||
)
|
||||
|
||||
def test_reload(self):
|
||||
self.sw.perform_action = mock.Mock(return_value=True)
|
||||
|
||||
self.sw.reload()
|
||||
self.sw.perform_action.assert_called_once_with(
|
||||
'Reload',
|
||||
'kolla-test-container.service',
|
||||
'replace'
|
||||
)
|
||||
|
||||
def test_enable(self):
|
||||
self.sw.perform_action = mock.Mock(return_value=True)
|
||||
|
||||
self.sw.enable()
|
||||
self.sw.perform_action.assert_called_once_with(
|
||||
'EnableUnitFiles',
|
||||
['kolla-test-container.service'],
|
||||
False,
|
||||
True
|
||||
)
|
||||
|
||||
def test_check_unit_change(self):
|
||||
self.sw.generate_unit_file = mock.Mock()
|
||||
self.sw.check_unit_file = mock.Mock(return_value=True)
|
||||
open_mock = mock.mock_open(read_data='test data')
|
||||
return_val = None
|
||||
|
||||
with mock.patch('builtins.open', open_mock, create=True):
|
||||
return_val = self.sw.check_unit_change('test data')
|
||||
|
||||
self.assertFalse(return_val)
|
||||
self.sw.generate_unit_file.assert_not_called()
|
||||
open_mock.assert_called_with(
|
||||
'/etc/systemd/system/kolla-test-container.service',
|
||||
'r'
|
||||
)
|
||||
open_mock.return_value.read.assert_called_once()
|
||||
|
||||
def test_check_unit_change_diff(self):
|
||||
self.sw.generate_unit_file = mock.Mock()
|
||||
self.sw.check_unit_file = mock.Mock(return_value=True)
|
||||
open_mock = mock.mock_open(read_data='new data')
|
||||
return_val = None
|
||||
|
||||
with mock.patch('builtins.open', open_mock, create=True):
|
||||
return_val = self.sw.check_unit_change('old data')
|
||||
|
||||
self.assertTrue(return_val)
|
||||
self.sw.generate_unit_file.assert_not_called()
|
||||
open_mock.assert_called_with(
|
||||
'/etc/systemd/system/kolla-test-container.service',
|
||||
'r'
|
||||
)
|
||||
open_mock.return_value.read.assert_called_once()
|
||||
|
||||
@mock.patch(
|
||||
'kolla_systemd_worker.TEMPLATE',
|
||||
"""${name}, ${restart_policy},
|
||||
${graceful_timeout}, ${restart_timeout},
|
||||
${restart_retries}"""
|
||||
)
|
||||
def test_generate_unit_file(self):
|
||||
self.sw = swm.SystemdWorker(self.params_dict)
|
||||
p = self.params_dict
|
||||
ref_string = f"""{p.get('name')}, {p.get('restart_policy')},
|
||||
{p.get('graceful_timeout')}, {p.get('client_timeout')},
|
||||
{p.get('restart_retries')}"""
|
||||
|
||||
ret_string = self.sw.generate_unit_file()
|
||||
|
||||
self.assertEqual(ref_string, ret_string)
|
||||
|
||||
def test_create_unit_file(self):
|
||||
self.sw.generate_unit_file = mock.Mock(return_value='test data')
|
||||
self.sw.check_unit_change = mock.Mock(return_value=True)
|
||||
self.sw.reload = mock.Mock()
|
||||
self.sw.enable = mock.Mock()
|
||||
open_mock = mock.mock_open()
|
||||
return_val = None
|
||||
|
||||
with mock.patch('builtins.open', open_mock, create=True):
|
||||
return_val = self.sw.create_unit_file()
|
||||
|
||||
self.assertTrue(return_val)
|
||||
open_mock.assert_called_with(
|
||||
'/etc/systemd/system/kolla-test-container.service',
|
||||
'w'
|
||||
)
|
||||
open_mock.return_value.write.assert_called_once_with('test data')
|
||||
self.sw.reload.assert_called_once()
|
||||
self.sw.enable.assert_called_once()
|
||||
|
||||
def test_create_unit_file_no_change(self):
|
||||
self.sw.generate_unit_file = mock.Mock()
|
||||
self.sw.check_unit_change = mock.Mock(return_value=False)
|
||||
self.sw.reload = mock.Mock()
|
||||
self.sw.enable = mock.Mock()
|
||||
open_mock = mock.mock_open()
|
||||
|
||||
return_val = self.sw.create_unit_file()
|
||||
|
||||
self.assertFalse(return_val)
|
||||
open_mock.assert_not_called()
|
||||
self.sw.reload.assert_not_called()
|
||||
self.sw.enable.assert_not_called()
|
||||
|
||||
def test_remove_unit_file(self):
|
||||
self.sw.check_unit_file = mock.Mock(return_value=True)
|
||||
os.remove = mock.Mock()
|
||||
self.sw.reload = mock.Mock()
|
||||
|
||||
return_val = self.sw.remove_unit_file()
|
||||
|
||||
self.assertTrue(return_val)
|
||||
os.remove.assert_called_once_with(
|
||||
'/etc/systemd/system/kolla-test-container.service'
|
||||
)
|
||||
self.sw.reload.assert_called_once()
|
||||
|
||||
def test_get_unit_state(self):
|
||||
unit_list = [
|
||||
('foo.service', '', 'loaded', 'active', 'exited'),
|
||||
('kolla-test-container.service', '', 'loaded', 'active', 'running')
|
||||
]
|
||||
self.sw.manager.ListUnits = mock.Mock(return_value=unit_list)
|
||||
|
||||
state = self.sw.get_unit_state()
|
||||
|
||||
self.sw.manager.ListUnits.assert_called_once()
|
||||
self.assertEqual('running', state)
|
||||
|
||||
def test_get_unit_state_not_exist(self):
|
||||
unit_list = [
|
||||
('foo.service', '', 'loaded', 'active', 'exited'),
|
||||
('bar.service', '', 'loaded', 'active', 'running')
|
||||
]
|
||||
self.sw.manager.ListUnits = mock.Mock(return_value=unit_list)
|
||||
|
||||
state = self.sw.get_unit_state()
|
||||
|
||||
self.sw.manager.ListUnits.assert_called_once()
|
||||
self.assertIsNone(state)
|
||||
|
||||
def test_wait_for_unit(self):
|
||||
self.sw.get_unit_state = mock.Mock()
|
||||
self.sw.get_unit_state.side_effect = ['starting', 'running']
|
||||
|
||||
result = self.sw.wait_for_unit(10)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_wait_for_unit_timeout(self):
|
||||
self.sw.get_unit_state = mock.Mock()
|
||||
self.sw.get_unit_state.side_effect = [
|
||||
'starting', 'starting', 'failed', 'failed']
|
||||
|
||||
result = self.sw.wait_for_unit(10)
|
||||
|
||||
self.assertFalse(result)
|
||||
|
@ -30,7 +30,9 @@ echo "Removing ovs bridge..."
|
||||
fi
|
||||
|
||||
echo "Stopping containers..."
|
||||
(sudo docker stop -t 2 ${containers_to_kill} 2>&1) > /dev/null
|
||||
for container in ${containers_to_kill}; do
|
||||
sudo systemctl stop kolla-${container}-container.service
|
||||
done
|
||||
|
||||
echo "Removing containers..."
|
||||
(sudo docker rm -v -f ${containers_to_kill} 2>&1) > /dev/null
|
||||
@ -46,4 +48,8 @@ echo "Removing volumes..."
|
||||
echo "Removing link of kolla_log volume..."
|
||||
(sudo rm -f /var/log/kolla 2>&1) > /dev/null
|
||||
|
||||
echo "Removing unit files..."
|
||||
sudo rm -f /etc/systemd/system/kolla-*-container.service
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
echo "All cleaned up!"
|
||||
|
2
tox.ini
2
tox.ini
@ -30,7 +30,7 @@ setenv = VIRTUAL_ENV={envdir}
|
||||
NOSE_COVER_BRANCHES=1
|
||||
NOSE_COVER_HTML=1
|
||||
NOSE_COVER_HTML_DIR={toxinidir}/cover
|
||||
PYTHON=coverage run --source kolla_ansible,ansible/action_plugins,ansible/library,ansible/roles/keystone/files/ --parallel-mode
|
||||
PYTHON=coverage run --source kolla_ansible,ansible/action_plugins,ansible/library,ansible/module_utils,ansible/roles/keystone/files/ --parallel-mode
|
||||
commands =
|
||||
bash {toxinidir}/tests/link-module-utils.sh {toxinidir} {envsitepackagesdir}
|
||||
stestr run {posargs}
|
||||
|
Loading…
Reference in New Issue
Block a user