Update heat stack template and outputs
Change-Id: Id8aac0dd907f9db3ec57cfb8efadea01fbce0b5d
This commit is contained in:
parent
4e5132e350
commit
9432aa9faa
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
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}"
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
||||
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=self.template_dirs)
|
||||
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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = heat.heat_template_file(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)
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue