Skip to content

Commit c49876a

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Check config when checking the containers" into stable/wallaby
2 parents 1688083 + 767f1e4 commit c49876a

File tree

3 files changed

+149
-1
lines changed

3 files changed

+149
-1
lines changed

ansible/library/kolla_docker.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@
264264
'''
265265

266266

267+
COMPARE_CONFIG_CMD = ['/usr/local/bin/kolla_set_configs', '--check']
268+
269+
267270
def get_docker_client():
268271
return docker.APIClient
269272

@@ -348,7 +351,9 @@ def get_container_info(self):
348351

349352
def compare_container(self):
350353
container = self.check_container()
351-
if not container or self.check_container_differs():
354+
if (not container or
355+
self.check_container_differs() or
356+
self.compare_config()):
352357
self.changed = True
353358
return self.changed
354359

@@ -614,6 +619,43 @@ def compare_healthcheck(self, container_info):
614619
if current_healthcheck:
615620
return True
616621

622+
def compare_config(self):
623+
try:
624+
job = self.dc.exec_create(
625+
self.params['name'],
626+
COMPARE_CONFIG_CMD,
627+
user='root',
628+
)
629+
output = self.dc.exec_start(job)
630+
exec_inspect = self.dc.exec_inspect(job)
631+
except docker.errors.APIError as e:
632+
# NOTE(yoctozepto): If we have a client error, then the container
633+
# cannot be used for config check (e.g., is restarting, or stopped
634+
# in the mean time) - assume config is stale = return True.
635+
# Else, propagate the server error back.
636+
if e.is_client_error():
637+
return True
638+
else:
639+
raise
640+
# Exit codes:
641+
# 0: not changed
642+
# 1: changed
643+
# 137: abrupt exit -> changed
644+
# else: error
645+
if exec_inspect['ExitCode'] == 0:
646+
return False
647+
elif exec_inspect['ExitCode'] == 1:
648+
return True
649+
elif exec_inspect['ExitCode'] == 137:
650+
# NOTE(yoctozepto): This is Docker's command exit due to container
651+
# exit. It means the container is unstable so we are better off
652+
# marking it as requiring a restart due to config update.
653+
return True
654+
else:
655+
raise Exception('Failed to compare container configuration: '
656+
'ExitCode: %s Message: %s' %
657+
(exec_inspect['ExitCode'], output))
658+
617659
def parse_image(self):
618660
full_image = self.params.get('image')
619661

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
fixes:
3+
- |
4+
Fixes an issue where configuration in containers could become stale.
5+
This prevented containers with updated configuration from being
6+
restarted, e.g., if the ``kolla-ansible genconfig`` and
7+
``kolla-ansible deploy-containers`` commands were used together.
8+
`LP#1848775 <https://launchpad.net/bugs/1848775>`__

tests/test_kolla_docker.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,104 @@ def test_compare_image(self):
721721
self.dw.dc.images.assert_called_once_with()
722722
self.assertTrue(return_data)
723723

724+
def test_compare_config_unchanged(self):
725+
self.dw = get_DockerWorker(FAKE_DATA['params'])
726+
job = mock.MagicMock()
727+
self.dw.dc.exec_create.return_value = job
728+
self.dw.dc.exec_start.return_value = 'fake output'
729+
self.dw.dc.exec_inspect.return_value = {'ExitCode': 0}
730+
return_data = self.dw.compare_config()
731+
self.dw.dc.exec_create.assert_called_once_with(
732+
FAKE_DATA['params']['name'],
733+
kd.COMPARE_CONFIG_CMD,
734+
user='root')
735+
self.dw.dc.exec_start.assert_called_once_with(job)
736+
self.dw.dc.exec_inspect.assert_called_once_with(job)
737+
self.assertFalse(return_data)
738+
739+
def test_compare_config_changed(self):
740+
self.dw = get_DockerWorker(FAKE_DATA['params'])
741+
job = mock.MagicMock()
742+
self.dw.dc.exec_create.return_value = job
743+
self.dw.dc.exec_start.return_value = 'fake output'
744+
self.dw.dc.exec_inspect.return_value = {'ExitCode': 1}
745+
return_data = self.dw.compare_config()
746+
self.dw.dc.exec_create.assert_called_once_with(
747+
FAKE_DATA['params']['name'],
748+
kd.COMPARE_CONFIG_CMD,
749+
user='root')
750+
self.dw.dc.exec_start.assert_called_once_with(job)
751+
self.dw.dc.exec_inspect.assert_called_once_with(job)
752+
self.assertTrue(return_data)
753+
754+
def test_compare_config_changed_container_exited(self):
755+
self.dw = get_DockerWorker(FAKE_DATA['params'])
756+
job = mock.MagicMock()
757+
self.dw.dc.exec_create.return_value = job
758+
self.dw.dc.exec_start.return_value = 'fake output'
759+
self.dw.dc.exec_inspect.return_value = {'ExitCode': 137}
760+
return_data = self.dw.compare_config()
761+
self.dw.dc.exec_create.assert_called_once_with(
762+
FAKE_DATA['params']['name'],
763+
kd.COMPARE_CONFIG_CMD,
764+
user='root')
765+
self.dw.dc.exec_start.assert_called_once_with(job)
766+
self.dw.dc.exec_inspect.assert_called_once_with(job)
767+
self.assertTrue(return_data)
768+
769+
def test_compare_config_changed_client_failure(self):
770+
self.dw = get_DockerWorker(FAKE_DATA['params'])
771+
job = mock.MagicMock()
772+
self.dw.dc.exec_create.return_value = job
773+
self.dw.dc.exec_start.return_value = 'fake output'
774+
failure_response = mock.MagicMock()
775+
failure_response.status_code = 409 # any client error should do here
776+
self.dw.dc.exec_inspect.side_effect = docker_error.APIError(
777+
message="foo",
778+
response=failure_response,
779+
)
780+
return_data = self.dw.compare_config()
781+
self.dw.dc.exec_create.assert_called_once_with(
782+
FAKE_DATA['params']['name'],
783+
kd.COMPARE_CONFIG_CMD,
784+
user='root')
785+
self.dw.dc.exec_start.assert_called_once_with(job)
786+
self.dw.dc.exec_inspect.assert_called_once_with(job)
787+
self.assertTrue(return_data)
788+
789+
def test_compare_config_error(self):
790+
self.dw = get_DockerWorker(FAKE_DATA['params'])
791+
job = mock.MagicMock()
792+
self.dw.dc.exec_create.return_value = job
793+
self.dw.dc.exec_start.return_value = 'fake output'
794+
self.dw.dc.exec_inspect.return_value = {'ExitCode': -1}
795+
self.assertRaises(Exception, self.dw.compare_config) # noqa: H202
796+
self.dw.dc.exec_create.assert_called_once_with(
797+
FAKE_DATA['params']['name'],
798+
kd.COMPARE_CONFIG_CMD,
799+
user='root')
800+
self.dw.dc.exec_start.assert_called_once_with(job)
801+
self.dw.dc.exec_inspect.assert_called_once_with(job)
802+
803+
def test_compare_config_error_server_failure(self):
804+
self.dw = get_DockerWorker(FAKE_DATA['params'])
805+
job = mock.MagicMock()
806+
self.dw.dc.exec_create.return_value = job
807+
self.dw.dc.exec_start.return_value = 'fake output'
808+
failure_response = mock.MagicMock()
809+
failure_response.status_code = 500 # any server error should do here
810+
self.dw.dc.exec_inspect.side_effect = docker_error.APIError(
811+
message="foo",
812+
response=failure_response,
813+
)
814+
self.assertRaises(docker_error.APIError, self.dw.compare_config)
815+
self.dw.dc.exec_create.assert_called_once_with(
816+
FAKE_DATA['params']['name'],
817+
kd.COMPARE_CONFIG_CMD,
818+
user='root')
819+
self.dw.dc.exec_start.assert_called_once_with(job)
820+
self.dw.dc.exec_inspect.assert_called_once_with(job)
821+
724822
def test_get_image_id_not_exists(self):
725823
self.dw = get_DockerWorker(
726824
{'image': 'myregistrydomain.com:5000/ubuntu:16.04'})

0 commit comments

Comments
 (0)