Merge "Add systemd container control"
This commit is contained in:
commit
38ccebb8cb
@ -15,6 +15,13 @@
|
|||||||
when:
|
when:
|
||||||
- not ansible_facts
|
- 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
|
- name: Group hosts to determine when using --limit
|
||||||
group_by:
|
group_by:
|
||||||
key: "all_using_limit_{{ (ansible_play_batch | length) != (groups['all'] | length) }}"
|
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.
|
# We gathered facts for all hosts in the batch during the first play.
|
||||||
when:
|
when:
|
||||||
- not hostvars[item].ansible_facts
|
- 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
|
tags: always
|
||||||
|
@ -19,6 +19,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
|
from ansible.module_utils.kolla_systemd_worker import SystemdWorker
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
|
|
||||||
COMPARE_CONFIG_CMD = ['/usr/local/bin/kolla_set_configs', '--check']
|
COMPARE_CONFIG_CMD = ['/usr/local/bin/kolla_set_configs', '--check']
|
||||||
@ -50,6 +51,8 @@ class DockerWorker(object):
|
|||||||
self._cgroupns_mode_supported = (
|
self._cgroupns_mode_supported = (
|
||||||
StrictVersion(self.dc._version) >= StrictVersion('1.41'))
|
StrictVersion(self.dc._version) >= StrictVersion('1.41'))
|
||||||
|
|
||||||
|
self.systemd = SystemdWorker(self.params)
|
||||||
|
|
||||||
def generate_tls(self):
|
def generate_tls(self):
|
||||||
tls = {'verify': self.params.get('tls_verify')}
|
tls = {'verify': self.params.get('tls_verify')}
|
||||||
tls_cert = self.params.get('tls_cert'),
|
tls_cert = self.params.get('tls_cert'),
|
||||||
@ -110,7 +113,8 @@ class DockerWorker(object):
|
|||||||
container = self.check_container()
|
container = self.check_container()
|
||||||
if (not container or
|
if (not container or
|
||||||
self.check_container_differs() or
|
self.check_container_differs() or
|
||||||
self.compare_config()):
|
self.compare_config() or
|
||||||
|
self.systemd.check_unit_change()):
|
||||||
self.changed = True
|
self.changed = True
|
||||||
return self.changed
|
return self.changed
|
||||||
|
|
||||||
@ -469,6 +473,7 @@ class DockerWorker(object):
|
|||||||
self.changed = old_image_id != new_image_id
|
self.changed = old_image_id != new_image_id
|
||||||
|
|
||||||
def remove_container(self):
|
def remove_container(self):
|
||||||
|
self.changed |= self.systemd.remove_unit_file()
|
||||||
if self.check_container():
|
if self.check_container():
|
||||||
self.changed = True
|
self.changed = True
|
||||||
# NOTE(jeffrey4l): in some case, docker failed to remove container
|
# NOTE(jeffrey4l): in some case, docker failed to remove container
|
||||||
@ -575,18 +580,6 @@ class DockerWorker(object):
|
|||||||
dimensions = self.parse_dimensions(dimensions)
|
dimensions = self.parse_dimensions(dimensions)
|
||||||
options.update(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:
|
if binds:
|
||||||
options['binds'] = binds
|
options['binds'] = binds
|
||||||
|
|
||||||
@ -599,6 +592,11 @@ class DockerWorker(object):
|
|||||||
if cgroupns_mode is not None:
|
if cgroupns_mode is not None:
|
||||||
host_config['CgroupnsMode'] = cgroupns_mode
|
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
|
return host_config
|
||||||
|
|
||||||
def _inject_env_var(self, environment_info):
|
def _inject_env_var(self, environment_info):
|
||||||
@ -637,6 +635,8 @@ class DockerWorker(object):
|
|||||||
self.changed = True
|
self.changed = True
|
||||||
options = self.build_container_options()
|
options = self.build_container_options()
|
||||||
self.dc.create_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):
|
def recreate_or_restart_container(self):
|
||||||
self.changed = True
|
self.changed = True
|
||||||
@ -678,7 +678,15 @@ class DockerWorker(object):
|
|||||||
|
|
||||||
if not container['Status'].startswith('Up '):
|
if not container['Status'].startswith('Up '):
|
||||||
self.changed = True
|
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
|
# We do not want to detach so we wait around for container to exit
|
||||||
if not self.params.get('detach'):
|
if not self.params.get('detach'):
|
||||||
@ -806,7 +814,11 @@ class DockerWorker(object):
|
|||||||
msg="No such container: {} to stop".format(name))
|
msg="No such container: {} to stop".format(name))
|
||||||
elif not container['Status'].startswith('Exited '):
|
elif not container['Status'].startswith('Exited '):
|
||||||
self.changed = True
|
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):
|
def stop_and_remove_container(self):
|
||||||
container = self.check_container()
|
container = self.check_container()
|
||||||
@ -825,8 +837,13 @@ class DockerWorker(object):
|
|||||||
msg="No such container: {}".format(name))
|
msg="No such container: {}".format(name))
|
||||||
else:
|
else:
|
||||||
self.changed = True
|
self.changed = True
|
||||||
self.dc.stop(name, timeout=graceful_timeout)
|
self.systemd.create_unit_file()
|
||||||
self.dc.start(name)
|
|
||||||
|
if not self.systemd.restart():
|
||||||
|
self.module.fail_json(
|
||||||
|
changed=True,
|
||||||
|
msg="Container timed out",
|
||||||
|
**self.check_container())
|
||||||
|
|
||||||
def create_volume(self):
|
def create_volume(self):
|
||||||
if not self.check_volume():
|
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'
|
- kolla_container_engine == 'docker'
|
||||||
failed_when: result is failed or result.stdout is version(docker_py_version_min, '<')
|
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
|
# 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
|
# to check. as ansible_version does not provide major.minor in dict, we need to
|
||||||
# set it as variable.
|
# 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
|
- name: Checking Docker version
|
||||||
become: true
|
become: true
|
||||||
command: "{{ kolla_container_engine }} --version"
|
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 docker.types import Ulimit
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
|
||||||
|
sys.modules['dbus'] = mock.MagicMock()
|
||||||
|
|
||||||
this_dir = os.path.dirname(sys.modules[__name__].__file__)
|
this_dir = os.path.dirname(sys.modules[__name__].__file__)
|
||||||
ansible_dir = os.path.join(this_dir, '..', 'ansible')
|
ansible_dir = os.path.join(this_dir, '..', 'ansible')
|
||||||
kolla_docker_file = os.path.join(ansible_dir,
|
kolla_docker_file = os.path.join(ansible_dir,
|
||||||
'library', 'kolla_docker.py')
|
'library', 'kolla_docker.py')
|
||||||
docker_worker_file = os.path.join(ansible_dir,
|
docker_worker_file = os.path.join(ansible_dir,
|
||||||
'module_utils', 'kolla_docker_worker.py')
|
'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)
|
kd = imp.load_source('kolla_docker', kolla_docker_file)
|
||||||
dwm = imp.load_source('kolla_docker_worker', docker_worker_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):
|
class ModuleArgsTest(base.BaseTestCase):
|
||||||
@ -157,6 +162,7 @@ FAKE_DATA = {
|
|||||||
'remove_on_exit': True,
|
'remove_on_exit': True,
|
||||||
'volumes': None,
|
'volumes': None,
|
||||||
'tty': False,
|
'tty': False,
|
||||||
|
'client_timeout': 120,
|
||||||
},
|
},
|
||||||
|
|
||||||
'images': [
|
'images': [
|
||||||
@ -213,7 +219,8 @@ def get_DockerWorker(mod_param, docker_api_version='1.40'):
|
|||||||
module.params = mod_param
|
module.params = mod_param
|
||||||
with mock.patch("docker.APIClient") as MockedDockerClientClass:
|
with mock.patch("docker.APIClient") as MockedDockerClientClass:
|
||||||
MockedDockerClientClass.return_value._version = docker_api_version
|
MockedDockerClientClass.return_value._version = docker_api_version
|
||||||
dw = kd.DockerWorker(module)
|
dw = dwm.DockerWorker(module)
|
||||||
|
dw.systemd = mock.MagicMock()
|
||||||
return dw
|
return dw
|
||||||
|
|
||||||
|
|
||||||
@ -400,11 +407,10 @@ class TestContainer(base.BaseTestCase):
|
|||||||
self.dw.dc.containers = mock.MagicMock(
|
self.dw.dc.containers = mock.MagicMock(
|
||||||
return_value=self.fake_data['containers'])
|
return_value=self.fake_data['containers'])
|
||||||
self.dw.check_container_differs = mock.MagicMock(return_value=False)
|
self.dw.check_container_differs = mock.MagicMock(return_value=False)
|
||||||
self.dw.dc.start = mock.MagicMock()
|
|
||||||
self.dw.start_container()
|
self.dw.start_container()
|
||||||
self.assertTrue(self.dw.changed)
|
self.assertTrue(self.dw.changed)
|
||||||
self.dw.dc.start.assert_called_once_with(
|
self.dw.dc.start.assert_not_called()
|
||||||
container=self.fake_data['params'].get('name'))
|
self.dw.systemd.start.assert_called_once()
|
||||||
|
|
||||||
def test_start_container_no_detach(self):
|
def test_start_container_no_detach(self):
|
||||||
self.fake_data['params'].update({'name': 'my_container',
|
self.fake_data['params'].update({'name': 'my_container',
|
||||||
@ -425,12 +431,58 @@ class TestContainer(base.BaseTestCase):
|
|||||||
self.dw.dc.logs.assert_has_calls([
|
self.dw.dc.logs.assert_has_calls([
|
||||||
mock.call(name, stdout=True, stderr=False),
|
mock.call(name, stdout=True, stderr=False),
|
||||||
mock.call(name, stdout=False, stderr=True)])
|
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(
|
self.dw.dc.remove_container.assert_called_once_with(
|
||||||
container=name, force=True)
|
container=name, force=True)
|
||||||
expected = {'rc': 0, 'stdout': 'fake stdout', 'stderr': 'fake stderr'}
|
expected = {'rc': 0, 'stdout': 'fake stdout', 'stderr': 'fake stderr'}
|
||||||
self.assertEqual(expected, self.dw.result)
|
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):
|
def test_stop_container(self):
|
||||||
self.dw = get_DockerWorker({'name': 'my_container',
|
self.dw = get_DockerWorker({'name': 'my_container',
|
||||||
'action': 'stop_container'})
|
'action': 'stop_container'})
|
||||||
@ -439,7 +491,22 @@ class TestContainer(base.BaseTestCase):
|
|||||||
|
|
||||||
self.assertTrue(self.dw.changed)
|
self.assertTrue(self.dw.changed)
|
||||||
self.dw.dc.containers.assert_called_once_with(all=True)
|
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()
|
self.dw.module.fail_json.assert_not_called()
|
||||||
|
|
||||||
def test_stop_container_already_stopped(self):
|
def test_stop_container_already_stopped(self):
|
||||||
@ -485,7 +552,7 @@ class TestContainer(base.BaseTestCase):
|
|||||||
|
|
||||||
self.assertTrue(self.dw.changed)
|
self.assertTrue(self.dw.changed)
|
||||||
self.dw.dc.containers.assert_called_with(all=True)
|
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(
|
self.dw.dc.remove_container.assert_called_once_with(
|
||||||
container='my_container', force=True)
|
container='my_container', force=True)
|
||||||
|
|
||||||
@ -497,7 +564,7 @@ class TestContainer(base.BaseTestCase):
|
|||||||
|
|
||||||
self.assertFalse(self.dw.changed)
|
self.assertFalse(self.dw.changed)
|
||||||
self.dw.dc.containers.assert_called_with(all=True)
|
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)
|
self.assertFalse(self.dw.dc.remove_container.called)
|
||||||
|
|
||||||
def test_restart_container(self):
|
def test_restart_container(self):
|
||||||
@ -512,9 +579,7 @@ class TestContainer(base.BaseTestCase):
|
|||||||
|
|
||||||
self.assertTrue(self.dw.changed)
|
self.assertTrue(self.dw.changed)
|
||||||
self.dw.dc.containers.assert_called_once_with(all=True)
|
self.dw.dc.containers.assert_called_once_with(all=True)
|
||||||
self.dw.dc.inspect_container.assert_called_once_with('my_container')
|
self.dw.systemd.restart.assert_called_once_with()
|
||||||
self.dw.dc.stop.assert_called_once_with('my_container', timeout=10)
|
|
||||||
self.dw.dc.start.assert_called_once_with('my_container')
|
|
||||||
|
|
||||||
def test_restart_container_not_exists(self):
|
def test_restart_container_not_exists(self):
|
||||||
self.dw = get_DockerWorker({'name': 'fake-container',
|
self.dw = get_DockerWorker({'name': 'fake-container',
|
||||||
@ -527,6 +592,24 @@ class TestContainer(base.BaseTestCase):
|
|||||||
self.dw.module.fail_json.assert_called_once_with(
|
self.dw.module.fail_json.assert_called_once_with(
|
||||||
msg="No such container: fake-container")
|
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):
|
def test_remove_container(self):
|
||||||
self.dw = get_DockerWorker({'name': 'my_container',
|
self.dw = get_DockerWorker({'name': 'my_container',
|
||||||
'action': 'remove_container'})
|
'action': 'remove_container'})
|
||||||
@ -1576,3 +1659,213 @@ class TestAttrComp(base.BaseTestCase):
|
|||||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||||
self.assertIsNone(self.dw.parse_healthcheck(
|
self.assertIsNone(self.dw.parse_healthcheck(
|
||||||
self.fake_data['params']['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
|
fi
|
||||||
|
|
||||||
echo "Stopping containers..."
|
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..."
|
echo "Removing containers..."
|
||||||
(sudo docker rm -v -f ${containers_to_kill} 2>&1) > /dev/null
|
(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..."
|
echo "Removing link of kolla_log volume..."
|
||||||
(sudo rm -f /var/log/kolla 2>&1) > /dev/null
|
(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!"
|
echo "All cleaned up!"
|
||||||
|
2
tox.ini
2
tox.ini
@ -30,7 +30,7 @@ setenv = VIRTUAL_ENV={envdir}
|
|||||||
NOSE_COVER_BRANCHES=1
|
NOSE_COVER_BRANCHES=1
|
||||||
NOSE_COVER_HTML=1
|
NOSE_COVER_HTML=1
|
||||||
NOSE_COVER_HTML_DIR={toxinidir}/cover
|
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 =
|
commands =
|
||||||
bash {toxinidir}/tests/link-module-utils.sh {toxinidir} {envsitepackagesdir}
|
bash {toxinidir}/tests/link-module-utils.sh {toxinidir} {envsitepackagesdir}
|
||||||
stestr run {posargs}
|
stestr run {posargs}
|
||||||
|
Loading…
Reference in New Issue
Block a user