From 9b5f1ccf67f833fa95532b079fa1ab38147c0fbf Mon Sep 17 00:00:00 2001 From: Federico Ressi Date: Mon, 10 Jun 2019 09:24:46 +0200 Subject: [PATCH] Optimize getting stack outputs Change-Id: I46744520c4974ebe11f9c66b602174b431d2f19b --- tobiko/openstack/heat/_stack.py | 89 +++++++++++-------- tobiko/openstack/heat/_template.py | 5 ++ .../tests/functional/openstack/test_stacks.py | 2 +- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/tobiko/openstack/heat/_stack.py b/tobiko/openstack/heat/_stack.py index 10597fc63..25de57c68 100644 --- a/tobiko/openstack/heat/_stack.py +++ b/tobiko/openstack/heat/_stack.py @@ -39,7 +39,6 @@ DELETE_FAILED = 'DELETE_FAILED' TEMPLATE_FILE_SUFFIX = '.yaml' -INVALID_OUTPUT_KEY = 'INVALID_OUTPUT_KEY' class HeatStackFixture(tobiko.SharedFixture): @@ -51,10 +50,8 @@ class HeatStackFixture(tobiko.SharedFixture): wait_interval = 5 stack_name = None template = None - template_fixture = None parameters = None stack = None - output_keys = None def __init__(self, stack_name=None, template=None, parameters=None, wait_interval=None, client=None): @@ -90,7 +87,6 @@ class HeatStackFixture(tobiko.SharedFixture): self.setup_parameters() self.setup_client() self.setup_stack() - self.setup_output_keys() def setup_template(self): tobiko.setup_fixture(self.template) @@ -107,12 +103,12 @@ class HeatStackFixture(tobiko.SharedFixture): self.parameters.update(self._parameters) # Add template's missing stack parameters - if 'parameters' in self.template.template: - for name in self.template.template['parameters']: - if name not in self.parameters: - value = getattr(self, name, None) - if value is not None: - self.parameters[name] = value + template_parameters = set(self.template.template.get('parameters', [])) + missing_parameters = sorted(template_parameters - set(self.parameters)) + for name in missing_parameters: + value = getattr(self, name, None) + if value is not None: + self.parameters[name] = value def setup_client(self): client_fixture = self.client_fixture @@ -124,11 +120,6 @@ class HeatStackFixture(tobiko.SharedFixture): def setup_stack(self): self.create_stack() - def setup_output_keys(self): - template_outputs = self.template.template.get('outputs', None) - if template_outputs: - self.output_keys = set(template_outputs.keys()) - def create_stack(self, retry=None): """Creates stack based on passed parameters.""" created_stack_ids = set() @@ -251,54 +242,80 @@ class HeatStackFixture(tobiko.SharedFixture): _outputs = None - def _outputs_fixture(self): + def get_stack_outputs(self): outputs = self._outputs if not outputs: self._outputs = outputs = HeatStackOutputsFixture(self) return outputs - outputs = tobiko.fixture_property(_outputs_fixture) + outputs = tobiko.fixture_property(get_stack_outputs) def __getattr__(self, name): - output_keys = self.output_keys - if output_keys and name in self.output_keys: - return self.outputs.get_output(name) + try: + return self.get_stack_outputs().get_output(name) + except InvalidHeatStackOutputKey: + pass message = "Object {!r} has no attribute {!r}".format(self, name) raise AttributeError(message) class HeatStackOutputsFixture(tobiko.SharedFixture): - outputs = None + _keys = None + _values = None def __init__(self, stack): super(HeatStackOutputsFixture, self).__init__() - stack = tobiko.get_fixture(stack) if not isinstance(stack, HeatStackFixture): message = "Object {!r} is not an HeatStackFixture".format(stack) raise TypeError(message) self.stack = stack def setup_fixture(self): - self.setup_outputs() + self.get_output_keys() + self.get_output_values() - def setup_outputs(self): - tobiko.setup_fixture(self.stack).wait_for_create_complete() - outputs = self.stack.get_stack(resolve_outputs=True).outputs - self.outputs = {output['output_key']: output['output_value'] - for output in outputs} + def get_output_keys(self): + keys = self._keys + if keys is None: + self._keys = keys = frozenset(tobiko.setup_fixture( + self.stack.template).outputs.keys()) + self.addCleanup(self.cleanup_keys) + return keys + + keys = property(get_output_keys) + + def cleanup_keys(self): + del self._keys + + def get_output_values(self): + values = self._values + if values is None: + # Can't get output values before stack creation is complete + self.stack.wait_for_create_complete() + outputs = self.stack.get_stack(resolve_outputs=True).outputs + self._values = values = {o['output_key']: o['output_value'] + for o in outputs} + self.addCleanup(self.cleanup_output_values) + return values + + def cleanup_output_values(self): + del self._values + + values = property(get_output_values) def get_output(self, key): - # Check template definition before setting up the whole stack - template = tobiko.setup_fixture(self.stack.template).template - if key in template.get('outputs', {}): - tobiko.setup_fixture(self) + # Match template outputs definition before fetching getting values + if key not in self.keys: + LOG.error('Output key %r not found in stack %r template', key, + self.stack.stack_name) + else: try: - return self.outputs[key] + return self.values[key] except KeyError: - pass - raise InvalidHeatStackOutputKey(name=self.stack.stack_name, - key=key) + LOG.error('Output key %r not found in stack %r outputs', key, + self.stack.stack_name) + raise InvalidHeatStackOutputKey(name=self.stack.stack_name, key=key) def __getattr__(self, name): try: diff --git a/tobiko/openstack/heat/_template.py b/tobiko/openstack/heat/_template.py index edd0d3635..2922f4e45 100644 --- a/tobiko/openstack/heat/_template.py +++ b/tobiko/openstack/heat/_template.py @@ -46,6 +46,11 @@ class HeatTemplateFixture(tobiko.SharedFixture): def setup_template(self): self.template_yaml = yaml.safe_dump(self.template) + @property + def outputs(self): + template = self.template + return template and template.get('outputs') or {} + class HeatTemplateFileFixture(HeatTemplateFixture): diff --git a/tobiko/tests/functional/openstack/test_stacks.py b/tobiko/tests/functional/openstack/test_stacks.py index 2b20d88a3..93733c534 100644 --- a/tobiko/tests/functional/openstack/test_stacks.py +++ b/tobiko/tests/functional/openstack/test_stacks.py @@ -52,4 +52,4 @@ class FloatingIpServerTest(testtools.TestCase): """Test that hostname of instance server matches Nova server name""" result = sh.execute('hostname', ssh_client=self.ssh_client) hostname, = str(result.stdout).splitlines() - self.assertEqual(hostname, self.server_name) + self.assertEqual(hostname, self.stack.server_name)