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 Format
------ ------
It is a yaml text file with two main sections "resource_registry" and It is a yaml text file with three main sections "resource_registry",
"parameters". "parameters" and "parameter_defaults".
------------------ ------------------
Command line usage Command line usage
@@ -81,8 +81,18 @@ Usage examples
InstanceType: m1.micro InstanceType: m1.micro
ImageId: F18-x86_64-cfntools 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*" So all existing resources which can be matched with "OS::Neutron*"
will be mapped to "OS::Quantum*" accordingly. 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 or ".template", or it will not be treated as a custom template
resource. The supported URL types are "http, https and file". 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" "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 from heat.common import template_format
SECTIONS = (PARAMETERS, RESOURCE_REGISTRY) = \ SECTIONS = (PARAMETERS, RESOURCE_REGISTRY, PARAMETER_DEFAULTS) = \
('parameters', 'resource_registry') ('parameters', 'resource_registry', 'parameter_defaults')
def parse(env_str): def parse(env_str):

View File

@@ -69,14 +69,20 @@ class CfnTemplate(template.Template):
# to be consistent with an empty json section. # to be consistent with an empty json section.
return self.t.get(section) or default 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 {} 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)) return dict((name, parameters.Schema.from_dict(name, schema))
for name, schema in six.iteritems(params)) 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, return parameters.Parameters(stack_identifier, self,
user_params=user_params) user_params=user_params,
param_defaults=param_defaults)
def resource_definitions(self, stack): def resource_definitions(self, stack):
def rsrc_defn_item(name, snippet): def rsrc_defn_item(name, snippet):

View File

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

View File

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

View File

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

View File

@@ -207,6 +207,7 @@ class Parameter(object):
self.name = name self.name = name
self.schema = schema self.schema = schema
self.user_value = value self.user_value = value
self.user_default = None
def validate(self, validate_value=True, context=None): def validate(self, validate_value=True, context=None):
''' '''
@@ -264,11 +265,15 @@ class Parameter(object):
def has_default(self): def has_default(self):
'''Return whether the parameter has a default value.''' '''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): def default(self):
'''Return the default value of the parameter.''' '''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): def __str__(self):
'''Return a string representation of the parameter''' '''Return a string representation of the parameter'''
@@ -421,12 +426,14 @@ class Parameters(collections.Mapping):
'AWS::StackId', 'AWS::StackName', 'AWS::Region' '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 Create the parameter container for a stack from the stack name and
template, optionally setting the user-supplied parameter values. template, optionally setting the user-supplied parameter values.
''' '''
user_params = user_params or {} user_params = user_params or {}
param_defaults = param_defaults or {}
def user_parameter(schema_item): def user_parameter(schema_item):
name, schema = schema_item name, schema = schema_item
@@ -445,6 +452,10 @@ class Parameters(collections.Mapping):
p) for p in itertools.chain(pseudo_parameters, p) for p in itertools.chain(pseudo_parameters,
user_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): def validate(self, validate_value=True, context=None):
''' '''
Validates all parameters. Validates all parameters.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -392,13 +392,14 @@ params_schema = json.loads('''{
class ParametersTest(testtools.TestCase): class ParametersTest(testtools.TestCase):
def new_parameters(self, stack_name, tmpl, user_params=None, 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 {} user_params = user_params or {}
tmpl.update({'HeatTemplateFormatVersion': '2012-12-12'}) tmpl.update({'HeatTemplateFormatVersion': '2012-12-12'})
tmpl = template.Template(tmpl) tmpl = template.Template(tmpl)
params = tmpl.parameters( params = tmpl.parameters(
identifier.HeatIdentifier('', stack_name, stack_id), identifier.HeatIdentifier('', stack_name, stack_id),
user_params) user_params, param_defaults=param_defaults)
params.validate(validate_value) params.validate(validate_value)
return params return params
@@ -505,6 +506,21 @@ class ParametersTest(testtools.TestCase):
'test', 'test',
params) 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): 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((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual('c8a19429-7fde-47ea-a42f-40045488226c', self.assertEqual('c8a19429-7fde-47ea-a42f-40045488226c',
rsrc.resource_id) rsrc.resource_id)
registry = rsrc.stack.env.registry env = environment.get_child_environment(rsrc.stack.env,
env = environment.get_custom_environment(registry, {'name': 'foo'}) {'name': 'foo'})
args = { args = {
'stack_name': rsrc.physical_resource_name(), 'stack_name': rsrc.physical_resource_name(),
'template': template_format.parse(remote_template), '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((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual('bar', rsrc.properties.get('parameters')['name']) self.assertEqual('bar', rsrc.properties.get('parameters')['name'])
registry = rsrc.stack.env.registry env = environment.get_child_environment(rsrc.stack.env,
env = environment.get_custom_environment(registry, {'name': 'bar'}) {'name': 'bar'})
fields = { fields = {
'stack_id': rsrc.resource_id, 'stack_id': rsrc.resource_id,
'template': template_format.parse(remote_template), 'template': template_format.parse(remote_template),

View File

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

View File

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