From b1bc65c599812a02f6861c1d80c31c3b9be894da Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Tue, 6 Feb 2018 12:26:29 +0100 Subject: [PATCH] Allow not resolving outputs on get stacks Depending on the stack complexity, resolving outputs of a Heat stack can be fairly expensive. The API has a parameter to disable output resolution: this adds this parameter to the get_stack call, and use it in places where outputs are superfluous. Change-Id: I779e451c9f058a954a0526404489a4fb1b39ee9c --- shade/_heat/event_utils.py | 2 +- shade/openstackcloud.py | 13 ++++-- shade/tests/unit/test_stack.py | 77 +++++++++++++++++++--------------- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/shade/_heat/event_utils.py b/shade/_heat/event_utils.py index 69c286220..839f2aad4 100644 --- a/shade/_heat/event_utils.py +++ b/shade/_heat/event_utils.py @@ -86,7 +86,7 @@ def poll_for_events( if no_event_polls >= 2: # after 2 polls with no events, fall back to a stack get - stack = cloud.get_stack(stack_name) + stack = cloud.get_stack(stack_name, resolve_outputs=False) stack_status = stack['stack_status'] msg = msg_template % dict( name=stack_name, status=stack_status) diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 9af2f6b3a..6a132f751 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -1394,7 +1394,7 @@ class OpenStackCloud( :raises: ``OpenStackCloudException`` if something goes wrong during the OpenStack API call """ - stack = self.get_stack(name_or_id) + stack = self.get_stack(name_or_id, resolve_outputs=False) if stack is None: self.log.debug("Stack %s not found for deleting", name_or_id) return False @@ -1416,7 +1416,7 @@ class OpenStackCloud( marker=marker) except OpenStackCloudHTTPError: pass - stack = self.get_stack(name_or_id) + stack = self.get_stack(name_or_id, resolve_outputs=False) if stack and stack['stack_status'] == 'DELETE_FAILED': raise OpenStackCloudException( "Failed to delete stack {id}: {reason}".format( @@ -3336,12 +3336,14 @@ class OpenStackCloud( return self._normalize_floating_ip( self._get_and_munchify('floating_ip', data)) - def get_stack(self, name_or_id, filters=None): + def get_stack(self, name_or_id, filters=None, resolve_outputs=True): """Get exactly one stack. :param name_or_id: Name or ID of the desired stack. :param filters: a dict containing additional filters to use. e.g. {'stack_status': 'CREATE_COMPLETE'} + :param resolve_outputs: If True, then outputs for this + stack will be resolved :returns: a ``munch.Munch`` containing the stack description @@ -3353,8 +3355,11 @@ class OpenStackCloud( # stack names are mandatory and enforced unique in the project # so a StackGet can always be used for name or ID. try: + url = '/stacks/{name_or_id}'.format(name_or_id=name_or_id) + if not resolve_outputs: + url = '{url}?resolve_outputs=False'.format(url=url) data = self._orchestration_client.get( - '/stacks/{name_or_id}'.format(name_or_id=name_or_id), + url, error_message="Error fetching stack") stack = self._get_and_munchify('stack', data) # Treat DELETE_COMPLETE stacks as a NotFound diff --git a/shade/tests/unit/test_stack.py b/shade/tests/unit/test_stack.py index 5c4daa635..bc3380c22 100644 --- a/shade/tests/unit/test_stack.py +++ b/shade/tests/unit/test_stack.py @@ -112,20 +112,22 @@ class TestStack(base.RequestsMockTestCase): self.cloud.search_stacks() def test_delete_stack(self): + resolve = 'resolve_outputs=False' self.register_uris([ dict(method='GET', - uri='{endpoint}/stacks/{name}'.format( + uri='{endpoint}/stacks/{name}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - name=self.stack_name), + name=self.stack_name, resolve=resolve), status_code=302, headers=dict( - location='{endpoint}/stacks/{name}/{id}'.format( + location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name))), + id=self.stack_id, name=self.stack_name, + resolve=resolve))), dict(method='GET', - uri='{endpoint}/stacks/{name}/{id}'.format( + uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name), + id=self.stack_id, name=self.stack_name, resolve=resolve), json={"stack": self.stack}), dict(method='DELETE', uri='{endpoint}/stacks/{id}'.format( @@ -136,30 +138,33 @@ class TestStack(base.RequestsMockTestCase): self.assert_calls() def test_delete_stack_not_found(self): + resolve = 'resolve_outputs=False' self.register_uris([ dict(method='GET', - uri='{endpoint}/stacks/stack_name'.format( - endpoint=fakes.ORCHESTRATION_ENDPOINT), + uri='{endpoint}/stacks/stack_name?{resolve}'.format( + endpoint=fakes.ORCHESTRATION_ENDPOINT, resolve=resolve), status_code=404), ]) self.assertFalse(self.cloud.delete_stack('stack_name')) self.assert_calls() def test_delete_stack_exception(self): + resolve = 'resolve_outputs=False' self.register_uris([ dict(method='GET', - uri='{endpoint}/stacks/{id}'.format( + uri='{endpoint}/stacks/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id), + id=self.stack_id, resolve=resolve), status_code=302, headers=dict( - location='{endpoint}/stacks/{name}/{id}'.format( + location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name))), + id=self.stack_id, name=self.stack_name, + resolve=resolve))), dict(method='GET', - uri='{endpoint}/stacks/{name}/{id}'.format( + uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name), + id=self.stack_id, name=self.stack_name, resolve=resolve), json={"stack": self.stack}), dict(method='DELETE', uri='{endpoint}/stacks/{id}'.format( @@ -177,20 +182,23 @@ class TestStack(base.RequestsMockTestCase): self.stack_id, self.stack_name, status='CREATE_COMPLETE') marker_qs = 'marker={e_id}&sort_dir=asc'.format( e_id=marker_event['id']) + resolve = 'resolve_outputs=False' self.register_uris([ dict(method='GET', - uri='{endpoint}/stacks/{id}'.format( + uri='{endpoint}/stacks/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id), + id=self.stack_id, + resolve=resolve), status_code=302, headers=dict( - location='{endpoint}/stacks/{name}/{id}'.format( + location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name))), + id=self.stack_id, name=self.stack_name, + resolve=resolve))), dict(method='GET', - uri='{endpoint}/stacks/{name}/{id}'.format( + uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name), + id=self.stack_id, name=self.stack_name, resolve=resolve), json={"stack": self.stack}), dict(method='GET', uri='{endpoint}/stacks/{id}/events?{qs}'.format( @@ -215,9 +223,9 @@ class TestStack(base.RequestsMockTestCase): status='DELETE_COMPLETE'), ]}), dict(method='GET', - uri='{endpoint}/stacks/{id}'.format( + uri='{endpoint}/stacks/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name), + id=self.stack_id, name=self.stack_name, resolve=resolve), status_code=404), ]) @@ -231,20 +239,22 @@ class TestStack(base.RequestsMockTestCase): self.stack_id, self.stack_name, status='CREATE_COMPLETE') marker_qs = 'marker={e_id}&sort_dir=asc'.format( e_id=marker_event['id']) + resolve = 'resolve_outputs=False' self.register_uris([ dict(method='GET', - uri='{endpoint}/stacks/{id}'.format( + uri='{endpoint}/stacks/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id), + id=self.stack_id, resolve=resolve), status_code=302, headers=dict( - location='{endpoint}/stacks/{name}/{id}'.format( + location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name))), + id=self.stack_id, name=self.stack_name, + resolve=resolve))), dict(method='GET', - uri='{endpoint}/stacks/{name}/{id}'.format( + uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name), + id=self.stack_id, name=self.stack_name, resolve=resolve), json={"stack": self.stack}), dict(method='GET', uri='{endpoint}/stacks/{id}/events?{qs}'.format( @@ -269,18 +279,19 @@ class TestStack(base.RequestsMockTestCase): status='DELETE_COMPLETE'), ]}), dict(method='GET', - uri='{endpoint}/stacks/{id}'.format( + uri='{endpoint}/stacks/{id}?resolve_outputs=False'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, id=self.stack_id, name=self.stack_name), status_code=302, headers=dict( - location='{endpoint}/stacks/{name}/{id}'.format( + location='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name))), + id=self.stack_id, name=self.stack_name, + resolve=resolve))), dict(method='GET', - uri='{endpoint}/stacks/{name}/{id}'.format( + uri='{endpoint}/stacks/{name}/{id}?{resolve}'.format( endpoint=fakes.ORCHESTRATION_ENDPOINT, - id=self.stack_id, name=self.stack_name), + id=self.stack_id, name=self.stack_name, resolve=resolve), json={"stack": failed_stack}), ])