Add support for dynamic parameters allocation in Heat stacks

- dynamically allocate subnet CIDR from configed pool

Change-Id: I8a166cb348ca46b1267b1507e9b2f9a8b95eb2fe
This commit is contained in:
Federico Ressi 2019-06-18 08:25:14 +02:00
parent d9e9bcab86
commit ceb5420cf4
6 changed files with 212 additions and 156 deletions

View File

@ -41,17 +41,28 @@ DELETE_FAILED = 'DELETE_FAILED'
TEMPLATE_FILE_SUFFIX = '.yaml' TEMPLATE_FILE_SUFFIX = '.yaml'
def _stack_parameters(obj, stack=None):
if obj is None or isinstance(obj, collections.Mapping):
parameters = HeatStackParametersFixture(stack, obj)
else:
parameters = tobiko.get_fixture(obj)
if not isinstance(parameters, HeatStackParametersFixture):
msg = "Object {!r} is not an HeatStackParametersFixture".format(
parameters)
raise TypeError(msg)
return parameters
class HeatStackFixture(tobiko.SharedFixture): class HeatStackFixture(tobiko.SharedFixture):
"""Manages Heat stacks.""" """Manages Heat stacks."""
client = None client = None
client_fixture = None
retry_create_stack = 1 retry_create_stack = 1
wait_interval = 5 wait_interval = 5
stack_name = None stack_name = None
template = None template = None
parameters = None
stack = None stack = None
parameters = None
def __init__(self, stack_name=None, template=None, parameters=None, def __init__(self, stack_name=None, template=None, parameters=None,
wait_interval=None, client=None): wait_interval=None, client=None):
@ -60,62 +71,24 @@ class HeatStackFixture(tobiko.SharedFixture):
self.stack_name or self.stack_name or
self.fixture_name) self.fixture_name)
template = template or self.template self.template = _template.heat_template(template or self.template)
if template: self.parameters = _stack_parameters(
if isinstance(template, collections.Mapping): stack=self, obj=(parameters or self.parameters))
template = _template.heat_template(template) self.client = client or self.client
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):
self.client_fixture = client
elif client:
self.client = client
if wait_interval: if wait_interval:
self.wait_interval = wait_interval self.wait_interval = wait_interval
def setup_fixture(self): def setup_fixture(self):
self.setup_template() self.setup_template()
self.setup_parameters()
self.setup_client() self.setup_client()
self.setup_stack() self.setup_stack()
def setup_template(self): def setup_template(self):
tobiko.setup_fixture(self.template) tobiko.setup_fixture(self.template)
def setup_parameters(self):
self.parameters = {}
# Merge all parameters dictionaries in the class hierarchy
for cls in reversed(type(self).__mro__):
parameters = cls.__dict__.get('parameters')
if parameters:
self.parameters.update(parameters)
if self._parameters:
self.parameters.update(self._parameters)
# Add template's missing stack parameters
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): def setup_client(self):
client_fixture = self.client_fixture self.client = _client.heat_client(self.client)
if client_fixture:
self.client = tobiko.setup_fixture(client_fixture).client
elif not self.client:
self.client = _client.get_heat_client()
def setup_stack(self): def setup_stack(self):
self.create_stack() self.create_stack()
@ -159,13 +132,15 @@ class HeatStackFixture(tobiko.SharedFixture):
expected_status={DELETE_COMPLETE}) expected_status={DELETE_COMPLETE})
self.stack = self._outputs = None self.stack = self._outputs = None
# Compile template parameters
parameters = tobiko.reset_fixture(self.parameters).values
try: try:
LOG.debug('Creating stack %r (re-tries left %d)...', LOG.debug('Creating stack %r (re-tries left %d)...',
self.stack_name, retry) self.stack_name, retry)
stack_id = self.client.stacks.create( stack_id = self.client.stacks.create(
stack_name=self.stack_name, stack_name=self.stack_name,
template=self.template.template_yaml, template=self.template.template_yaml,
parameters=self.parameters)['stack']['id'] parameters=parameters)['stack']['id']
except exc.HTTPConflict: except exc.HTTPConflict:
LOG.debug('Stack %r already exists.', self.stack_name) LOG.debug('Stack %r already exists.', self.stack_name)
else: else:
@ -252,80 +227,141 @@ class HeatStackFixture(tobiko.SharedFixture):
def __getattr__(self, name): def __getattr__(self, name):
try: try:
return self.get_stack_outputs().get_output(name) return self.get_stack_outputs().get_value(name)
except InvalidHeatStackOutputKey: except HeatStackOutputKeyError:
pass pass
message = "Object {!r} has no attribute {!r}".format(self, name) message = "Object {!r} has no attribute {!r}".format(self, name)
raise AttributeError(message) raise AttributeError(message)
class HeatStackOutputsFixture(tobiko.SharedFixture): class HeatStackKeyError(tobiko.TobikoException):
message = "key {key!r} not found in stack {name!r}"
class HeatStackParameterKeyError(HeatStackKeyError):
message = "parameter key {key!r} not found in stack {name!r}"
class HeatStackOutputKeyError(HeatStackKeyError):
message = "output key {key!r} not found in stack {name!r}"
class HeatStackNamespaceFixture(tobiko.SharedFixture):
key_error = HeatStackKeyError
_keys = None _keys = None
_values = None _values = None
def __init__(self, stack): def __init__(self, stack):
super(HeatStackOutputsFixture, self).__init__() super(HeatStackNamespaceFixture, self).__init__()
if not isinstance(stack, HeatStackFixture): if not isinstance(stack, HeatStackFixture):
message = "Object {!r} is not an HeatStackFixture".format(stack) message = "Object {!r} is not an HeatStackFixture".format(stack)
raise TypeError(message) raise TypeError(message)
self.stack = stack self.stack = stack
def setup_fixture(self): def setup_fixture(self):
self.get_output_keys() self.setup_keys()
self.get_output_values() self.setup_values()
def get_output_keys(self): def setup_keys(self):
keys = self._keys keys = self._keys
if keys is None: if keys is None:
self._keys = keys = frozenset(tobiko.setup_fixture( self._keys = keys = self.get_keys()
self.stack.template).outputs.keys())
self.addCleanup(self.cleanup_keys) self.addCleanup(self.cleanup_keys)
return keys return keys
keys = property(get_output_keys) keys = tobiko.fixture_property(setup_keys)
def get_keys(self):
raise NotImplementedError
def cleanup_keys(self): def cleanup_keys(self):
del self._keys del self._keys
def get_output_values(self): def setup_values(self):
values = self._values values = self._values
if values is None: if values is None:
# Can't get output values before stack creation is complete self._values = values = self.get_values()
self.stack.wait_for_create_complete() self.addCleanup(self.cleanup_values)
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 return values
def cleanup_output_values(self): values = tobiko.fixture_property(setup_values)
def get_values(self):
raise NotImplementedError
def cleanup_values(self):
del self._values del self._values
values = property(get_output_values) def get_value(self, key):
# Match template outputs definition before getting value
def get_output(self, key): if key in self.keys:
# 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: try:
return self.values[key] return self.values[key]
except KeyError: except KeyError:
LOG.error('Output key %r not found in stack %r outputs', key, LOG.error('Key %r not found in stack %r', key,
self.stack.stack_name) self.stack.stack_name)
raise InvalidHeatStackOutputKey(name=self.stack.stack_name, key=key) else:
LOG.error('Key %r not found in template for stack %r', key,
self.stack.stack_name)
raise self.key_error(name=self.stack.stack_name, key=key)
def set_value(self, key, value):
# Match template outputs definition before setting value
if key in self.keys:
self.values[key] = value
else:
LOG.error('Key %r not found in template for stack %r', key,
self.stack.stack_name)
raise self.key_error(name=self.stack.stack_name, key=key)
def __getattr__(self, name): def __getattr__(self, name):
try: try:
return self.get_output(name) return self.get_value(name)
except InvalidHeatStackOutputKey: except self.key_error:
pass pass
message = "Object {!r} has no attribute {!r}".format(self, name) message = "Object {!r} has no attribute {!r}".format(self, name)
raise AttributeError(message) raise AttributeError(message)
class HeatStackParametersFixture(HeatStackNamespaceFixture):
key_error = HeatStackParameterKeyError
def __init__(self, stack, parameters=None):
super(HeatStackParametersFixture, self).__init__(stack)
self.parameters = parameters and dict(parameters) or {}
def get_keys(self):
template = tobiko.setup_fixture(self.stack.template)
return frozenset(template.parameters or [])
def get_values(self):
values = dict(self.parameters)
missing_keys = sorted(self.keys - set(values))
for key in missing_keys:
value = getattr(self.stack, key, None)
if value is not None:
values[key] = value
return values
class HeatStackOutputsFixture(HeatStackNamespaceFixture):
key_error = HeatStackOutputKeyError
def get_keys(self):
template = tobiko.setup_fixture(self.stack.template)
return frozenset(template.outputs or [])
def get_values(self):
# 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
return {o['output_key']: o['output_value']
for o in outputs}
def check_stack_status(stack, expected): def check_stack_status(stack, expected):
observed = stack.stack_status observed = stack.stack_status
if observed not in expected: if observed not in expected:
@ -341,10 +377,6 @@ def check_stack_status(stack, expected):
status_reason=stack.stack_status_reason) status_reason=stack.stack_status_reason)
class InvalidHeatStackOutputKey(tobiko.TobikoException, AttributeError):
message = "output key {key!r} not found in stack {name!r}"
class HeatStackNotFound(tobiko.TobikoException): class HeatStackNotFound(tobiko.TobikoException):
message = "stack {name!r} not found" message = "stack {name!r} not found"

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
from __future__ import absolute_import from __future__ import absolute_import
import collections
import os import os
import sys import sys
@ -49,7 +50,12 @@ class HeatTemplateFixture(tobiko.SharedFixture):
@property @property
def outputs(self): def outputs(self):
template = self.template template = self.template
return template and template.get('outputs') or {} return template and template.get('outputs') or None
@property
def parameters(self):
template = self.template
return template and template.get('parameters') or None
class HeatTemplateFileFixture(HeatTemplateFixture): class HeatTemplateFileFixture(HeatTemplateFixture):
@ -80,9 +86,17 @@ class HeatTemplateFileFixture(HeatTemplateFixture):
super(HeatTemplateFileFixture, self).setup_template() super(HeatTemplateFileFixture, self).setup_template()
def heat_template(template, template_files=None): def heat_template(obj, template_files=None):
return HeatTemplateFixture(template=template, if isinstance(obj, collections.Mapping):
template_files=template_files) template = HeatTemplateFixture(template=obj,
template_files=template_files)
else:
template = tobiko.get_fixture(obj)
if not isinstance(template, HeatTemplateFixture):
msg = "Object {!r} is not an HeatTemplateFixture".format(template)
raise TypeError(msg)
return template
def heat_template_file(template_file, template_dirs=None): def heat_template_file(template_file, template_dirs=None):

View File

@ -35,7 +35,6 @@ class CIDRGeneratorFixture(tobiko.SharedFixture):
client = None client = None
config = None config = None
cidr_generator = None cidr_generator = None
subnet_cidrs = None
def __init__(self, cidr=None, prefixlen=None, client=None): def __init__(self, cidr=None, prefixlen=None, client=None):
super(CIDRGeneratorFixture, self).__init__() super(CIDRGeneratorFixture, self).__init__()
@ -48,7 +47,7 @@ class CIDRGeneratorFixture(tobiko.SharedFixture):
def setup_fixture(self): def setup_fixture(self):
self.setup_config() self.setup_config()
self.setup_subnet_cidrs() self.setup_client()
self.setup_cidr_generator() self.setup_cidr_generator()
def setup_config(self): def setup_config(self):
@ -56,18 +55,16 @@ class CIDRGeneratorFixture(tobiko.SharedFixture):
CONF = config.CONF CONF = config.CONF
self.config = CONF.tobiko.neutron self.config = CONF.tobiko.neutron
def setup_subnet_cidrs(self): def setup_client(self):
client = _client.neutron_client(self.client) self.client = _client.neutron_client(self.client)
self.subnet_cidrs = set(_client.list_subnet_cidrs(client=client))
def setup_cidr_generator(self): def setup_cidr_generator(self):
cidr = netaddr.IPNetwork(self.cidr) self.cidr_generator = self.cidr.subnet(self.prefixlen)
prefixlen = int(self.prefixlen)
self.cidr_generator = cidr.subnet(prefixlen)
def new_cidr(self): def new_cidr(self):
used_cidrs = set(_client.list_subnet_cidrs(client=self.client))
for cidr in self.cidr_generator: for cidr in self.cidr_generator:
if cidr not in self.subnet_cidrs: if cidr not in used_cidrs:
return cidr return cidr
raise NoSuchCIDRLeft(cidr=self.cidr, prefixlen=self.prefixlen) raise NoSuchCIDRLeft(cidr=self.cidr, prefixlen=self.prefixlen)
@ -76,22 +73,22 @@ class IPv4CIDRGeneratorFixture(CIDRGeneratorFixture):
@property @property
def cidr(self): def cidr(self):
return self.config.ipv4_cidr return netaddr.IPNetwork(self.config.ipv4_cidr)
@property @property
def prefixlen(self): def prefixlen(self):
return self.config.ipv4_prefixlen return int(self.config.ipv4_prefixlen)
class IPv6CIDRGeneratorFixture(CIDRGeneratorFixture): class IPv6CIDRGeneratorFixture(CIDRGeneratorFixture):
@property @property
def cidr(self): def cidr(self):
return self.config.ipv6_cidr return netaddr.IPNetwork(self.config.ipv6_cidr)
@property @property
def prefixlen(self): def prefixlen(self):
return self.config.ipv6_prefixlen return int(self.config.ipv6_prefixlen)
class NoSuchCIDRLeft(tobiko.TobikoException): class NoSuchCIDRLeft(tobiko.TobikoException):

View File

@ -15,6 +15,7 @@
# under the License. # under the License.
from __future__ import absolute_import from __future__ import absolute_import
import netaddr
import tobiko import tobiko
from tobiko import config from tobiko import config
@ -38,21 +39,33 @@ class NetworkStackFixture(heat.HeatStackFixture):
#: Disable port security by default for new network ports #: Disable port security by default for new network ports
port_security_enabled = False port_security_enabled = False
#: Default IPv4 sub-net CIDR
ipv4_cidr = '190.40.2.0/24'
@property @property
def has_ipv4(self): def has_ipv4(self):
"""Whenever to setup IPv4 subnet""" """Whenever to setup IPv4 subnet"""
return bool(self.ipv4_cidr) return bool(CONF.tobiko.neutron.ipv4_cidr)
#: IPv6 sub-net CIDR @property
ipv6_cidr = '2001:db8:1:2::/64' def ipv4_cidr(self):
if self.has_ipv4:
return neutron.new_ipv4_cidr()
else:
return None
@property @property
def has_ipv6(self): def has_ipv6(self):
"""Whenever to setup IPv6 subnet""" """Whenever to setup IPv6 subnet"""
return bool(self.ipv6_cidr) return bool(CONF.tobiko.neutron.ipv4_cidr)
@property
def ipv6_cidr(self):
if self.has_ipv6:
return neutron.new_ipv6_cidr()
else:
return None
@property
def value_specs(self):
return {}
#: Floating IP network where the Neutron floating IPs are created #: Floating IP network where the Neutron floating IPs are created
gateway_network = CONF.tobiko.neutron.floating_network gateway_network = CONF.tobiko.neutron.floating_network
@ -73,10 +86,18 @@ class NetworkStackFixture(heat.HeatStackFixture):
def ipv4_subnet_details(self): def ipv4_subnet_details(self):
return neutron.show_subnet(self.ipv4_subnet_id) return neutron.show_subnet(self.ipv4_subnet_id)
@property
def ipv4_subnet_cidr(self):
return netaddr.IPNetwork(self.ipv4_subnet_details['cidr'])
@property @property
def ipv6_subnet_details(self): def ipv6_subnet_details(self):
return neutron.show_subnet(self.ipv6_subnet_id) return neutron.show_subnet(self.ipv6_subnet_id)
@property
def ipv6_subnet_cidr(self):
return netaddr.IPNetwork(self.ipv6_subnet_details['cidr'])
@property @property
def gateway_details(self): def gateway_details(self):
return neutron.show_router(self.gateway_id) return neutron.show_router(self.gateway_id)
@ -95,16 +116,11 @@ class NetworkWithNetMtuWriteStackFixture(NetworkStackFixture):
#: Value for maximum transfer unit on the internal network #: Value for maximum transfer unit on the internal network
mtu = 1000 mtu = 1000
def setup_parameters(self): @property
"""Setup Heat template parameters""" def value_specs(self):
super(NetworkWithNetMtuWriteStackFixture, self).setup_parameters() return dict(
if self.mtu: super(NetworkWithNetMtuWriteStackFixture, self).value_specs,
self.setup_net_mtu_writable() mtu=int(self.mtu))
@neutron.skip_if_missing_networking_extensions('net-mtu-writable')
def setup_net_mtu_writable(self):
"""Setup maximum transfer unit size for the network"""
self.parameters.setdefault('value_specs', {}).update(mtu=self.mtu)
@neutron.skip_if_missing_networking_extensions('security-group') @neutron.skip_if_missing_networking_extensions('security-group')

View File

@ -15,9 +15,6 @@
# under the License. # under the License.
from __future__ import absolute_import from __future__ import absolute_import
import unittest
import netaddr
import testtools import testtools
import tobiko import tobiko
@ -45,27 +42,19 @@ class NetworkTestCase(testtools.TestCase):
self.assertEqual(self.stack.network_details['mtu'], self.assertEqual(self.stack.network_details['mtu'],
self.stack.outputs.mtu) self.stack.outputs.mtu)
@unittest.skip('Feature not implemented')
def test_ipv4_subnet_cidr(self): def test_ipv4_subnet_cidr(self):
if not self.stack.has_ipv4: if not self.stack.has_ipv4:
tobiko.skip('Stack {!s} has no ipv4 subnet', self.stack.stack_name) tobiko.skip('Stack {!s} has no ipv4 subnet', self.stack.stack_name)
self.assertEqual(str(self.stack.ipv4_cidr), subnet = neutron.find_subnet(str(self.stack.ipv4_subnet_cidr),
self.stack.ipv4_subnet_details['cidr'])
subnet = neutron.find_subnet(str(self.stack.ipv4_cidr),
properties=['cidr']) properties=['cidr'])
self.assertEqual(neutron.show_subnet(self.stack.ipv4_subnet_id), self.assertEqual(neutron.show_subnet(self.stack.ipv4_subnet_id),
subnet) subnet)
@unittest.skip('Feature not implemented')
def test_ipv6_subnet_cidr(self): def test_ipv6_subnet_cidr(self):
if not self.stack.has_ipv6: if not self.stack.has_ipv6:
tobiko.skip('Stack {!s} has no ipv4 subnet', self.stack.stack_name) tobiko.skip('Stack {!s} has no ipv4 subnet', self.stack.stack_name)
self.assertEqual(str(self.stack.ipv6_cidr), subnet = neutron.find_subnet(str(self.stack.ipv6_subnet_cidr),
self.stack.ipv6_subnet_details['cidr'])
subnet = neutron.find_subnet(str(self.stack.ipv6_cidr),
properties=['cidr']) properties=['cidr'])
self.assertEqual(neutron.show_subnet(self.stack.ipv6_subnet_id), self.assertEqual(neutron.show_subnet(self.stack.ipv6_subnet_id),
subnet) subnet)
@ -74,14 +63,14 @@ class NetworkTestCase(testtools.TestCase):
if not self.stack.has_ipv4 or self.stack.has_gateway: if not self.stack.has_ipv4 or self.stack.has_gateway:
tobiko.skip('Stack {!s} has no IPv4 gateway', tobiko.skip('Stack {!s} has no IPv4 gateway',
self.stack.stack_name) self.stack.stack_name)
self.assertEqual(str(netaddr.IPNetwork(self.stack.ipv4_cidr).ip + 1), self.assertEqual(str(self.stack.ipv4_cidr.ip + 1),
self.stack.ipv4_subnet_details['gateway_ip']) self.stack.ipv4_subnet_details['gateway_ip'])
def test_ipv6_subnet_gateway_ip(self): def test_ipv6_subnet_gateway_ip(self):
if not self.stack.has_ipv6 or self.stack.has_gateway: if not self.stack.has_ipv6 or self.stack.has_gateway:
tobiko.skip('Stack {!s} has no IPv6 gateway', tobiko.skip('Stack {!s} has no IPv6 gateway',
self.stack.stack_name) self.stack.stack_name)
self.assertEqual(str(netaddr.IPNetwork(self.stack.ipv6_cidr).ip + 1), self.assertEqual(str(self.stack.ipv6_cidr.ip + 1),
self.stack.ipv6_subnet_details['gateway_ip']) self.stack.ipv6_subnet_details['gateway_ip'])
def test_gateway_network(self): def test_gateway_network(self):

View File

@ -23,6 +23,7 @@ import yaml
import tobiko import tobiko
from tobiko.openstack import heat from tobiko.openstack import heat
from tobiko.openstack.heat import _stack
from tobiko.openstack import keystone from tobiko.openstack import keystone
from tobiko.tests.unit import openstack from tobiko.tests.unit import openstack
@ -47,8 +48,16 @@ class MyTemplateFixture(heat.HeatTemplateFixture):
template = {'template': 'from-class'} template = {'template': 'from-class'}
class MockClient(mock.NonCallableMagicMock):
pass
class HeatStackFixtureTest(openstack.OpenstackTest): class HeatStackFixtureTest(openstack.OpenstackTest):
def setUp(self):
super(HeatStackFixtureTest, self).setUp()
self.patch(heatclient, 'Client', MockClient)
def test_init(self, fixture_class=MyStack, stack_name=None, def test_init(self, fixture_class=MyStack, stack_name=None,
template=None, parameters=None, wait_interval=None, template=None, parameters=None, wait_interval=None,
client=None): client=None):
@ -62,20 +71,13 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
self.check_stack_template(stack=stack, template=template) self.check_stack_template(stack=stack, template=template)
self.assertIs(fixture_class.parameters, stack.parameters) self.assertIsInstance(stack.parameters,
_stack.HeatStackParametersFixture)
self.assertEqual(parameters or fixture_class.parameters or {},
stack.parameters.parameters)
self.assertEqual(wait_interval or fixture_class.wait_interval, self.assertEqual(wait_interval or fixture_class.wait_interval,
stack.wait_interval) stack.wait_interval)
self.assertIs(client or fixture_class.client, stack.client)
if tobiko.is_fixture(client):
self.assertIsNone(stack.client)
self.assertIs(client, stack.client_fixture)
elif client:
self.assertIs(client, stack.client)
self.assertIsNone(stack.client_fixture)
else:
self.assertIsNone(stack.client)
self.assertIsNone(stack.client_fixture)
def test_init_with_stack_name(self): def test_init_with_stack_name(self):
self.test_init(stack_name='my-stack-name') self.test_init(stack_name='my-stack-name')
@ -112,12 +114,7 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
stack_name=None, parameters=None, wait_interval=None, stack_name=None, parameters=None, wait_interval=None,
stacks=None, create_conflict=False, call_create=True, stacks=None, create_conflict=False, call_create=True,
call_delete=False, call_sleep=False): call_delete=False, call_sleep=False):
from tobiko.openstack.heat import _client client = MockClient()
client = mock.MagicMock(specs=heatclient.Client)
get_heat_client = self.patch(_client, 'get_heat_client',
return_value=client)
stacks = stacks or [ stacks = stacks or [
exc.HTTPNotFound, exc.HTTPNotFound,
mock_stack('CREATE_IN_PROGRESS')] mock_stack('CREATE_IN_PROGRESS')]
@ -131,14 +128,14 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
sleep = self.patch(time, 'sleep') sleep = self.patch(time, 'sleep')
stack = fixture_class(stack_name=stack_name, parameters=parameters, stack = fixture_class(stack_name=stack_name, parameters=parameters,
template=template, wait_interval=wait_interval) template=template, wait_interval=wait_interval,
client=client)
stack.setUp() stack.setUp()
self.assertIs(client, stack.client) self.assertIs(client, stack.client)
self.assertEqual(wait_interval or fixture_class.wait_interval, self.assertEqual(wait_interval or fixture_class.wait_interval,
stack.wait_interval) stack.wait_interval)
get_heat_client.assert_called_once_with()
client.stacks.get.assert_has_calls([mock.call(stack.stack_name, client.stacks.get.assert_has_calls([mock.call(stack.stack_name,
resolve_outputs=False)]) resolve_outputs=False)])
@ -147,11 +144,11 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
else: else:
client.stacks.delete.assert_not_called() client.stacks.delete.assert_not_called()
self.assertEqual(parameters or fixture_class.parameters or {}, parameters = parameters or fixture_class.parameters or {}
stack.parameters) self.assertEqual(parameters, stack.parameters.values)
if call_create: if call_create:
client.stacks.create.assert_called_once_with( client.stacks.create.assert_called_once_with(
parameters=stack.parameters, stack_name=stack.stack_name, parameters=parameters, stack_name=stack.stack_name,
template=yaml.safe_dump(stack.template.template)) template=yaml.safe_dump(stack.template.template))
else: else:
client.stacks.create.assert_not_called() client.stacks.create.assert_not_called()
@ -169,8 +166,8 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
self.test_setup(template={'other': 'template'}) self.test_setup(template={'other': 'template'})
def test_setup_with_template_fixture(self): def test_setup_with_template_fixture(self):
self.test_setup(template=heat.heat_template(template={'template': self.test_setup(template=heat.heat_template({'template':
'from-fixture'})) 'from-fixture'}))
def test_setup_with_template_fixture_type(self): def test_setup_with_template_fixture_type(self):
self.test_setup(template=MyTemplateFixture) self.test_setup(template=MyTemplateFixture)
@ -242,7 +239,7 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
self.test_setup(create_conflict=True) self.test_setup(create_conflict=True)
def test_cleanup(self): def test_cleanup(self):
client = mock.MagicMock(specs=heatclient.Client) client = MockClient()
stack = MyStack(client=client) stack = MyStack(client=client)
stack.cleanUp() stack.cleanUp()
client.stacks.delete.assert_called_once_with(stack.stack_name) client.stacks.delete.assert_called_once_with(stack.stack_name)
@ -253,7 +250,7 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
'output_value': 'value1'}, 'output_value': 'value1'},
{'output_key': 'key2', {'output_key': 'key2',
'output_value': 'value2'}]) 'output_value': 'value2'}])
client = mock.MagicMock(specs=heatclient.Client) client = MockClient()
client.stacks.get.return_value = stack client.stacks.get.return_value = stack
stack_fixture = MyStack( stack_fixture = MyStack(
template={'outputs': {'key1': {}, 'key2': {}}}, template={'outputs': {'key1': {}, 'key2': {}}},
@ -264,6 +261,17 @@ class HeatStackFixtureTest(openstack.OpenstackTest):
self.assertEqual('value1', outputs.key1) self.assertEqual('value1', outputs.key1)
self.assertEqual('value2', outputs.key2) self.assertEqual('value2', outputs.key2)
def test_parameters(self):
stack_fixture = MyStack(
template={'parameters': {'key1': {}, 'key2': {}}},
parameters={'key1': 'value1',
'key2': 'value2'})
parameters = stack_fixture.parameters
self.assertEqual('value1', parameters.key1)
self.assertEqual('value2', parameters.key2)
def check_stack_template(self, stack, template): def check_stack_template(self, stack, template):
expected_template = template or type(stack).template expected_template = template or type(stack).template
if tobiko.is_fixture(expected_template): if tobiko.is_fixture(expected_template):