diff --git a/doc/source/model.rst b/doc/source/model.rst index ece33ce9e..70818f586 100644 --- a/doc/source/model.rst +++ b/doc/source/model.rst @@ -451,3 +451,34 @@ A Magnum Service from magnum report_count=int(), state=str(), properties=dict()) + +Stack +----- + +A Stack from Heat + +.. code-block:: python + + Stack = dict( + location=Location(), + id=str(), + name=str(), + created_at=str(), + deleted_at=str(), + updated_at=str(), + description=str(), + action=str(), + identifier=str(), + is_rollback_enabled=bool(), + notification_topics=list(), + outputs=list(), + owner=str(), + parameters=dict(), + parent=str(), + stack_user_project_id=str(), + status=str(), + status_reason=str(), + tags=dict(), + tempate_description=str(), + timeout_mins=int(), + properties=dict()) diff --git a/shade/_normalize.py b/shade/_normalize.py index 9a8103ece..7e7f12c3e 100644 --- a/shade/_normalize.py +++ b/shade/_normalize.py @@ -936,3 +936,62 @@ class Normalizer(object): ret[key] = magnum_service.pop(key) ret['properties'] = magnum_service return ret + + def _normalize_stacks(self, stacks): + """Normalize Heat Stacks""" + ret = [] + for stack in stacks: + ret.append(self._normalize_stack(stack)) + return ret + + def _normalize_stack(self, stack): + """Normalize Heat Stack""" + stack = stack.copy() + + # Discard noise + stack.pop('HUMAN_ID', None) + stack.pop('human_id', None) + stack.pop('NAME_ATTR', None) + stack.pop('links', None) + # Discard things heatclient adds that aren't in the REST + stack.pop('action', None) + stack.pop('status', None) + stack.pop('identifier', None) + + stack_status = stack.pop('stack_status') + (action, status) = stack_status.split('_') + + ret = munch.Munch( + id=stack.pop('id'), + location=self._get_current_location(), + action=action, + status=status, + ) + if not self.strict_mode: + ret['stack_status'] = stack_status + + for (new_name, old_name) in ( + ('name', 'stack_name'), + ('created_at', 'creation_time'), + ('deleted_at', 'deletion_time'), + ('updated_at', 'updated_time'), + ('description', 'description'), + ('is_rollback_enabled', 'disable_rollback'), + ('parent', 'parent'), + ('notification_topics', 'notification_topics'), + ('parameters', 'parameters'), + ('outputs', 'outputs'), + ('owner', 'stack_owner'), + ('status_reason', 'stack_status_reason'), + ('stack_user_project_id', 'stack_user_project_id'), + ('tempate_description', 'template_description'), + ('timeout_mins', 'timeout_mins'), + ('tags', 'tags')): + value = stack.pop(old_name, None) + ret[new_name] = value + if not self.strict_mode: + ret[old_name] = value + ret['identifier'] = '{name}/{id}'.format( + name=ret['name'], id=ret['id']) + ret['properties'] = stack + return ret diff --git a/shade/_utils.py b/shade/_utils.py index f54e8809b..238986685 100644 --- a/shade/_utils.py +++ b/shade/_utils.py @@ -369,13 +369,6 @@ def normalize_flavor_accesses(flavor_accesses): ] -def normalize_stacks(stacks): - """ Normalize Stack Object """ - for stack in stacks: - stack['name'] = stack['stack_name'] - return stacks - - def valid_kwargs(*valid_args): # This decorator checks if argument passed as **kwargs to a function are # present in valid_args. diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 2c7f988f2..32a84f8f4 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -1771,7 +1771,7 @@ class OpenStackCloud(_normalize.Normalizer): """ with _utils.shade_exceptions("Error fetching stack list"): stacks = self.manager.submit_task(_tasks.StackList()) - return _utils.normalize_stacks(stacks) + return self._normalize_stacks(stacks) def list_server_security_groups(self, server): """List all security groups associated with the given server. @@ -2781,7 +2781,7 @@ class OpenStackCloud(_normalize.Normalizer): stacks = [stack] except heat_exceptions.NotFound: return [] - nstacks = _utils.normalize_stacks(stacks) + nstacks = self._normalize_stacks(stacks) return _utils._filter_list(nstacks, name_or_id, filters) return _utils._get_entity( diff --git a/shade/tests/unit/test_stack.py b/shade/tests/unit/test_stack.py index f0d1e2f51..1618b9d3b 100644 --- a/shade/tests/unit/test_stack.py +++ b/shade/tests/unit/test_stack.py @@ -33,7 +33,9 @@ class TestStack(base.TestCase): mock_heat.stacks.list.return_value = fake_stacks stacks = self.cloud.list_stacks() mock_heat.stacks.list.assert_called_once_with() - self.assertEqual(meta.obj_list_to_dict(fake_stacks), stacks) + self.assertEqual( + self.cloud._normalize_stacks(meta.obj_list_to_dict(fake_stacks)), + stacks) @mock.patch.object(shade.OpenStackCloud, 'heat_client') def test_list_stacks_exception(self, mock_heat): @@ -53,19 +55,24 @@ class TestStack(base.TestCase): mock_heat.stacks.list.return_value = fake_stacks stacks = self.cloud.search_stacks() mock_heat.stacks.list.assert_called_once_with() - self.assertEqual(meta.obj_list_to_dict(fake_stacks), stacks) + self.assertEqual( + self.cloud._normalize_stacks(meta.obj_list_to_dict(fake_stacks)), + stacks) @mock.patch.object(shade.OpenStackCloud, 'heat_client') def test_search_stacks_filters(self, mock_heat): fake_stacks = [ - fakes.FakeStack('001', 'stack1', status='GOOD'), - fakes.FakeStack('002', 'stack2', status='BAD'), + fakes.FakeStack('001', 'stack1', status='CREATE_COMPLETE'), + fakes.FakeStack('002', 'stack2', status='CREATE_FAILED'), ] mock_heat.stacks.list.return_value = fake_stacks - filters = {'stack_status': 'GOOD'} + filters = {'status': 'COMPLETE'} stacks = self.cloud.search_stacks(filters=filters) mock_heat.stacks.list.assert_called_once_with() - self.assertEqual(meta.obj_list_to_dict(fake_stacks[:1]), stacks) + self.assertEqual( + self.cloud._normalize_stacks( + meta.obj_list_to_dict(fake_stacks[:1])), + stacks) @mock.patch.object(shade.OpenStackCloud, 'heat_client') def test_search_stacks_exception(self, mock_heat): @@ -138,6 +145,8 @@ class TestStack(base.TestCase): @mock.patch.object(shade.OpenStackCloud, 'heat_client') def test_create_stack(self, mock_heat, mock_template): mock_template.return_value = ({}, {}) + mock_heat.stacks.create.return_value = fakes.FakeStack('001', 'stack1') + mock_heat.stacks.get.return_value = fakes.FakeStack('001', 'stack1') self.cloud.create_stack('stack_name') self.assertTrue(mock_template.called) mock_heat.stacks.create.assert_called_once_with( @@ -159,6 +168,8 @@ class TestStack(base.TestCase): stack = {'id': 'stack_id', 'name': 'stack_name'} mock_template.return_value = ({}, {}) mock_get.return_value = stack + mock_heat.stacks.create.return_value = fakes.FakeStack('001', 'stack1') + mock_heat.stacks.get.return_value = fakes.FakeStack('001', 'stack1') ret = self.cloud.create_stack('stack_name', wait=True) self.assertTrue(mock_template.called) mock_heat.stacks.create.assert_called_once_with( @@ -178,6 +189,8 @@ class TestStack(base.TestCase): @mock.patch.object(shade.OpenStackCloud, 'heat_client') def test_update_stack(self, mock_heat, mock_template): mock_template.return_value = ({}, {}) + mock_heat.stacks.update.return_value = fakes.FakeStack('001', 'stack1') + mock_heat.stacks.get.return_value = fakes.FakeStack('001', 'stack1') self.cloud.update_stack('stack_name') self.assertTrue(mock_template.called) mock_heat.stacks.update.assert_called_once_with(