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
|
required: False
|
||||||
default: 120
|
default: 120
|
||||||
type: int
|
type: int
|
||||||
|
healthcheck:
|
||||||
|
description:
|
||||||
|
- Container healthcheck configuration
|
||||||
|
required: False
|
||||||
|
default: dict()
|
||||||
|
type: dict
|
||||||
author: Sam Yaple
|
author: Sam Yaple
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@ -341,7 +347,8 @@ class DockerWorker(object):
|
|||||||
self.compare_environment(container_info) or
|
self.compare_environment(container_info) or
|
||||||
self.compare_container_state(container_info) or
|
self.compare_container_state(container_info) or
|
||||||
self.compare_dimensions(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):
|
def compare_ipc_mode(self, container_info):
|
||||||
@ -535,6 +542,30 @@ class DockerWorker(object):
|
|||||||
new_args != container_info['Args']):
|
new_args != container_info['Args']):
|
||||||
return True
|
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):
|
def parse_image(self):
|
||||||
full_image = self.params.get('image')
|
full_image = self.params.get('image')
|
||||||
|
|
||||||
@ -719,7 +750,8 @@ class DockerWorker(object):
|
|||||||
|
|
||||||
def build_container_options(self):
|
def build_container_options(self):
|
||||||
volumes, binds = self.generate_volumes()
|
volumes, binds = self.generate_volumes()
|
||||||
return {
|
|
||||||
|
options = {
|
||||||
'command': self.params.get('command'),
|
'command': self.params.get('command'),
|
||||||
'detach': self.params.get('detach'),
|
'detach': self.params.get('detach'),
|
||||||
'environment': self._format_env_vars(),
|
'environment': self._format_env_vars(),
|
||||||
@ -731,6 +763,12 @@ class DockerWorker(object):
|
|||||||
'tty': self.params.get('tty'),
|
'tty': self.params.get('tty'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
healthcheck = self.parse_healthcheck(self.params.get('healthcheck'))
|
||||||
|
if healthcheck:
|
||||||
|
options.update(healthcheck)
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
||||||
def create_container(self):
|
def create_container(self):
|
||||||
self.changed = True
|
self.changed = True
|
||||||
options = self.build_container_options()
|
options = self.build_container_options()
|
||||||
@ -826,6 +864,71 @@ class DockerWorker(object):
|
|||||||
else:
|
else:
|
||||||
self.module.exit_json(**info['State'])
|
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):
|
def stop_container(self):
|
||||||
name = self.params.get('name')
|
name = self.params.get('name')
|
||||||
graceful_timeout = self.params.get('graceful_timeout')
|
graceful_timeout = self.params.get('graceful_timeout')
|
||||||
@ -935,6 +1038,7 @@ def generate_module():
|
|||||||
labels=dict(required=False, type='dict', default=dict()),
|
labels=dict(required=False, type='dict', default=dict()),
|
||||||
name=dict(required=False, type='str'),
|
name=dict(required=False, type='str'),
|
||||||
environment=dict(required=False, type='dict'),
|
environment=dict(required=False, type='dict'),
|
||||||
|
healthcheck=dict(required=False, type='dict'),
|
||||||
image=dict(required=False, type='str'),
|
image=dict(required=False, type='str'),
|
||||||
ipc_mode=dict(required=False, type='str', choices=['',
|
ipc_mode=dict(required=False, type='str', choices=['',
|
||||||
'host',
|
'host',
|
||||||
|
@ -93,6 +93,7 @@ class ModuleArgsTest(base.BaseTestCase):
|
|||||||
dimensions=dict(required=False, type='dict', default=dict()),
|
dimensions=dict(required=False, type='dict', default=dict()),
|
||||||
tty=dict(required=False, type='bool', default=False),
|
tty=dict(required=False, type='bool', default=False),
|
||||||
client_timeout=dict(required=False, type='int', default=120),
|
client_timeout=dict(required=False, type='int', default=120),
|
||||||
|
healthcheck=dict(required=False, type='dict'),
|
||||||
)
|
)
|
||||||
required_if = [
|
required_if = [
|
||||||
['action', 'pull_image', ['image']],
|
['action', 'pull_image', ['image']],
|
||||||
@ -259,8 +260,9 @@ class TestContainer(base.BaseTestCase):
|
|||||||
self.assertTrue(self.dw.changed)
|
self.assertTrue(self.dw.changed)
|
||||||
self.fake_data['params'].pop('dimensions')
|
self.fake_data['params'].pop('dimensions')
|
||||||
self.fake_data['params']['host_config']['blkio_weight'] = '10'
|
self.fake_data['params']['host_config']['blkio_weight'] = '10'
|
||||||
expected_args = {'command', 'detach', 'environment', 'host_config',
|
expected_args = {'command', 'detach', 'environment',
|
||||||
'image', 'labels', 'name', 'tty', 'volumes'}
|
'host_config', 'image', 'labels', 'name', 'tty',
|
||||||
|
'volumes'}
|
||||||
self.dw.dc.create_container.assert_called_once_with(
|
self.dw.dc.create_container.assert_called_once_with(
|
||||||
**{k: self.fake_data['params'][k] for k in expected_args})
|
**{k: self.fake_data['params'][k] for k in expected_args})
|
||||||
self.dw.dc.create_host_config.assert_called_with(
|
self.dw.dc.create_host_config.assert_called_with(
|
||||||
@ -278,6 +280,20 @@ class TestContainer(base.BaseTestCase):
|
|||||||
failed=True, msg=repr("Unsupported dimensions"),
|
failed=True, msg=repr("Unsupported dimensions"),
|
||||||
unsupported_dimensions=set(['random']))
|
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):
|
def test_start_container_without_pull(self):
|
||||||
self.fake_data['params'].update({'auth_username': 'fake_user',
|
self.fake_data['params'].update({'auth_username': 'fake_user',
|
||||||
'auth_password': 'fake_psw',
|
'auth_password': 'fake_psw',
|
||||||
@ -1102,3 +1118,236 @@ class TestAttrComp(base.BaseTestCase):
|
|||||||
'Ulimits': [ulimits_nofile]}
|
'Ulimits': [ulimits_nofile]}
|
||||||
self.dw = get_DockerWorker(self.fake_data['params'])
|
self.dw = get_DockerWorker(self.fake_data['params'])
|
||||||
self.assertFalse(self.dw.compare_dimensions(container_info))
|
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