Add healthchecks option to kolla_docker
blueprint container-health-check Implements healthchecks option in kolla_docker Ansible module Change-Id: I9323d4e75378d06f52b869f31009fd656bf270d2
This commit is contained in:
parent
6c1399d078
commit
d6f69174ac
@ -207,6 +207,12 @@ options:
|
||||
required: False
|
||||
default: 120
|
||||
type: int
|
||||
healthcheck:
|
||||
description:
|
||||
- Container healthcheck configuration
|
||||
required: False
|
||||
default: dict()
|
||||
type: dict
|
||||
author: Sam Yaple
|
||||
'''
|
||||
|
||||
@ -341,7 +347,8 @@ class DockerWorker(object):
|
||||
self.compare_environment(container_info) or
|
||||
self.compare_container_state(container_info) or
|
||||
self.compare_dimensions(container_info) or
|
||||
self.compare_command(container_info)
|
||||
self.compare_command(container_info) or
|
||||
self.compare_healthcheck(container_info)
|
||||
)
|
||||
|
||||
def compare_ipc_mode(self, container_info):
|
||||
@ -535,6 +542,30 @@ class DockerWorker(object):
|
||||
new_args != container_info['Args']):
|
||||
return True
|
||||
|
||||
def compare_healthcheck(self, container_info):
|
||||
new_healthcheck = self.parse_healthcheck(
|
||||
self.params.get('healthcheck'))
|
||||
current_healthcheck = container_info['Config'].get('Healthcheck')
|
||||
|
||||
healthcheck_map = {
|
||||
'test': 'Test',
|
||||
'retries': 'Retries',
|
||||
'interval': 'Interval',
|
||||
'start_period': 'StartPeriod',
|
||||
'timeout': 'Timeout'}
|
||||
|
||||
if new_healthcheck:
|
||||
new_healthcheck = new_healthcheck['healthcheck']
|
||||
if current_healthcheck:
|
||||
new_healthcheck = dict((healthcheck_map.get(k, k), v)
|
||||
for (k, v) in new_healthcheck.items())
|
||||
return new_healthcheck != current_healthcheck
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
if current_healthcheck:
|
||||
return True
|
||||
|
||||
def parse_image(self):
|
||||
full_image = self.params.get('image')
|
||||
|
||||
@ -719,7 +750,8 @@ class DockerWorker(object):
|
||||
|
||||
def build_container_options(self):
|
||||
volumes, binds = self.generate_volumes()
|
||||
return {
|
||||
|
||||
options = {
|
||||
'command': self.params.get('command'),
|
||||
'detach': self.params.get('detach'),
|
||||
'environment': self._format_env_vars(),
|
||||
@ -731,6 +763,12 @@ class DockerWorker(object):
|
||||
'tty': self.params.get('tty'),
|
||||
}
|
||||
|
||||
healthcheck = self.parse_healthcheck(self.params.get('healthcheck'))
|
||||
if healthcheck:
|
||||
options.update(healthcheck)
|
||||
|
||||
return options
|
||||
|
||||
def create_container(self):
|
||||
self.changed = True
|
||||
options = self.build_container_options()
|
||||
@ -826,6 +864,71 @@ class DockerWorker(object):
|
||||
else:
|
||||
self.module.exit_json(**info['State'])
|
||||
|
||||
def parse_healthcheck(self, healthcheck):
|
||||
if not healthcheck:
|
||||
return None
|
||||
|
||||
result = dict(healthcheck={})
|
||||
|
||||
# All supported healthcheck parameters
|
||||
supported = set(['test', 'interval', 'timeout', 'start_period',
|
||||
'retries'])
|
||||
unsupported = set(healthcheck) - supported
|
||||
missing = supported - set(healthcheck)
|
||||
duration_options = set(['interval', 'timeout', 'start_period'])
|
||||
|
||||
if unsupported:
|
||||
self.module.exit_json(failed=True,
|
||||
msg=repr("Unsupported healthcheck options"),
|
||||
unsupported_healthcheck=unsupported)
|
||||
|
||||
if missing:
|
||||
self.module.exit_json(failed=True,
|
||||
msg=repr("Missing healthcheck option"),
|
||||
missing_healthcheck=missing)
|
||||
|
||||
for key in healthcheck:
|
||||
value = healthcheck.get(key)
|
||||
if key in duration_options:
|
||||
try:
|
||||
result['healthcheck'][key] = int(value) * 1000000000
|
||||
except TypeError:
|
||||
raise TypeError(
|
||||
'Cannot parse healthcheck "{0}". '
|
||||
'Expected an integer, got "{1}".'
|
||||
.format(value, type(value).__name__)
|
||||
)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'Cannot parse healthcheck "{0}". '
|
||||
'Expected an integer, got "{1}".'
|
||||
.format(value, type(value).__name__)
|
||||
)
|
||||
else:
|
||||
if key == 'test':
|
||||
# If the user explicitly disables the healthcheck,
|
||||
# return None as the healthcheck object
|
||||
if value in (['NONE'], 'NONE'):
|
||||
return None
|
||||
else:
|
||||
if isinstance(value, (tuple, list)):
|
||||
result['healthcheck'][key] = \
|
||||
[str(e) for e in value]
|
||||
else:
|
||||
result['healthcheck'][key] = \
|
||||
['CMD-SHELL', str(value)]
|
||||
elif key == 'retries':
|
||||
try:
|
||||
result['healthcheck'][key] = int(value)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'Cannot parse healthcheck number of retries.'
|
||||
'Expected an integer, got "{0}".'
|
||||
.format(type(value))
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def stop_container(self):
|
||||
name = self.params.get('name')
|
||||
graceful_timeout = self.params.get('graceful_timeout')
|
||||
@ -935,6 +1038,7 @@ def generate_module():
|
||||
labels=dict(required=False, type='dict', default=dict()),
|
||||
name=dict(required=False, type='str'),
|
||||
environment=dict(required=False, type='dict'),
|
||||
healthcheck=dict(required=False, type='dict'),
|
||||
image=dict(required=False, type='str'),
|
||||
ipc_mode=dict(required=False, type='str', choices=['',
|
||||
'host',
|
||||
|
@ -93,6 +93,7 @@ class ModuleArgsTest(base.BaseTestCase):
|
||||
dimensions=dict(required=False, type='dict', default=dict()),
|
||||
tty=dict(required=False, type='bool', default=False),
|
||||
client_timeout=dict(required=False, type='int', default=120),
|
||||
healthcheck=dict(required=False, type='dict'),
|
||||
)
|
||||
required_if = [
|
||||
['action', 'pull_image', ['image']],
|
||||
@ -259,8 +260,9 @@ class TestContainer(base.BaseTestCase):
|
||||
self.assertTrue(self.dw.changed)
|
||||
self.fake_data['params'].pop('dimensions')
|
||||
self.fake_data['params']['host_config']['blkio_weight'] = '10'
|
||||
expected_args = {'command', 'detach', 'environment', 'host_config',
|
||||
'image', 'labels', 'name', 'tty', 'volumes'}
|
||||
expected_args = {'command', 'detach', 'environment',
|
||||
'host_config', 'image', 'labels', 'name', 'tty',
|
||||
'volumes'}
|
||||
self.dw.dc.create_container.assert_called_once_with(
|
||||
**{k: self.fake_data['params'][k] for k in expected_args})
|
||||
self.dw.dc.create_host_config.assert_called_with(
|
||||
@ -278,6 +280,20 @@ class TestContainer(base.BaseTestCase):
|
||||
failed=True, msg=repr("Unsupported dimensions"),
|
||||
unsupported_dimensions=set(['random']))
|
||||
|
||||
def test_create_container_with_healthcheck(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD-SHELL', '/bin/check.sh']}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.dw.dc.create_host_config = mock.MagicMock(
|
||||
return_value=self.fake_data['params']['host_config'])
|
||||
self.dw.create_container()
|
||||
self.assertTrue(self.dw.changed)
|
||||
expected_args = {'command', 'detach', 'environment', 'host_config',
|
||||
'healthcheck', 'image', 'labels', 'name', 'tty',
|
||||
'volumes'}
|
||||
self.dw.dc.create_container.assert_called_once_with(
|
||||
**{k: self.fake_data['params'][k] for k in expected_args})
|
||||
|
||||
def test_start_container_without_pull(self):
|
||||
self.fake_data['params'].update({'auth_username': 'fake_user',
|
||||
'auth_password': 'fake_psw',
|
||||
@ -1102,3 +1118,236 @@ class TestAttrComp(base.BaseTestCase):
|
||||
'Ulimits': [ulimits_nofile]}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertFalse(self.dw.compare_dimensions(container_info))
|
||||
|
||||
def test_compare_empty_new_healthcheck(self):
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
'Healthcheck': {
|
||||
'Test': [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertTrue(self.dw.compare_healthcheck(container_info))
|
||||
|
||||
def test_compare_empty_current_healthcheck(self):
|
||||
self.fake_data['params']['healthcheck'] = {
|
||||
'test': ['CMD-SHELL', '/bin/check.sh'],
|
||||
'interval': 30,
|
||||
'timeout': 30,
|
||||
'start_period': 5,
|
||||
'retries': 3}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertTrue(self.dw.compare_healthcheck(container_info))
|
||||
|
||||
def test_compare_healthcheck_no_test(self):
|
||||
self.fake_data['params']['healthcheck'] = {
|
||||
'interval': 30,
|
||||
'timeout': 30,
|
||||
'start_period': 5,
|
||||
'retries': 3}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
'Healthcheck': {
|
||||
'Test': [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.dw.compare_healthcheck(container_info)
|
||||
self.dw.module.exit_json.assert_called_once_with(
|
||||
failed=True, msg=repr("Missing healthcheck option"),
|
||||
missing_healthcheck=set(['test']))
|
||||
|
||||
def test_compare_healthcheck_pos(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD', '/bin/check']}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
'Healthcheck': {
|
||||
'Test': [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertTrue(self.dw.compare_healthcheck(container_info))
|
||||
|
||||
def test_compare_healthcheck_neg(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD-SHELL', '/bin/check.sh'],
|
||||
'interval': 30,
|
||||
'timeout': 30,
|
||||
'start_period': 5,
|
||||
'retries': 3}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
"Healthcheck": {
|
||||
"Test": [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertFalse(self.dw.compare_healthcheck(container_info))
|
||||
|
||||
def test_compare_healthcheck_time_zero(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD-SHELL', '/bin/check.sh'],
|
||||
'interval': 0,
|
||||
'timeout': 30,
|
||||
'start_period': 5,
|
||||
'retries': 3}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
"Healthcheck": {
|
||||
"Test": [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertTrue(self.dw.compare_healthcheck(container_info))
|
||||
|
||||
def test_compare_healthcheck_time_wrong_type(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD-SHELL', '/bin/check.sh'],
|
||||
'timeout': 30,
|
||||
'start_period': 5,
|
||||
'retries': 3}
|
||||
self.fake_data['params']['healthcheck']['interval'] = \
|
||||
{"broken": {"interval": "True"}}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
"Healthcheck": {
|
||||
"Test": [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertRaises(TypeError,
|
||||
lambda: self.dw.compare_healthcheck(container_info))
|
||||
|
||||
def test_compare_healthcheck_time_wrong_value(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD-SHELL', '/bin/check.sh'],
|
||||
'timeout': 30,
|
||||
'start_period': 5,
|
||||
'retries': 3}
|
||||
self.fake_data['params']['healthcheck']['interval'] = "dog"
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
"Healthcheck": {
|
||||
"Test": [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertRaises(ValueError,
|
||||
lambda: self.dw.compare_healthcheck(container_info))
|
||||
|
||||
def test_compare_healthcheck_opt_missing(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD-SHELL', '/bin/check.sh'],
|
||||
'interval': 30,
|
||||
'timeout': 30,
|
||||
'retries': 3}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
"Healthcheck": {
|
||||
"Test": [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.dw.compare_healthcheck(container_info)
|
||||
self.dw.module.exit_json.assert_called_once_with(
|
||||
failed=True, msg=repr("Missing healthcheck option"),
|
||||
missing_healthcheck=set(['start_period']))
|
||||
|
||||
def test_compare_healthcheck_opt_extra(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD-SHELL', '/bin/check.sh'],
|
||||
'interval': 30,
|
||||
'start_period': 5,
|
||||
'extra_option': 1,
|
||||
'timeout': 30,
|
||||
'retries': 3}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
"Healthcheck": {
|
||||
"Test": [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.dw.compare_healthcheck(container_info)
|
||||
self.dw.module.exit_json.assert_called_once_with(
|
||||
failed=True, msg=repr("Unsupported healthcheck options"),
|
||||
unsupported_healthcheck=set(['extra_option']))
|
||||
|
||||
def test_compare_healthcheck_value_false(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['CMD-SHELL', '/bin/check.sh'],
|
||||
'interval': 30,
|
||||
'start_period': 5,
|
||||
'extra_option': 1,
|
||||
'timeout': 30,
|
||||
'retries': False}
|
||||
container_info = dict()
|
||||
container_info['Config'] = {
|
||||
"Healthcheck": {
|
||||
"Test": [
|
||||
"CMD-SHELL",
|
||||
"/bin/check.sh"],
|
||||
"Interval": 30000000000,
|
||||
"Timeout": 30000000000,
|
||||
"StartPeriod": 5000000000,
|
||||
"Retries": 3}}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertTrue(self.dw.compare_healthcheck(container_info))
|
||||
|
||||
def test_parse_healthcheck_empty(self):
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertIsNone(self.dw.parse_healthcheck(
|
||||
self.fake_data.get('params', {}).get('healthcheck')))
|
||||
|
||||
def test_parse_healthcheck_test_none(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': 'NONE'}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertIsNone(self.dw.parse_healthcheck(
|
||||
self.fake_data['params']['healthcheck']))
|
||||
|
||||
def test_parse_healthcheck_test_none_brackets(self):
|
||||
self.fake_data['params']['healthcheck'] = \
|
||||
{'test': ['NONE']}
|
||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||
self.assertIsNone(self.dw.parse_healthcheck(
|
||||
self.fake_data['params']['healthcheck']))
|
||||
|
Loading…
Reference in New Issue
Block a user