Add "parameter_defaults" to the environment

This give the user a way to set defaults recursively down nested stacks
without having to create the parameter in every template (it's ignored
if the template does not have the parameter).

blueprint env-nested-usability
Change-Id: Ie6b4481417204a527d322fd532c341b9acbce473
This commit is contained in:
Angus Salkeld 2014-11-24 12:38:30 +10:00
parent e1540f834c
commit 08431c7c06
21 changed files with 269 additions and 71 deletions

View File

@ -29,8 +29,8 @@ what plug-ins the cloud operator has installed.
Format
------
It is a yaml text file with two main sections "resource_registry" and
"parameters".
It is a yaml text file with three main sections "resource_registry",
"parameters" and "parameter_defaults".
------------------
Command line usage
@ -81,8 +81,18 @@ Usage examples
InstanceType: m1.micro
ImageId: F18-x86_64-cfntools
2) Define defaults to parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is especially useful when you have many template resources and
you want the same value in each. Note: these defaults will get passed
down into all template resources.
::
2) Deal with the mapping of Quantum to Neutron
parameter_defaults:
KeyName: heat_key
3) Deal with the mapping of Quantum to Neutron
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
@ -92,7 +102,7 @@ Usage examples
So all existing resources which can be matched with "OS::Neutron*"
will be mapped to "OS::Quantum*" accordingly.
3) Override a resource type with a custom template resource
4) Override a resource type with a custom template resource
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
@ -103,7 +113,7 @@ Please note that the template resource URL here must end with ".yaml"
or ".template", or it will not be treated as a custom template
resource. The supported URL types are "http, https and file".
4) Always map resource type X to Y
5) Always map resource type X to Y
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
@ -111,7 +121,7 @@ resource. The supported URL types are "http, https and file".
"OS::Networking::FloatingIP": "OS::Nova::FloatingIP"
5) Use default resources except one for a particular resource in the template
6) Use default resources except one for a particular resource in the template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::

View File

@ -15,8 +15,8 @@ from heat.common.i18n import _
from heat.common import template_format
SECTIONS = (PARAMETERS, RESOURCE_REGISTRY) = \
('parameters', 'resource_registry')
SECTIONS = (PARAMETERS, RESOURCE_REGISTRY, PARAMETER_DEFAULTS) = \
('parameters', 'resource_registry', 'parameter_defaults')
def parse(env_str):

View File

@ -69,14 +69,20 @@ class CfnTemplate(template.Template):
# to be consistent with an empty json section.
return self.t.get(section) or default
def param_schemata(self):
def param_schemata(self, param_defaults=None):
params = self.t.get(self.PARAMETERS) or {}
pdefaults = param_defaults or {}
for name, schema in six.iteritems(params):
if name in pdefaults:
params[name][parameters.DEFAULT] = pdefaults[name]
return dict((name, parameters.Schema.from_dict(name, schema))
for name, schema in six.iteritems(params))
def parameters(self, stack_identifier, user_params):
def parameters(self, stack_identifier, user_params, param_defaults=None):
return parameters.Parameters(stack_identifier, self,
user_params=user_params)
user_params=user_params,
param_defaults=param_defaults)
def resource_definitions(self, stack):
def rsrc_defn_item(name, snippet):

View File

@ -121,8 +121,10 @@ class TemplateResourceInfo(ResourceInfo):
def get_class(self):
from heat.engine.resources import template_resource
env = self.registry.environment
return template_resource.generate_class(str(self.name),
self.template_name)
self.template_name,
env)
class MapResourceInfo(ResourceInfo):
@ -156,9 +158,10 @@ class GlobResourceInfo(MapResourceInfo):
class ResourceRegistry(object):
"""By looking at the environment, find the resource implementation."""
def __init__(self, global_registry):
def __init__(self, global_registry, env):
self._registry = {'resources': {}}
self.global_registry = global_registry
self.environment = env
def load(self, json_snippet):
self._load_registry([], json_snippet)
@ -363,20 +366,28 @@ class Environment(object):
else:
global_registry = None
self.registry = ResourceRegistry(global_registry)
self.registry = ResourceRegistry(global_registry, self)
self.registry.load(env.get(env_fmt.RESOURCE_REGISTRY, {}))
if env_fmt.PARAMETER_DEFAULTS in env:
self.param_defaults = env[env_fmt.PARAMETER_DEFAULTS]
else:
self.param_defaults = {}
if env_fmt.PARAMETERS in env:
self.params = env[env_fmt.PARAMETERS]
else:
self.params = dict((k, v) for (k, v) in six.iteritems(env)
if k != env_fmt.RESOURCE_REGISTRY)
if k not in (env_fmt.PARAMETER_DEFAULTS,
env_fmt.RESOURCE_REGISTRY))
self.constraints = {}
self.stack_lifecycle_plugins = []
def load(self, env_snippet):
self.registry.load(env_snippet.get(env_fmt.RESOURCE_REGISTRY, {}))
self.params.update(env_snippet.get(env_fmt.PARAMETERS, {}))
self.param_defaults.update(
env_snippet.get(env_fmt.PARAMETER_DEFAULTS, {}))
def patch_previous_parameters(self, previous_env, clear_parameters=[]):
"""This instance of Environment is the new environment where
@ -394,7 +405,8 @@ class Environment(object):
def user_env_as_dict(self):
"""Get the environment as a dict, ready for storing in the db."""
return {env_fmt.RESOURCE_REGISTRY: self.registry.as_dict(),
env_fmt.PARAMETERS: self.params}
env_fmt.PARAMETERS: self.params,
env_fmt.PARAMETER_DEFAULTS: self.param_defaults}
def register_class(self, resource_type, resource_class):
self.registry.register_class(resource_type, resource_class)
@ -425,22 +437,25 @@ class Environment(object):
return self.stack_lifecycle_plugins
def get_custom_environment(registry, cust_params):
"""Build a customized environment using the given registry and params.
This is built from the cust_params and the given registry so some
def get_child_environment(parent_env, child_params):
"""Build a child environment using the parent environment and params.
This is built from the child_params and the parent env so some
resources can use user-provided parameters as if they come from an
environment.
"""
new_env = Environment()
new_env.registry = registry
cust_env = {env_fmt.PARAMETERS: {}}
if cust_params is not None:
if env_fmt.PARAMETERS not in cust_params:
cust_env[env_fmt.PARAMETERS] = cust_params
new_env.registry = copy.copy(parent_env.registry)
new_env.environment = new_env
child_env = {
env_fmt.PARAMETERS: {},
env_fmt.PARAMETER_DEFAULTS: parent_env.param_defaults}
if child_params is not None:
if env_fmt.PARAMETERS not in child_params:
child_env[env_fmt.PARAMETERS] = child_params
else:
cust_env.update(cust_params)
child_env.update(child_params)
new_env.load(cust_env)
new_env.load(child_env)
return new_env

View File

@ -171,17 +171,21 @@ class HOTemplate20130523(template.Template):
return self._translate_section('outputs', 'value', outputs,
HOT_TO_CFN_ATTRS)
def param_schemata(self):
parameter_section = self.t.get(self.PARAMETERS)
if parameter_section is None:
parameter_section = {}
def param_schemata(self, param_defaults=None):
parameter_section = self.t.get(self.PARAMETERS) or {}
pdefaults = param_defaults or {}
for name, schema in six.iteritems(parameter_section):
if name in pdefaults:
parameter_section[name]['default'] = pdefaults[name]
params = six.iteritems(parameter_section)
return dict((name, parameters.HOTParamSchema.from_dict(name, schema))
for name, schema in params)
def parameters(self, stack_identifier, user_params):
def parameters(self, stack_identifier, user_params, param_defaults=None):
return parameters.HOTParameters(stack_identifier, self,
user_params=user_params)
user_params=user_params,
param_defaults=param_defaults)
def resource_definitions(self, stack):
allowed_keys = set(_RESOURCE_KEYS)

View File

@ -27,7 +27,7 @@ class ParameterGroups(object):
'''
def __init__(self, tmpl):
self.tmpl = tmpl
self.parameters = tmpl.parameters(None, {})
self.parameters = tmpl.parameters(None, {}, param_defaults={})
LOG.debug(self.tmpl)
LOG.debug(self.parameters)
self.parameter_names = []

View File

@ -207,6 +207,7 @@ class Parameter(object):
self.name = name
self.schema = schema
self.user_value = value
self.user_default = None
def validate(self, validate_value=True, context=None):
'''
@ -264,11 +265,15 @@ class Parameter(object):
def has_default(self):
'''Return whether the parameter has a default value.'''
return self.schema.default is not None
return (self.schema.default is not None or
self.user_default is not None)
def default(self):
'''Return the default value of the parameter.'''
return self.schema.default
return self.user_default or self.schema.default
def set_default(self, value):
self.user_default = value
def __str__(self):
'''Return a string representation of the parameter'''
@ -421,12 +426,14 @@ class Parameters(collections.Mapping):
'AWS::StackId', 'AWS::StackName', 'AWS::Region'
)
def __init__(self, stack_identifier, tmpl, user_params=None):
def __init__(self, stack_identifier, tmpl, user_params=None,
param_defaults=None):
'''
Create the parameter container for a stack from the stack name and
template, optionally setting the user-supplied parameter values.
'''
user_params = user_params or {}
param_defaults = param_defaults or {}
def user_parameter(schema_item):
name, schema = schema_item
@ -445,6 +452,10 @@ class Parameters(collections.Mapping):
p) for p in itertools.chain(pseudo_parameters,
user_parameters))
for pd in six.iterkeys(param_defaults):
if pd in self.params:
self.params[pd].set_default(param_defaults[pd])
def validate(self, validate_value=True, context=None):
'''
Validates all parameters.

View File

@ -135,9 +135,8 @@ class RemoteStack(resource.Resource):
raise exception.StackValidationFailed(message=msg)
try:
registry = self.stack.env.registry
params = self.properties[self.PARAMETERS]
env = environment.get_custom_environment(registry, params)
env = environment.get_child_environment(self.stack.env, params)
tmpl = template_format.parse(self.properties[self.TEMPLATE])
args = {
'template': tmpl,
@ -153,9 +152,8 @@ class RemoteStack(resource.Resource):
raise exception.StackValidationFailed(message=msg)
def handle_create(self):
registry = self.stack.env.registry
params = self.properties[self.PARAMETERS]
env = environment.get_custom_environment(registry, params)
env = environment.get_child_environment(self.stack.env, params)
tmpl = template_format.parse(self.properties[self.TEMPLATE])
args = {
'stack_name': self.physical_resource_name_or_FnGetRefId(),
@ -197,8 +195,7 @@ class RemoteStack(resource.Resource):
self.name)
params = self.properties[self.PARAMETERS]
registry = self.stack.env.registry
env = environment.get_custom_environment(registry, params)
env = environment.get_child_environment(self.stack.env, params)
tmpl = template_format.parse(self.properties[self.TEMPLATE])
fields = {
'stack_id': self.resource_id,

View File

@ -27,10 +27,10 @@ from heat.engine import stack_resource
from heat.engine import template
def generate_class(name, template_name):
def generate_class(name, template_name, env):
data = TemplateResource.get_template_file(template_name, ('file',))
tmpl = template.Template(template_format.parse(data))
props, attrs = TemplateResource.get_schemas(tmpl)
props, attrs = TemplateResource.get_schemas(tmpl, env.param_defaults)
cls = type(name, (TemplateResource,),
{'properties_schema': props,
'attributes_schema': attrs})
@ -83,9 +83,9 @@ class TemplateResource(stack_resource.StackResource):
raise exception.NotFound(msg_fmt=msg)
@staticmethod
def get_schemas(tmpl):
def get_schemas(tmpl, param_defaults):
return ((properties.Properties.schema_from_params(
tmpl.param_schemata())),
tmpl.param_schemata(param_defaults))),
(attributes.Attributes.schema_from_outputs(
tmpl[tmpl.OUTPUTS])))
@ -99,7 +99,8 @@ class TemplateResource(stack_resource.StackResource):
{"HeatTemplateFormatVersion": "2012-12-12"})
# re-generate the properties and attributes from the template.
self.properties_schema, self.attributes_schema = self.get_schemas(tmpl)
self.properties_schema, self.attributes_schema = self.get_schemas(
tmpl, self.stack.env.param_defaults)
self.properties = definition.properties(self.properties_schema,
self.context)

View File

@ -131,8 +131,10 @@ class Stack(collections.Mapping):
resources.initialise()
self.env = env or environment.Environment({})
self.parameters = self.t.parameters(self.identifier(),
user_params=self.env.params)
self.parameters = self.t.parameters(
self.identifier(),
user_params=self.env.params,
param_defaults=self.env.param_defaults)
self._set_param_stackid()
if resolve_data:

View File

@ -164,13 +164,12 @@ class StackResource(resource.Resource):
# Note we disable rollback for nested stacks, since they
# should be rolled back by the parent stack on failure
child_env = environment.get_custom_environment(
self.stack.env.registry,
child_params)
child_env = environment.get_child_environment(
self.stack.env, child_params)
nested = parser.Stack(self.context,
stack_name,
parsed_template,
child_env,
env=child_env,
timeout_mins=timeout_mins,
disable_rollback=True,
parent_resource=self,

View File

@ -139,12 +139,12 @@ class Template(collections.Mapping):
return len(self.SECTIONS) - len(self.SECTIONS_NO_DIRECT_ACCESS)
@abc.abstractmethod
def param_schemata(self):
def param_schemata(self, param_defaults=None):
'''Return a dict of parameters.Schema objects for the parameters.'''
pass
@abc.abstractmethod
def parameters(self, stack_identifier, user_params):
def parameters(self, stack_identifier, user_params, param_defaults=None):
'''Return a parameters.Parameters object for the stack.'''
pass

View File

@ -163,6 +163,7 @@ blarg: wibble
def test_parameters(self):
params = {'foo': 'bar', 'blarg': 'wibble'}
body = {'parameters': params,
'parameter_defaults': {},
'resource_registry': {}}
data = stacks.InstantiationData(body)
self.assertEqual(body, data.environment())
@ -178,6 +179,7 @@ blarg: wibble
'environment': {'parameters': {'blarg': 'wibble'}}}
expect = {'parameters': {'blarg': 'wibble',
'foo': 'bar'},
'parameter_defaults': {},
'resource_registry': {}}
data = stacks.InstantiationData(body)
self.assertEqual(expect, data.environment())
@ -192,6 +194,7 @@ blarg: wibble
expect = {'parameters': {'blarg': 'wibble',
'foo': 'bar',
'tester': 'Yes'},
'parameter_defaults': {},
'resource_registry': {}}
data = stacks.InstantiationData(body)
self.assertEqual(expect, data.environment())
@ -206,7 +209,8 @@ blarg: wibble
env = {'foo': 'bar', 'blarg': 'wibble'}
body = {'not the environment': env}
data = stacks.InstantiationData(body)
self.assertEqual({'parameters': {}, 'resource_registry': {}},
self.assertEqual({'parameters': {}, 'parameter_defaults': {},
'resource_registry': {}},
data.environment())
def test_args(self):
@ -710,6 +714,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': identity.stack_name,
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30},
@ -770,6 +775,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': identity.stack_name,
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30,
@ -835,6 +841,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': identity.stack_name,
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {'my.yaml': 'This is the file contents.'},
'args': {'timeout_mins': 30},
@ -877,6 +884,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30},
@ -892,6 +900,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30},
@ -907,6 +916,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30},
@ -959,6 +969,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30},
@ -1017,6 +1028,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_name': stack_name,
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30},
@ -1415,6 +1427,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30}})
@ -1450,6 +1463,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {u'parameters': parameters,
u'parameter_defaults': {},
u'resource_registry': {}},
'files': {},
'args': {'timeout_mins': 30}})
@ -1509,6 +1523,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': {},
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {rpc_api.PARAM_EXISTING: True,
@ -1544,6 +1559,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {rpc_api.PARAM_EXISTING: True,
@ -1581,6 +1597,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': {},
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {rpc_api.PARAM_EXISTING: True,
@ -1620,6 +1637,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
{'stack_identity': dict(identity),
'template': template,
'params': {'parameters': parameters,
'parameter_defaults': {},
'resource_registry': {}},
'files': {},
'args': {rpc_api.PARAM_EXISTING: True,
@ -1757,6 +1775,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
('validate_template',
{'template': template,
'params': {'parameters': {},
'parameter_defaults': {},
'resource_registry': {}}})
).AndReturn(engine_response)
self.m.ReplayAll()
@ -1780,6 +1799,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
('validate_template',
{'template': template,
'params': {'parameters': {},
'parameter_defaults': {},
'resource_registry': {}}})
).AndReturn({'Error': 'fubar'})
self.m.ReplayAll()

View File

@ -2286,7 +2286,8 @@ class StackServiceTest(common.HeatTestCase):
self.assertEqual(expected, schema)
def _no_template_file(self, function):
info = environment.ResourceInfo(environment.ResourceRegistry,
env = environment.Environment()
info = environment.ResourceInfo(env.registry,
['ResourceWithWrongRefOnFile'],
'not_existing.yaml')
mock_iterable = mock.MagicMock(return_value=iter([info]))

View File

@ -36,12 +36,14 @@ class EnvironmentTest(common.HeatTestCase):
def test_load_old_parameters(self):
old = {u'a': u'ff', u'b': u'ss'}
expected = {u'parameters': old,
u'parameter_defaults': {},
u'resource_registry': {u'resources': {}}}
env = environment.Environment(old)
self.assertEqual(expected, env.user_env_as_dict())
def test_load_new_env(self):
new_env = {u'parameters': {u'a': u'ff', u'b': u'ss'},
u'parameter_defaults': {u'ff': 'new_def'},
u'resource_registry': {u'OS::Food': u'fruity.yaml',
u'resources': {}}}
env = environment.Environment(new_env)
@ -52,6 +54,7 @@ class EnvironmentTest(common.HeatTestCase):
prev_params = {'foo': 'bar', 'tester': 'Yes'}
params = {}
expected = {'parameters': prev_params,
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
prev_env = environment.Environment(
{'parameters': prev_params,
@ -65,6 +68,7 @@ class EnvironmentTest(common.HeatTestCase):
prev_params = {'foo': 'bar', 'tester': 'Yes'}
params = {'tester': 'patched'}
expected = {'parameters': {'foo': 'bar', 'tester': 'patched'},
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
prev_env = environment.Environment(
{'parameters': prev_params,
@ -79,6 +83,7 @@ class EnvironmentTest(common.HeatTestCase):
'another_tester': 'Yes'}
params = {'tester': 'patched'}
expected = {'parameters': {'foo': 'bar', 'tester': 'patched'},
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
prev_env = environment.Environment(
{'parameters': prev_params,
@ -92,6 +97,7 @@ class EnvironmentTest(common.HeatTestCase):
prev_params = {'foo': 'bar', 'tester': 'Yes'}
params = {}
expected = {'parameters': {'foo': 'bar'},
'parameter_defaults': {},
'resource_registry': {'resources': {}}}
prev_env = environment.Environment(
{'parameters': prev_params,

View File

@ -24,6 +24,7 @@ class YamlEnvironmentTest(common.HeatTestCase):
yaml1 = ''
yaml2 = '''
parameters: {}
parameter_defaults: {}
resource_registry: {}
'''
tpl1 = environment_format.parse(yaml1)

View File

@ -392,13 +392,14 @@ params_schema = json.loads('''{
class ParametersTest(testtools.TestCase):
def new_parameters(self, stack_name, tmpl, user_params=None,
stack_id=None, validate_value=True):
stack_id=None, validate_value=True,
param_defaults=None):
user_params = user_params or {}
tmpl.update({'HeatTemplateFormatVersion': '2012-12-12'})
tmpl = template.Template(tmpl)
params = tmpl.parameters(
identifier.HeatIdentifier('', stack_name, stack_id),
user_params)
user_params, param_defaults=param_defaults)
params.validate(validate_value)
return params
@ -505,6 +506,21 @@ class ParametersTest(testtools.TestCase):
'test',
params)
def test_use_user_default(self):
template = {'Parameters': {'a': {'Type': 'Number', 'Default': '42'}}}
params = self.new_parameters('test_params', template,
param_defaults={'a': '77'})
self.assertEqual(77, params['a'])
def test_dont_use_user_default(self):
template = {'Parameters': {'a': {'Type': 'Number', 'Default': '42'}}}
params = self.new_parameters('test_params', template,
{'a': '111'},
param_defaults={'a': '77'})
self.assertEqual(111, params['a'])
class ParameterSchemaTest(testtools.TestCase):

View File

@ -285,8 +285,8 @@ class RemoteStackTest(tests_common.HeatTestCase):
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual('c8a19429-7fde-47ea-a42f-40045488226c',
rsrc.resource_id)
registry = rsrc.stack.env.registry
env = environment.get_custom_environment(registry, {'name': 'foo'})
env = environment.get_child_environment(rsrc.stack.env,
{'name': 'foo'})
args = {
'stack_name': rsrc.physical_resource_name(),
'template': template_format.parse(remote_template),
@ -520,8 +520,8 @@ class RemoteStackTest(tests_common.HeatTestCase):
self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual('bar', rsrc.properties.get('parameters')['name'])
registry = rsrc.stack.env.registry
env = environment.get_custom_environment(registry, {'name': 'bar'})
env = environment.get_child_environment(rsrc.stack.env,
{'name': 'bar'})
fields = {
'stack_id': rsrc.resource_id,
'template': template_format.parse(remote_template),

View File

@ -210,7 +210,7 @@ class StackResourceTest(common.HeatTestCase):
self.stack = self.parent_resource.nested()
self.assertEqual({"foo": "bar"}, self.stack.t.files)
@mock.patch('heat.engine.environment.get_custom_environment')
@mock.patch('heat.engine.environment.get_child_environment')
@mock.patch.object(stack_resource.parser, 'Stack')
def test_preview_with_implemented_child_resource(self, mock_stack_class,
mock_env_class):
@ -231,14 +231,14 @@ class StackResourceTest(common.HeatTestCase):
parent_resource._validate_nested_resources = validation_mock
result = parent_resource.preview()
mock_env_class.assert_called_once_with(self.parent_stack.env.registry,
mock_env_class.assert_called_once_with(self.parent_stack.env,
params)
self.assertEqual('preview_nested_stack', result)
mock_stack_class.assert_called_once_with(
mock.ANY,
'test_stack-test',
mock.ANY,
'environment',
env='environment',
timeout_mins=None,
disable_rollback=True,
parent_resource=parent_resource,
@ -249,7 +249,7 @@ class StackResourceTest(common.HeatTestCase):
nested_depth=1
)
@mock.patch('heat.engine.environment.get_custom_environment')
@mock.patch('heat.engine.environment.get_child_environment')
@mock.patch.object(stack_resource.parser, 'Stack')
def test_preview_with_implemented_dict_child_resource(self,
mock_stack_class,
@ -271,14 +271,14 @@ class StackResourceTest(common.HeatTestCase):
parent_resource._validate_nested_resources = validation_mock
result = parent_resource.preview()
mock_env_class.assert_called_once_with(self.parent_stack.env.registry,
mock_env_class.assert_called_once_with(self.parent_stack.env,
params)
self.assertEqual('preview_nested_stack', result)
mock_stack_class.assert_called_once_with(
mock.ANY,
'test_stack-test',
mock.ANY,
'environment',
env='environment',
timeout_mins=None,
disable_rollback=True,
parent_resource=parent_resource,
@ -810,7 +810,7 @@ class StackResourceTest(common.HeatTestCase):
environment.Environment().AndReturn(env)
self.m.StubOutWithMock(parser, 'Stack')
parser.Stack(ctx, phy_id, templ, env, timeout_mins=None,
parser.Stack(ctx, phy_id, templ, env=env, timeout_mins=None,
disable_rollback=True,
parent_resource=self.parent_resource,
owner_id=self.parent_stack.id,

View File

@ -17,6 +17,7 @@ import random
import re
import six
import subprocess
import testscenarios
import testtools
import time
@ -63,7 +64,8 @@ def rand_name(name=''):
return randbits
class HeatIntegrationTest(testtools.TestCase):
class HeatIntegrationTest(testscenarios.WithScenarios,
testtools.TestCase):
def setUp(self):
super(HeatIntegrationTest, self).setUp()

View File

@ -0,0 +1,107 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import yaml
from heat_integrationtests.common import test
class DefaultParametersTest(test.HeatIntegrationTest):
template = '''
heat_template_version: 2013-05-23
parameters:
length:
type: string
default: 40
resources:
random1:
type: nested_random.yaml
random2:
type: OS::Heat::RandomString
properties:
length: {get_param: length}
outputs:
random1:
value: {get_attr: [random1, random1_value]}
random2:
value: {get_resource: random2}
'''
nested_template = '''
heat_template_version: 2013-05-23
parameters:
length:
type: string
default: 50
resources:
random1:
type: OS::Heat::RandomString
properties:
length: {get_param: length}
outputs:
random1_value:
value: {get_resource: random1}
'''
scenarios = [
('none', dict(param=None, default=None, temp_def=True,
expect1=50, expect2=40)),
('default', dict(param=None, default=12, temp_def=True,
expect1=12, expect2=12)),
('both', dict(param=15, default=12, temp_def=True,
expect1=12, expect2=15)),
('no_temp_default', dict(param=None, default=12, temp_def=False,
expect1=12, expect2=12)),
]
def setUp(self):
super(DefaultParametersTest, self).setUp()
self.client = self.orchestration_client
def test_defaults(self):
stack_name = self._stack_rand_name()
env = {'parameters': {}, 'parameter_defaults': {}}
if self.param:
env['parameters'] = {'length': self.param}
if self.default:
env['parameter_defaults'] = {'length': self.default}
if not self.temp_def:
# remove the default from the parameter in the nested template.
ntempl = yaml.load(self.nested_template)
del ntempl['parameters']['length']['default']
nested_template = yaml.dump(ntempl)
else:
nested_template = self.nested_template
self.client.stacks.create(
stack_name=stack_name,
template=self.template,
files={'nested_random.yaml': nested_template},
disable_rollback=True,
parameters={},
environment=env
)
self.addCleanup(self.client.stacks.delete, stack_name)
stack = self.client.stacks.get(stack_name)
stack_identifier = '%s/%s' % (stack_name, stack.id)
self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
stack = self.client.stacks.get(stack_name)
for out in stack.outputs:
if out['output_key'] == 'random1':
self.assertEqual(self.expect1, len(out['output_value']))
if out['output_key'] == 'random2':
self.assertEqual(self.expect2, len(out['output_value']))