Update heat stack template and outputs

Change-Id: Id8aac0dd907f9db3ec57cfb8efadea01fbce0b5d
This commit is contained in:
Federico Ressi 2019-06-08 16:45:01 +02:00
parent 4e5132e350
commit 9432aa9faa
5 changed files with 164 additions and 178 deletions

View File

@ -21,8 +21,9 @@ from tobiko.openstack.heat import _stack
get_heat_client = _client.get_heat_client
HeatClientFixture = _client.HeatClientFixture
HeatTemplate = _template.HeatTemplate
get_heat_template = _template.get_heat_template
heat_template = _template.heat_template
heat_template_file = _template.heat_template_file
HeatTemplateFixture = _template.HeatTemplateFixture
HeatTemplateFileFixture = _template.HeatTemplateFileFixture
HeatStackFixture = _stack.HeatStackFixture

View File

@ -39,6 +39,7 @@ DELETE_FAILED = 'DELETE_FAILED'
TEMPLATE_FILE_SUFFIX = '.yaml'
INVALID_OUTPUT_KEY = 'INVALID_OUTPUT_KEY'
class HeatStackFixture(tobiko.SharedFixture):
@ -53,6 +54,7 @@ class HeatStackFixture(tobiko.SharedFixture):
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):
@ -62,15 +64,17 @@ class HeatStackFixture(tobiko.SharedFixture):
self.fixture_name)
template = template or self.template
if tobiko.is_fixture(template):
self.template = None
self.template_fixture = template
elif isinstance(template, collections.Mapping):
self.template = _template.HeatTemplate.from_dict(template)
self.template_fixture = None
elif template:
msg = "Invalid type for template parameter: {!r}".format(template)
raise TypeError(msg)
if template:
if isinstance(template, collections.Mapping):
template = _template.heat_template(template)
else:
template = tobiko.get_fixture(template)
if not isinstance(template, _template.HeatTemplateFixture):
msg = "Object {!r} is not an HeatTemplateFixture".format(
template)
raise TypeError(msg)
self.template = template
self._parameters = parameters
if tobiko.is_fixture(client):
@ -86,16 +90,10 @@ class HeatStackFixture(tobiko.SharedFixture):
self.setup_parameters()
self.setup_client()
self.setup_stack()
self.setup_output_keys()
def setup_template(self):
template_fixture = self.template_fixture
if template_fixture:
self.template = tobiko.setup_fixture(template_fixture).template
elif not self.template:
template_name = self.stack_name.rsplit('.')[-1]
self.template = _template.get_heat_template(
template_file=(template_name + TEMPLATE_FILE_SUFFIX),
template_dirs=[tobiko.get_fixture_dir(self)])
tobiko.setup_fixture(self.template)
def setup_parameters(self):
self.parameters = {}
@ -126,6 +124,11 @@ 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()
@ -170,7 +173,7 @@ class HeatStackFixture(tobiko.SharedFixture):
self.stack_name, retry)
stack_id = self.client.stacks.create(
stack_name=self.stack_name,
template=self.template.yaml,
template=self.template.template_yaml,
parameters=self.parameters)['stack']['id']
except exc.HTTPConflict:
LOG.debug('Stack %r already exists.', self.stack_name)
@ -248,41 +251,62 @@ class HeatStackFixture(tobiko.SharedFixture):
_outputs = None
@property
def outputs(self):
return self.wait_for_outputs()
def get_outputs(self):
stack = self.stack
if not hasattr(stack, 'outputs'):
stack = self.get_stack(resolve_outputs=True)
check_stack_status(stack, {CREATE_COMPLETE})
self._outputs = outputs = HeatStackOutputs(
stack_name=self.stack_name,
outputs={output['output_key']: output['output_value']
for output in stack.outputs})
def _outputs_fixture(self):
outputs = self._outputs
if not outputs:
self._outputs = outputs = HeatStackOutputsFixture(self)
return outputs
def wait_for_outputs(self):
if self._outputs:
return self._outputs
else:
self.wait_for_create_complete()
return self.get_outputs()
outputs = tobiko.fixture_property(_outputs_fixture)
def __getattr__(self, name):
output_keys = self.output_keys
if output_keys and name in self.output_keys:
return self.outputs.get_output(name)
message = "Object {!r} has no attribute {!r}".format(self, name)
raise AttributeError(message)
class HeatStackOutputs(object):
class HeatStackOutputsFixture(tobiko.SharedFixture):
def __init__(self, stack_name, outputs):
self.stack_name = stack_name
self.outputs = outputs
outputs = 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()
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(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)
try:
return self.outputs[key]
except KeyError:
pass
raise InvalidHeatStackOutputKey(name=self.stack.stack_name,
key=key)
def __getattr__(self, name):
try:
return self.outputs[name]
except KeyError:
raise InvalidHeatStackOutputKey(name=self.stack_name,
key=name)
return self.get_output(name)
except InvalidHeatStackOutputKey:
pass
message = "Object {!r} has no attribute {!r}".format(self, name)
raise AttributeError(message)
def check_stack_status(stack, expected):
@ -300,7 +324,7 @@ def check_stack_status(stack, expected):
status_reason=stack.stack_status_reason)
class InvalidHeatStackOutputKey(tobiko.TobikoException):
class InvalidHeatStackOutputKey(tobiko.TobikoException, AttributeError):
message = "output key {key!r} not found in stack {name!r}"

View File

@ -13,7 +13,6 @@
# under the License.
from __future__ import absolute_import
import collections
import os
import sys
@ -28,37 +27,33 @@ TEMPLATE_SUFFIX = '.yaml'
TEMPLATE_DIRS = list(sys.path)
class HeatTemplate(collections.namedtuple('HeatTemplate',
['template', 'file', 'files'])):
class HeatTemplateFixture(tobiko.SharedFixture):
_yaml = None
template = None
template_files = None
template_yaml = None
@classmethod
def from_dict(cls, template):
return cls(template=template, file=None, files=None)
def __init__(self, template=None, template_files=None):
super(HeatTemplateFixture, self).__init__()
if template:
self.template = template
if template_files:
self.template_files = template_files
@classmethod
def from_file(cls, template_file, template_dirs=None):
if template_dirs or not os.path.isfile(template_file):
template_dirs = template_dirs or TEMPLATE_DIRS
template_file = find_heat_template_file(
template_file=template_file, template_dirs=template_dirs)
files, template = template_utils.get_template_contents(
template_file=template_file)
return cls(file=template_file, files=files, template=template)
def setup_fixture(self):
self.setup_template()
@property
def yaml(self):
if not self._yaml:
self._yaml = yaml.safe_dump(self.template)
return self._yaml
def setup_template(self):
self.template_yaml = yaml.safe_dump(self.template)
class HeatTemplateFileFixture(tobiko.SharedFixture):
class HeatTemplateFileFixture(HeatTemplateFixture):
template_file = None
template_dirs = None
template_files = None
template = None
template_yaml = None
def __init__(self, template_file=None, template_dirs=None):
super(HeatTemplateFileFixture, self).__init__()
@ -67,13 +62,22 @@ class HeatTemplateFileFixture(tobiko.SharedFixture):
if template_dirs:
self.template_dirs = template_dirs
def setup_fixture(self):
self.setup_template()
def setup_template(self):
self.template = HeatTemplate.from_file(
template_file=self.template_file,
template_dirs=self.template_dirs)
if self.template_dirs or not os.path.isfile(self.template_file):
template_dirs = self.template_dirs or TEMPLATE_DIRS
template_file = find_heat_template_file(
template_file=self.template_file,
template_dirs=template_dirs)
template_files, template = template_utils.get_template_contents(
template_file=template_file)
self.template = template
self.template_files = template_files
super(HeatTemplateFileFixture, self).setup_template()
def heat_template(template, template_files=None):
return HeatTemplateFixture(template=template,
template_files=template_files)
def heat_template_file(template_file, template_dirs=None):
@ -81,11 +85,6 @@ def heat_template_file(template_file, template_dirs=None):
template_dirs=template_dirs)
def get_heat_template(template_file, template_dirs=None):
return HeatTemplate.from_file(template_file=template_file,
template_dirs=template_dirs)
def find_heat_template_file(template_file, template_dirs):
for template_dir in template_dirs:
template_path = os.path.join(template_dir, template_file)

View File

@ -14,7 +14,6 @@
from __future__ import absolute_import
import collections
import os
import time
from heatclient.v1 import client as heatclient
@ -29,43 +28,30 @@ from tobiko.tests.unit import openstack
class MyStack(heat.HeatStackFixture):
pass
class MyStackWithStackName(heat.HeatStackFixture):
stack_name = 'stack.name.from.class'
class MyStackWithParameters(heat.HeatStackFixture):
parameters = {'param': 'from-class'}
class MyStackWithTemplate(heat.HeatStackFixture):
template = {'template': 'from-class'}
class MyStackWithWaitInterval(heat.HeatStackFixture):
class MyStackWithStackName(MyStack):
stack_name = 'stack.name.from.class'
class MyStackWithParameters(MyStack):
parameters = {'param': 'from-class'}
class MyStackWithWaitInterval(MyStack):
wait_interval = 10
class MyTemplateFixture(tobiko.SharedFixture):
_template = {'template': 'from-class'}
template = None
def __init__(self, template=None):
super(MyTemplateFixture, self).__init__()
if template:
self._template = template
def setup_fixture(self):
self.template = heat.HeatTemplate.from_dict(self._template)
class MyTemplateFixture(heat.HeatTemplateFixture):
template = {'template': 'from-class'}
class HeatStackFixtureTest(openstack.OpenstackTest):
def test_init(self, fixture_class=MyStack, stack_name=None, template=None,
parameters=None, wait_interval=None, client=None):
def test_init(self, fixture_class=MyStack, stack_name=None,
template=None, parameters=None, wait_interval=None,
client=None):
stack = fixture_class(stack_name=stack_name, template=template,
parameters=parameters,
wait_interval=wait_interval, client=client)
@ -74,20 +60,7 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
tobiko.get_fixture_name(stack),
stack.stack_name)
if tobiko.is_fixture(template):
self.assertIsNone(stack.template)
self.assertIs(template, stack.template_fixture)
elif isinstance(template, collections.Mapping):
self.assertEqual(
heat.HeatTemplate.from_dict(template=template),
stack.template)
self.assertIsNone(stack.template_fixture)
elif template:
self.assertIs(template, stack.template)
self.assertIsNone(stack.template_fixture)
else:
self.assertIsNone(stack.template)
self.assertIsNone(stack.template_fixture)
self.check_stack_template(stack=stack, template=template)
self.assertIs(fixture_class.parameters, stack.parameters)
@ -120,7 +93,7 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
self.test_init(parameters={'my': 'value'})
def test_init_with_parameters_from_class(self):
self.test_init(fixture_class=MyStackWithParameters)
self.test_init(fixture_class=MyStackWithParameters, )
def test_init_with_wait_interval(self):
self.test_init(wait_interval=20)
@ -135,12 +108,11 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
def test_init_with_client_fixture(self):
self.test_init(client=heat.HeatClientFixture())
def test_setup(self, fixture_class=MyStack, stack_name=None,
template=None, parameters=None, wait_interval=None,
stacks=None, create_conflict=False,
call_create=True, call_delete=False, call_sleep=False):
def test_setup(self, fixture_class=MyStack, template=None,
stack_name=None, parameters=None, wait_interval=None,
stacks=None, create_conflict=False, call_create=True,
call_delete=False, call_sleep=False):
from tobiko.openstack.heat import _client
from tobiko.openstack.heat import _template
client = mock.MagicMock(specs=heatclient.Client)
get_heat_client = self.patch(_client, 'get_heat_client',
@ -161,11 +133,6 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
stack = fixture_class(stack_name=stack_name, parameters=parameters,
template=template, wait_interval=wait_interval)
default_template = heat.HeatTemplate.from_dict(
{'default': 'template'})
get_heat_template = self.patch(_template, 'get_heat_template',
return_value=default_template)
stack.setUp()
self.assertIs(client, stack.client)
@ -192,32 +159,6 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
if call_sleep:
sleep.assert_has_calls([mock.call(stack.wait_interval)])
if tobiko.is_fixture(template):
self.assertIs(tobiko.get_fixture(template).template,
stack.template)
elif isinstance(template, collections.Mapping):
self.assertEqual(heat.HeatTemplate.from_dict(template),
stack.template)
elif isinstance(template, heat.HeatTemplate):
self.assertIs(template, stack.template)
elif not template:
if fixture_class.template:
self.assertEqual(
heat.HeatTemplate.from_dict(fixture_class.template),
stack.template)
else:
self.assertEqual(default_template, stack.template)
else:
self.fail("Unsupported template type: {!r}".format(template))
if template or fixture_class.template:
get_heat_template.assert_not_called()
else:
template_file_name = stack.stack_name.rsplit('.', 1)[-1] + '.yaml'
get_heat_template.assert_called_once_with(
template_file=template_file_name,
template_dirs=[os.path.dirname(__file__)])
def test_setup_with_stack_name(self):
self.test_setup(stack_name='my-stack-name')
@ -227,11 +168,8 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
def test_setup_with_template(self):
self.test_setup(template={'other': 'template'})
def test_setup_with_template_from_class(self):
self.test_setup(fixture_class=MyStackWithTemplate)
def test_setup_with_template_fixture(self):
self.test_setup(template=MyTemplateFixture(template={'template':
self.test_setup(template=heat.heat_template(template={'template':
'from-fixture'}))
def test_setup_with_template_fixture_type(self):
@ -309,7 +247,7 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
stack.cleanUp()
client.stacks.delete.assert_called_once_with(stack.stack_name)
def test_get_outputs(self):
def test_outputs(self):
stack = mock_stack(status='CREATE_COMPLETE',
outputs=[{'output_key': 'key1',
'output_value': 'value1'},
@ -317,15 +255,26 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
'output_value': 'value2'}])
client = mock.MagicMock(specs=heatclient.Client)
client.stacks.get.return_value = stack
stack_fixture = MyStack(client=client)
stack_fixture = MyStack(
template={'outputs': {'key1': {}, 'key2': {}}},
client=client)
outputs = stack_fixture.get_outputs()
outputs = stack_fixture.outputs
client.stacks.get.assert_called_once_with(stack_fixture.stack_name,
resolve_outputs=True)
self.assertEqual('value1', outputs.key1)
self.assertEqual('value2', outputs.key2)
def check_stack_template(self, stack, template):
expected_template = template or type(stack).template
if tobiko.is_fixture(expected_template):
self.assertIs(expected_template, stack.template)
elif isinstance(expected_template, collections.Mapping):
self.assertEqual(expected_template, stack.template.template)
else:
message = "Unsupported template type: {!r}".format(
expected_template)
self.fail(message)
def mock_stack(status, stack_id='<stack-id>', outputs=None):
return mock.MagicMock(stack_status=status,

View File

@ -15,22 +15,35 @@ from __future__ import absolute_import
import os
from heatclient.common import template_utils
import yaml
import tobiko
from tobiko.openstack import heat
from tobiko.tests.unit import openstack
class GetHeatTemplateTest(openstack.OpenstackTest):
class HeatTemplateFileTest(openstack.OpenstackTest):
template_dirs = [os.path.dirname(__file__)]
template_file = 'my-stack.yaml'
def test_get_template(self, template_file=None, template_dirs=None):
def test_heat_template_file(self, template_file=None, template_dirs=None):
template_file = template_file or self.template_file
template_dirs = template_dirs or self.template_dirs
template = heat.get_heat_template(template_file=template_file,
template_dirs=template_dirs)
self.assertIsInstance(template, heat.HeatTemplate)
self.assertIsInstance(template.template, dict)
self.assertEqual(
os.path.join(os.path.dirname(__file__), template_file),
template.file)
template = heat.heat_template_file(template_file=template_file,
template_dirs=template_dirs)
self.assertIsInstance(template, heat.HeatTemplateFileFixture)
self.assertEqual(template_file, template.template_file)
self.assertIsNone(template.template)
self.assertIsNone(template.template_files)
self.assertIsNone(template.template_yaml)
tobiko.setup_fixture(template)
template_files, template_dict = template_utils.get_template_contents(
template_file=os.path.join(os.path.dirname(__file__),
template_file))
self.assertEqual(template_dict, template.template)
self.assertEqual(template_files, template.template_files)
template_yaml = yaml.safe_dump(template_dict)
self.assertEqual(template_yaml, template.template_yaml)