diff --git a/tripleo_common/actions/scale.py b/tripleo_common/actions/scale.py index 17e478773..0d90de2a8 100644 --- a/tripleo_common/actions/scale.py +++ b/tripleo_common/actions/scale.py @@ -120,20 +120,37 @@ class ScaleDownAction(templates.ProcessTemplatesAction): return stack_params + def _match_hostname(self, heatclient, instance_list, res, stack_name): + type_patterns = ['DeployedServer', 'Server'] + if any(res.resource_type.endswith(x) for x in type_patterns): + res_details = heatclient.resources.get( + stack_name, res.resource_name) + if 'name' in res_details.attributes: + try: + instance_list.remove(res_details.attributes['name']) + return True + except ValueError: + return False + return False + def run(self, context): heatclient = self.get_orchestration_client(context) resources = heatclient.resources.list(self.container, nested_depth=5) resources_by_role = collections.defaultdict(list) instance_list = list(self.nodes) - for res in resources: - try: - instance_list.remove(res.physical_resource_id) - except ValueError: - continue + for res in resources: stack_name, stack_id = next( x['href'] for x in res.links if x['rel'] == 'stack').rsplit('/', 2)[1:] + + try: + instance_list.remove(res.physical_resource_id) + except ValueError: + if not self._match_hostname( + heatclient, instance_list, res, stack_name): + continue + # get resource to remove from resource group (it's parent resource # of nova server) role_resource = next(x for x in resources if diff --git a/tripleo_common/tests/actions/test_scale.py b/tripleo_common/tests/actions/test_scale.py index 41bd6decd..c73e364a5 100644 --- a/tripleo_common/tests/actions/test_scale.py +++ b/tripleo_common/tests/actions/test_scale.py @@ -73,7 +73,7 @@ class ScaleDownActionTest(base.TestCase): 'a959ac7d6a4a475daf2428df315c41ef/' 'stacks/overcloud/124'}], logical_resource_id='node0', - physical_resource_id='123', + physical_resource_id='124', resource_type='OS::TripleO::Compute', parent_resource='Compute', resource_name='node0', @@ -130,7 +130,7 @@ class ScaleDownActionTest(base.TestCase): # Test action = scale.ScaleDownAction( - constants.STACK_TIMEOUT_DEFAULT, ['resource_id'], 'stack') + constants.STACK_TIMEOUT_DEFAULT, ['124'], 'stack') result = action.run(mock_ctx) heatclient.stacks.validate.assert_called_once_with( @@ -203,3 +203,119 @@ class ScaleDownActionTest(base.TestCase): result = action.run(mock_ctx) self.assertEqual(actions.Result(error='Update error'), result) + + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'cache_delete') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + @mock.patch('heatclient.common.template_utils.' + 'process_multiple_environments_and_files') + @mock.patch('heatclient.common.template_utils.get_template_contents') + @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') + def test_run_with_hostmatch(self, mock_get_object_client, + mock_get_template_contents, mock_env_files, + mock_get_heat_client, mock_cache): + + mock_env_files.return_value = ({}, {}) + heatclient = mock.MagicMock() + heatclient.resources.list.return_value = [ + mock.MagicMock( + links=[{'rel': 'stack', + 'href': 'http://192.0.2.1:8004/v1/' + 'a959ac7d6a4a475daf2428df315c41ef/' + 'stacks/overcloud/123'}], + logical_resource_id='logical_id', + physical_resource_id='resource_id', + resource_type='OS::Heat::ResourceGroup', + resource_name='Compute' + ), + mock.MagicMock( + links=[{'rel': 'stack', + 'href': 'http://192.0.2.1:8004/v1/' + 'a959ac7d6a4a475daf2428df315c41ef/' + 'stacks/overcloud/124'}], + logical_resource_id='node0', + physical_resource_id='124', + resource_type='OS::TripleO::ComputeServer', + parent_resource='Compute', + resource_name='node0', + ) + ] + heatclient.resources.get.return_value = mock.MagicMock( + attributes={'name': 'node0'}) + heatclient.stacks.get.return_value = mock_stack() + heatclient.stacks.validate.return_value = {} + mock_get_heat_client.return_value = heatclient + + mock_ctx = mock.MagicMock() + swift = mock.MagicMock(url="http://test.com") + mock_env = yaml.safe_dump({ + 'name': 'overcloud', + 'temp_environment': 'temp_environment', + 'template': 'template', + 'environments': [{u'path': u'environments/test.yaml'}] + }, default_flow_style=False) + mock_roles = yaml.safe_dump([{"name": "foo"}]) + mock_network = yaml.safe_dump([{'enabled': False}]) + mock_exclude = yaml.safe_dump({"name": "foo"}) + swift.get_object.side_effect = ( + ({}, mock_env), + ({}, mock_env), + ({}, mock_roles), + ({}, mock_network), + ({}, mock_exclude), + ({}, mock_env), + ({}, mock_env), + ({}, mock_env), + ({}, mock_roles), + ({}, mock_network), + ({}, mock_exclude), + ({}, mock_env), + ({}, mock_env), + swiftexceptions.ClientException('atest2') + ) + + def return_container_files(*args): + return ('headers', [{'name': 'foo.role.j2.yaml'}]) + + swift.get_container = mock.MagicMock( + side_effect=return_container_files) + mock_get_object_client.return_value = swift + + env = { + 'resource_registry': { + 'resources': {'*': {'*': {'UpdateDeployment': {'hooks': []}}}} + } + } + + mock_get_template_contents.return_value = ({}, { + 'heat_template_version': '2016-04-30' + }) + + # Test + action = scale.ScaleDownAction( + constants.STACK_TIMEOUT_DEFAULT, ['node0'], 'stack') + result = action.run(mock_ctx) + + heatclient.stacks.validate.assert_called_once_with( + environment=env, + files={}, + show_nested=True, + template={'heat_template_version': '2016-04-30'} + ) + + clear_list = list(['ComputeCount', 'ComputeRemovalPolicies', + 'ComputeRemovalPoliciesMode']) + _, kwargs = heatclient.stacks.update.call_args + self.assertEqual(set(kwargs['clear_parameters']), set(clear_list)) + self.assertEqual(kwargs['environment'], env) + self.assertEqual(kwargs['existing'], True) + self.assertEqual(kwargs['files'], {}) + + mock_cache.assert_called_with( + mock_ctx, + "stack", + "tripleo.parameters.get" + ) + + self.assertEqual(None, result)