Skip to content

Commit 767f1e4

Browse files
Check config when checking the containers
The proposed approach allows for checking whether config files are current, e.g. cases when the deployment was aborted after config files were generated but before they were injected into the containers which lead to old config staying in containers. After this patch we can do: kolla-ansible genconfig kolla-ansible deploy-containers and it would do what we expected rather than being a noop in the second part. We also lose the need to have notifies and whens in config and handler sections respectively. This is optimised in a separate patch. Future work: - optimise for large files - could we get away with comparing timestamps and sizes? container's should have a newer timestamp due to copy, could also preserve it Change-Id: I1d26e48e1958f13b854d8afded4bfba5021a2dec Closes-Bug: #1848775 Depends-On: https://review.opendev.org/c/openstack/kolla/+/773257 Co-Authored-By: Mark Goddard <[email protected]> (cherry picked from commit c3afbd3)
1 parent b6d8eef commit 767f1e4

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)