diff --git a/senlin/drivers/container/docker_v1.py b/senlin/drivers/container/docker_v1.py index 737b0f133..b127e84fa 100644 --- a/senlin/drivers/container/docker_v1.py +++ b/senlin/drivers/container/docker_v1.py @@ -52,3 +52,7 @@ class DockerClient(object): def stop(self, container, timeout=None): params = {'timeout': timeout} self._dockerclient.stop(container, **params) + + @sdk.translate_exception + def rename(self, container, name): + self._dockerclient.rename(container, name) diff --git a/senlin/profiles/container/docker.py b/senlin/profiles/container/docker.py index e5a6e2ff9..58afb5e92 100644 --- a/senlin/profiles/container/docker.py +++ b/senlin/profiles/container/docker.py @@ -58,7 +58,8 @@ class DockerProfile(base.Profile): required=True, ), NAME: schema.String( - _('The name of the container.') + _('The name of the container.'), + updatable=True, ), COMMAND: schema.String( _('The command to run when container is started.') @@ -330,6 +331,53 @@ class DockerProfile(base.Profile): db_api.node_remove_dependents(ctx, self.host.id, obj.id) return + def do_update(self, obj, new_profile=None, **params): + """Perform update on the container. + + :param obj: the container to operate on + :param new_profile: the new profile for the container. + :param params: a dictionary of optional parameters. + :returns: True if update was successful or False otherwise. + :raises: `EResourceUpdate` if operation fails. + """ + self.server_id = obj.physical_id + if not self.server_id: + return False + + if not new_profile: + return False + + if not self.validate_for_update(new_profile): + return False + + name_changed, new_name = self._check_container_name(obj, new_profile) + if name_changed: + self._update_name(obj, new_name) + + return True + + def _check_container_name(self, obj, profile): + """Check if there is a new name to be assigned to the container. + + :param obj: The node object to operate on. + :param new_profile: The new profile which may contain a name for + the container. + :return: A tuple consisting a boolean indicating whether the name + needs change and the container name determined. + """ + old_name = self.properties[self.NAME] or obj.name + new_name = profile.properties[self.NAME] or obj.name + if old_name == new_name: + return False, new_name + return True, new_name + + def _update_name(self, obj, new_name): + try: + self.docker(obj).rename(obj.physical_id, new_name) + except exc.InternalError as ex: + raise exc.EResourceUpdate(type='container', id=obj.physical_id, + message=six.text_type(ex)) + def handle_reboot(self, obj, **options): """Handler for a reboot operation. diff --git a/senlin/tests/unit/drivers/test_docker_v1.py b/senlin/tests/unit/drivers/test_docker_v1.py index dd824550b..b2edccd72 100644 --- a/senlin/tests/unit/drivers/test_docker_v1.py +++ b/senlin/tests/unit/drivers/test_docker_v1.py @@ -106,3 +106,10 @@ class TestDocker(base.SenlinTestCase): self.assertIsNone(res) self.x_docker.stop.assert_called_once_with(container, **params) + + def test_rename(self): + container = mock.Mock() + res = self.sot.rename(container, 'new_name') + + self.assertIsNone(res) + self.x_docker.rename.assert_called_once_with(container, 'new_name') diff --git a/senlin/tests/unit/profiles/test_container_docker.py b/senlin/tests/unit/profiles/test_container_docker.py index cdef14e7a..6ac836bb4 100644 --- a/senlin/tests/unit/profiles/test_container_docker.py +++ b/senlin/tests/unit/profiles/test_container_docker.py @@ -476,6 +476,119 @@ class TestContainerDockerProfile(base.SenlinTestCase): self.assertRaises(exc.EResourceDeletion, profile.do_delete, obj) + @mock.patch.object(dp.DockerProfile, 'docker') + def test_update_name(self, mock_docker): + x_docker = mock.Mock() + x_docker = mock_docker.return_value + obj = mock.Mock(physical_id='FAKE_ID') + + docker = dp.DockerProfile('container', self.spec) + res = docker._update_name(obj, 'NEW_NAME') + + self.assertIsNone(res) + x_docker.rename.assert_called_once_with('FAKE_ID', 'NEW_NAME') + + @mock.patch.object(dp.DockerProfile, 'docker') + def test_update_name_docker_failure(self, mock_docker): + x_docker = mock.Mock() + x_docker = mock_docker.return_value + x_docker.rename.side_effect = exc.InternalError(message='BOOM') + obj = mock.Mock(physical_id='FAKE_ID') + docker = dp.DockerProfile('container', self.spec) + + ex = self.assertRaises(exc.EResourceUpdate, + docker._update_name, + obj, 'NEW_NAME') + + self.assertEqual("Failed in updating container 'FAKE_ID': BOOM.", + six.text_type(ex)) + x_docker.rename.assert_called_once_with('FAKE_ID', 'NEW_NAME') + + @mock.patch.object(dp.DockerProfile, 'docker') + def test_do_update(self, mock_docker): + obj = mock.Mock(physical_id='FAKE_ID') + docker = dp.DockerProfile('container', self.spec) + + new_spec = { + 'type': 'container.dockerinc.docker', + 'version': '1.0', + 'properties': { + 'context': { + 'region_name': 'RegionOne' + }, + 'name': 'new_name', + 'image': 'hello-world', + 'command': '/bin/sleep 30', + 'port': 2375, + 'host_node': 'fake_node', + } + } + new_profile = dp.DockerProfile('u', new_spec) + res = docker.do_update(obj, new_profile) + + self.assertTrue(res) + + @mock.patch.object(dp.DockerProfile, 'docker') + def test_do_update_no_new_profile(self, mock_docker): + obj = mock.Mock(physical_id='FAKE_ID') + docker = dp.DockerProfile('container', self.spec) + + params = {} + res = docker.do_update(obj, params) + + self.assertFalse(res) + + def test_do_update_no_physical_id(self): + obj = mock.Mock(physical_id=None) + profile = dp.DockerProfile('container', self.spec) + self.assertFalse(profile.do_update(obj)) + + def test_check_container_name(self): + obj = mock.Mock(physical_id='FAKE_ID') + docker = dp.DockerProfile('container', self.spec) + + new_spec = { + 'type': 'container.dockerinc.docker', + 'version': '1.0', + 'properties': { + 'context': { + 'region_name': 'RegionOne' + }, + 'name': 'new_name', + 'image': 'hello-world', + 'command': '/bin/sleep 30', + 'port': 2375, + 'host_node': 'fake_node', + } + } + new_profile = dp.DockerProfile('u', new_spec) + res, new_name = docker._check_container_name(obj, new_profile) + + self.assertTrue(res) + + def test_check_container_same_name(self): + obj = mock.Mock(physical_id='FAKE_ID') + docker = dp.DockerProfile('container', self.spec) + + new_spec = { + 'type': 'container.dockerinc.docker', + 'version': '1.0', + 'properties': { + 'context': { + 'region_name': 'RegionOne' + }, + 'name': 'docker_container', + 'image': 'hello-world', + 'command': '/bin/sleep 30', + 'port': 2375, + 'host_node': 'fake_node', + } + } + new_profile = dp.DockerProfile('u', new_spec) + res, new_name = docker._check_container_name(obj, new_profile) + + self.assertFalse(res) + @mock.patch.object(dp.DockerProfile, 'docker') def test_handle_reboot(self, mock_docker): x_docker = mock.Mock()