Added template combination functionality

Adds the domain model for representing the Heat and Tuskar objects, as
well as the functions needed to add/remove templates from a deployment
plan and generate the corresponding HOT templates for the plan. This
code is only focused on dealing with the contents of the Heat
templates; the storage and retrieval of them are going to be handled in
a different layer of Tuskar.

Change-Id: Ibef2858e62aeec83f2cb0b7a9ac64cbed89f2484
Implements: blueprint tripleo-juno-tuskar-plan-rest-api
This commit is contained in:
Jay Dobies 2014-07-14 15:50:13 -04:00
parent f197b62676
commit 2b73047350
12 changed files with 1892 additions and 0 deletions

View File

View File

@ -0,0 +1,162 @@
# -*- encoding: utf-8 -*-
#
# 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.
"""
Functionality for converting Tuskar domain models into their Heat-acceptable
formats.
These functions are written against the HOT specification found at:
http://docs.openstack.org/developer/heat/template_guide/hot_spec.html
"""
import yaml
def compose_template(template):
"""Converts a template object into its HOT template format.
:param template: template object to convert
:type template: tuskar.templates.heat.Template
:return: HOT template
:rtype: str
"""
parameters = _compose_parameters(template)
resources = _compose_resources(template)
outputs = _compose_outputs(template)
template_dict = {
'heat_template_version': template.version,
'parameters': parameters,
'resources': resources,
'outputs': outputs,
}
if template.description is not None:
template_dict['description'] = template.description
content = yaml.dump(template_dict, default_flow_style=False)
return content
def compose_environment(environment):
"""Converts an environment object into its HOT template format.
:param environment: environment object to convert
:type environment: tuskar.templates.heat.Environment
:return: HOT template
:rtype: str
"""
parameters = _compose_environment_parameters(environment)
registry = _compose_resource_registry(environment)
env_dict = {
'parameters': parameters,
'resource_registry': registry
}
content = yaml.dump(env_dict, default_flow_style=False)
return content
def _compose_parameters(template):
parameters = {}
for p in template.parameters:
details = {
'type': p.param_type,
'description': p.description,
'default': p.default,
'label': p.label,
'hidden': p.hidden,
}
details = _strip_missing(details)
if len(p.constraints) > 0:
details['constraints'] = []
for constraint in p.constraints:
constraint_value = {
constraint.constraint_type: constraint.definition
}
if constraint.description is not None:
constraint_value['description'] = constraint.description
details['constraints'].append(constraint_value)
parameters[p.name] = details
return parameters
def _compose_resources(template):
resources = {}
for r in template.resources:
details = {
'type': r.resource_type,
'metadata': r.metadata,
'depends_on': r.depends_on,
'update_policy': r.update_policy,
'deletion_policy': r.deletion_policy,
}
details = _strip_missing(details)
# Properties
if len(r.properties) > 0:
details['properties'] = {}
for p in r.properties:
details['properties'][p.name] = p.value
resources[r.resource_id] = details
return resources
def _compose_outputs(template):
outputs = {}
for o in template.outputs:
details = {
'description': o.description,
'value': o.value,
}
details = _strip_missing(details)
outputs[o.name] = details
return outputs
def _compose_environment_parameters(environment):
params = dict((p.name, p.value) for p in environment.parameters)
return params
def _compose_resource_registry(environment):
reg = dict((e.alias, e.filename) for e in environment.registry_entries)
return reg
def _strip_missing(details):
"""Removes all entries from a dictionary whose value is None. This is used
in this context to remove optional attributes that were added to the
template creation.
:type details: dict
:return: new dictionary with the empty attributes removed
:rtype: dict
"""
return dict((k, v) for k, v in details.items() if v is not None)

448
tuskar/templates/heat.py Normal file
View File

@ -0,0 +1,448 @@
# -*- encoding: utf-8 -*-
#
# 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.
"""
Object representations of the elements of a HOT template.
These objects were created against the HOT specification found at:
http://docs.openstack.org/developer/heat/template_guide/hot_spec.html
"""
from tuskar.templates import namespace as ns_utils
DEFAULT_VERSION = '2013-05-23'
class Template(object):
def __init__(self, version=DEFAULT_VERSION, description=None):
super(Template, self).__init__()
self.version = version
self.description = description
self._parameter_groups = [] # list of ParameterGroup
self._parameters = [] # list of Parameter
self._resources = [] # list of Resource
self._outputs = [] # list of Output
def __str__(self):
msg = 'Template: version=%(ver)s, description=%(desc)s, ' \
'parameter_count=%(param)s, output_count=%(out)s'
data = {
'ver': self.version,
'desc': _safe_strip(self.description),
'param': len(self.parameters),
'out': len(self.outputs)
}
return msg % data
@property
def parameter_groups(self):
return tuple(self._parameter_groups)
@property
def parameters(self):
return tuple(self._parameters)
@property
def resources(self):
return tuple(self._resources)
@property
def outputs(self):
return tuple(self._outputs)
def add_parameter(self, parameter):
"""Adds a parameter to the template.
:type parameter: tuskar.templates.heat.Parameter
"""
self._parameters.append(parameter)
def remove_parameter(self, parameter):
"""Removes a parameter from the template.
:type parameter: tuskar.templates.heat.Parameter
:raise ValueError: if the parameter is not in the template
"""
self._parameters.remove(parameter)
def remove_parameters_by_namespace(self, namespace):
"""Removes all parameters in the given namespace.
:type namespace: str
"""
self._parameters = \
[p for p in self.parameters
if not ns_utils.matches_template_namespace(namespace, p.name)]
def add_parameter_group(self, parameter_group):
"""Adds a parameter group to the template.
:type parameter_group: tuskar.templates.heat.ParameterGroup
"""
self._parameter_groups.append(parameter_group)
def remove_parameter_group(self, parameter_group):
"""Removes a parameter group from the template.
:type parameter_group: tuskar.templates.heat.ParameterGroup
:raise ValueError: if the parameter group is not in the template
"""
self._parameter_groups.remove(parameter_group)
def add_resource(self, resource):
"""Adds a resource to the template.
:type resource: tuskar.templates.heat.Resource
"""
self._resources.append(resource)
def remove_resource(self, resource):
"""Removes a resource from the template.
:type resource: tuskar.templates.heat.Resource
:raise ValueError: if the resource is not in the template
"""
self._resources.remove(resource)
def remove_resource_by_id(self, resource_id):
"""Removes a resource from the template if found.
:type resource_id: str
"""
self._resources = [r for r in self._resources
if r.resource_id != resource_id]
def add_output(self, output):
"""Adds an output to the template.
:type output: tuskar.templates.heat.Output
"""
self._outputs.append(output)
def remove_output(self, output):
"""Removes an output from the template.
:type output: tuskar.templates.heat.Output
:raise ValueError: if the output is not in the template
"""
self._outputs.remove(output)
def remove_outputs_by_namespace(self, namespace):
"""Removes all outputs in the given namespace from the template.
:type namespace: str
"""
self._outputs =\
[o for o in self.outputs
if not ns_utils.matches_template_namespace(namespace, o.name)]
class ParameterGroup(object):
def __init__(self, label, description):
super(ParameterGroup, self).__init__()
self.label = label
self.description = description
self._parameter_names = set()
def __str__(self):
msg = 'ParameterGroup: label=%(label)s, description=%(desc)s ' \
'parameter_names=%(names)s'
data = {
'label': self.label,
'desc': self.description,
'names': ','.join(self.parameter_names),
}
return msg % data
@property
def parameter_names(self):
return tuple(self._parameter_names)
def add_parameter_name(self, name):
"""Adds a parameter to the group.
:type name: str
"""
self._parameter_names.add(name)
def remove_parameter_name(self, name):
"""Removes a parameter from the group if it is present.
:type name: str
"""
self._parameter_names.discard(name)
class Parameter(object):
def __init__(self, name, param_type,
description=None, label=None, default=None, hidden=None):
super(Parameter, self).__init__()
self.name = name
self.param_type = param_type
self.description = description
self.label = label
self.default = default
self.hidden = hidden
self._constraints = []
def __str__(self):
msg = 'Parameter: name=%(name)s, type=%(type)s, ' \
'description=%(desc)s, label=%(label)s, ' \
'default=%(def)s, hidden=%(hidden)s'
data = {
'name': self.name,
'type': self.param_type,
'desc': self.description,
'label': self.label,
'def': self.default,
'hidden': self.hidden,
}
return msg % data
@property
def constraints(self):
return tuple(self._constraints)
def add_constraint(self, constraint):
"""Adds a constraint to the parameter.
:type constraint: tuskar.templates.heat.ParameterConstraint
"""
self._constraints.append(constraint)
def remove_constraint(self, constraint):
"""Removes a constraint from the template.
:type constraint: tuskar.templates.heat.ParameterConstraint
:raise ValueError: if the given constraint isn't in the parameter
"""
self._constraints.remove(constraint)
class ParameterConstraint(object):
def __init__(self, constraint_type, definition, description=None):
super(ParameterConstraint, self).__init__()
self.constraint_type = constraint_type
self.definition = definition
self.description = description
def __str__(self):
msg = 'Constraint: type=%(type)s, definition=%(def)s, ' \
'description=%(desc)s'
data = {
'type': self.constraint_type,
'def': self.definition,
'desc': self.description,
}
return msg % data
class Resource(object):
def __init__(self, resource_id, resource_type,
metadata=None, depends_on=None,
update_policy=None, deletion_policy=None):
super(Resource, self).__init__()
self.resource_id = resource_id
self.resource_type = resource_type
self.metadata = metadata
self.depends_on = depends_on
self.update_policy = update_policy
self.deletion_policy = deletion_policy
self._properties = []
def __str__(self):
msg = 'Resource: id=%(id)s, resource_type=%(type)s'
data = {
'id': self.resource_id,
'type': self.resource_type,
}
return msg % data
@property
def properties(self):
return tuple(self._properties)
def add_property(self, resource_property):
"""Adds a property to the resource.
:type resource_property: tuskar.templates.heat.ResourceProperty
"""
self._properties.append(resource_property)
def remove_property(self, resource_property):
"""Removes a property from the template.
:type resource_property: tuskar.templates.heat.ResourceProperty
:raise ValueError: if the property isn't in the resource
"""
self._properties.remove(resource_property)
class ResourceProperty(object):
def __init__(self, name, value):
super(ResourceProperty, self).__init__()
self.name = name
self.value = value
def __str__(self):
msg = 'ResourceProperty: name=%(name)s, value=%(value)s'
data = {
'name': self.name,
'value': self.value,
}
return msg % data
class Output(object):
def __init__(self, name, value, description=None):
super(Output, self).__init__()
self.name = name
self.value = value
self.description = description
def __str__(self):
msg = 'Output: name=%(name)s, value=%(value)s, description=%(desc)s'
data = {
'name': self.name,
'value': self.value,
'desc': _safe_strip(self.description)
}
return msg % data
class Environment(object):
def __init__(self):
super(Environment, self).__init__()
self._parameters = []
self._registry_entries = []
def __str__(self):
msg = 'Environment: parameter_count=%(p_count)s, ' \
'registry_count=%(r_count)s'
data = {
'p_count': len(self.parameters),
'r_count': len(self.registry_entries),
}
return msg % data
@property
def parameters(self):
return tuple(self._parameters)
@property
def registry_entries(self):
return tuple(self._registry_entries)
def add_parameter(self, parameter):
"""Adds a property to the environment.
:type parameter: tuskar.templates.heat.EnvironmentParameter
"""
self._parameters.append(parameter)
def remove_parameter(self, parameter):
"""Removes a parameter from the environment.
:type parameter: tuskar.templates.heat.EnvironmentParameter
:raise ValueError: if the parameter is not in the environment
"""
self._parameters.remove(parameter)
def remove_parameters_by_namespace(self, namespace):
"""Removes all parameters that match the given namespace.
:type namespace: str
"""
self._parameters =\
[p for p in self._parameters
if not ns_utils.matches_template_namespace(namespace, p.name)]
def add_registry_entry(self, entry):
"""Adds a registry entry to the environment.
:type entry: tuskar.templates.heat.RegistryEntry
"""
self._registry_entries.append(entry)
def remove_registry_entry(self, entry):
"""Removes a registry entry from the environment.
:type entry: tuskar.templates.heat.RegistryEntry
:raise ValueError: if the entry is not in the environment
"""
self._registry_entries.remove(entry)
def remove_registry_entry_by_alias(self, alias):
"""Removes a registry entry from the environment if it is found.
:type alias: str
"""
self._registry_entries = [e for e in self._registry_entries
if e.alias != alias]
class EnvironmentParameter(object):
def __init__(self, name, value):
super(EnvironmentParameter, self).__init__()
self.name = name
self.value = value
def __str__(self):
msg = 'EnvironmentParameter: name=%(name)s, value=%(value)s'
data = {
'name': self.name,
'value': self.value,
}
return msg % data
class RegistryEntry(object):
def __init__(self, alias, filename):
super(RegistryEntry, self).__init__()
self.alias = alias
self.filename = filename
def __str__(self):
msg = 'RegistryEntry: alias=%(alias)s, filename=%(f)s'
data = {
'alias': self.alias,
'f': self.filename,
}
return msg % data
def _safe_strip(value):
"""Strips the value if it is not None.
:param value: text to be cleaned up
:type value: str or None
:return: clean value if one was specified; None otherwise
:rtype: str or None
"""
if value is not None:
return value.strip()
return None

View File

@ -0,0 +1,66 @@
# -*- encoding: utf-8 -*-
#
# 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.
"""
Methods for manipulating Heat template pieces (parameters, outputs, etc.)
and Heat environment pieces (resource alias) to scope them to a particular
namespace to prevent conflicts when combining templates. This module contains
methods for applying, removing, and testing if a name is part of a
particular namespace.
"""
DELIMITER = '::'
ALIAS_PREFIX = 'Tuskar::'
def apply_template_namespace(namespace, original_name):
"""Applies a namespace to a template component, such as a parameter
or output.
:rtype: str
"""
return namespace + DELIMITER + original_name
def remove_template_namespace(name):
"""Strips any namespace off the given value and returns the original name.
:rtype: str
"""
return name[name.index(DELIMITER) + len(DELIMITER):]
def matches_template_namespace(namespace, name):
"""Returns whether or not the given name is in the specified namespace.
:rtype: bool
"""
return name.startswith(namespace + DELIMITER)
def apply_resource_alias_namespace(alias):
"""Creates a Heat environment resource alias under the Tuskar namespace.
:rtype: str
"""
return ALIAS_PREFIX + alias
def remove_resource_alias_namespace(alias):
"""Returns the original resource alias without the Tuskar namespace.
:rtype: str
"""
return alias[len(ALIAS_PREFIX) + 1:]

169
tuskar/templates/parser.py Normal file
View File

@ -0,0 +1,169 @@
# -*- encoding: utf-8 -*-
#
# 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.
"""
Functionality for parsing Heat files (templates and environment files) into
their object model representations.
The parsing was written against the HOT specification found at:
http://docs.openstack.org/developer/heat/template_guide/hot_spec.html
"""
import yaml
from tuskar.templates.heat import Environment
from tuskar.templates.heat import EnvironmentParameter
from tuskar.templates.heat import Output
from tuskar.templates.heat import Parameter
from tuskar.templates.heat import ParameterConstraint
from tuskar.templates.heat import RegistryEntry
from tuskar.templates.heat import Resource
from tuskar.templates.heat import ResourceProperty
from tuskar.templates.heat import Template
def parse_template(content):
"""Parses a Heat template into the Tuskar object model.
:param content: string representation of the template
:type content: str
:return: Tuskar representation of the template
:rtype: tuskar.templates.heat.Template
"""
yaml_parsed = yaml.load(content)
template = Template()
_parse_version(template, yaml_parsed)
_parse_description(template, yaml_parsed)
_parse_template_parameters(template, yaml_parsed)
_parse_parameter_group(template, yaml_parsed)
_parse_resources(template, yaml_parsed)
_parse_outputs(template, yaml_parsed)
return template
def parse_environment(content):
"""Parses a Heat environment file into the Tuskar object model.
:param content: string representation of the environment file
:type content: str
:return: Tuskar representation of the environment file
:rtype: tuskar.templates.heat.Environment
"""
yaml_parsed = yaml.load(content)
environment = Environment()
_parse_environment_parameters(environment, yaml_parsed)
_parse_resource_registry(environment, yaml_parsed)
return environment
def _parse_version(template, yaml_parsed):
template.version = \
yaml_parsed.get('heat_template_version', None) or template.version
def _parse_description(template, yaml_parsed):
template.description = \
yaml_parsed.get('description', None) or template.description
def _parse_template_parameters(template, yaml_parsed):
yaml_parameters = yaml_parsed.get('parameters', {})
for name, details in yaml_parameters.items():
# Basic parameter data
param_type = details['type'] # required
description = details.get('description', None)
label = details.get('label', None)
default = details.get('default', None)
hidden = details.get('hidden', None)
parameter = Parameter(name, param_type, description=description,
label=label, default=default, hidden=hidden)
template.add_parameter(parameter)
# Parse constraints if present
constraints = details.get('constraints', None)
if constraints is not None:
for constraint_details in constraints:
# The type of constraint is a key in the constraint data, so
# rather than know all of the possible values, pop out the
# description (if present) and assume the remaining key/value
# pair is the type and definition.
description = constraint_details.pop('description', None)
constraint_type = constraint_details.keys()[0]
definition = constraint_details[constraint_type]
constraint = ParameterConstraint(constraint_type, definition,
description=description)
parameter.add_constraint(constraint)
def _parse_parameter_group(template, yaml_parsed):
# There are no plans in Tuskar to use the role template groups, so
# we can hold off implementing this until they will be present.
pass
def _parse_resources(template, yaml_parsed):
yaml_resources = yaml_parsed.get('resources', {})
for resource_id, details in yaml_resources.items():
resource_type = details['type'] # required
metadata = details.get('metadata', None)
depends_on = details.get('depends_on', None)
update_policy = details.get('update_policy', None)
deletion_policy = details.get('deletion_policy', None)
resource = Resource(resource_id, resource_type, metadata=metadata,
depends_on=depends_on, update_policy=update_policy,
deletion_policy=deletion_policy)
template.add_resource(resource)
for key, value in details.get('properties', {}).items():
prop = ResourceProperty(key, value)
resource.add_property(prop)
def _parse_outputs(template, yaml_parsed):
yaml_outputs = yaml_parsed.get('outputs', {})
for name, details in yaml_outputs.items():
value = details['value'] # required
# HOT spec doesn't list this as optional, but most descriptions are,
# so assume it is here too
description = details.get('description', None)
output = Output(name, value, description=description)
template.add_output(output)
def _parse_environment_parameters(environment, yaml_parsed):
yaml_parameters = yaml_parsed.get('parameters', {})
for name, value in yaml_parameters.items():
parameter = EnvironmentParameter(name, value)
environment.add_parameter(parameter)
def _parse_resource_registry(environment, yaml_parsed):
yaml_entries = yaml_parsed.get('resource_registry', {})
for namespace, filename in yaml_entries.items():
entry = RegistryEntry(namespace, filename)
environment.add_registry_entry(entry)

144
tuskar/templates/plan.py Normal file
View File

@ -0,0 +1,144 @@
# -*- encoding: utf-8 -*-
#
# 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.
"""
Object representations of the Tuskar-specific domain concepts. These objects
are used to build up a deployment plan by adding templates (roles, in
Tuskar terminology). The composer module can then be used to translate
these models into the corresponding Heat format.
"""
import copy
from tuskar.templates.heat import Environment
from tuskar.templates.heat import EnvironmentParameter
from tuskar.templates.heat import Output
from tuskar.templates.heat import RegistryEntry
from tuskar.templates.heat import Resource
from tuskar.templates.heat import ResourceProperty
from tuskar.templates.heat import Template
import tuskar.templates.namespace as ns_utils
class DeploymentPlan(object):
def __init__(self, master_template=None, environment=None,
description=None):
super(DeploymentPlan, self).__init__()
self.master_template = \
master_template or Template(description=description)
self.environment = environment or Environment()
def add_template(self, namespace, template, filename):
"""Adds a new template to the plan. The pieces of the template will
be prefixed with the given namespace in the plan's master template.
:param namespace: prefix to use to prevent parameter and output
naming conflicts
:type namespace: str
:param template: template being added to the plan
:type template: tuskar.templates.heat.Template
:param filename: name of the file where the template is stored, used
when mapping the template in the environment
:type filename: str
"""
resource_alias = ns_utils.apply_resource_alias_namespace(namespace)
self._add_to_master_template(namespace, template, resource_alias)
self._add_to_environment(namespace, template, filename, resource_alias)
def remove_template(self, namespace):
"""Removes all references to the template added under the given
namespace. This call does not error if a template with the given
namespace hasn't been added.
:type namespace: str
"""
self._remove_from_master_template(namespace)
self._remove_from_environment(namespace)
def _add_to_master_template(self, namespace, template, resource_alias):
# Add Parameters
for add_me in template.parameters:
cloned = copy.deepcopy(add_me)
cloned.name = ns_utils.apply_template_namespace(namespace,
add_me.name)
self.master_template.add_parameter(cloned)
# Create Resource
resource = Resource(_generate_resource_id(namespace), resource_alias)
self.master_template.add_resource(resource)
for map_me in template.parameters:
name = map_me.name
master_name = ns_utils.apply_template_namespace(namespace,
map_me.name)
value = {'get_param': [master_name]}
resource_property = ResourceProperty(name, value)
resource.add_property(resource_property)
# Add Outputs
for add_me in template.outputs:
# The output creation is a bit trickier than simply copying the
# original. The master output is namespaced like the other pieces,
# and it's value is retrieved from the resource that's created in
# the master template, but will be present in that resource
# under it's original name.
output_name = ns_utils.apply_template_namespace(namespace,
add_me.name)
output_value = {'get_attr': [resource.resource_id, add_me.name]}
master_out = Output(output_name, output_value)
self.master_template.add_output(master_out)
def _add_to_environment(self, namespace, template,
filename, resource_alias):
# Add Parameters
for add_me in template.parameters:
name = ns_utils.apply_template_namespace(namespace, add_me.name)
env_parameter = EnvironmentParameter(name, '')
self.environment.add_parameter(env_parameter)
# Add Resource Registry Entry
registry_entry = RegistryEntry(resource_alias, filename)
self.environment.add_registry_entry(registry_entry)
def _remove_from_master_template(self, namespace):
# Remove Parameters
self.master_template.remove_parameters_by_namespace(namespace)
# Remove Outputs
self.master_template.remove_outputs_by_namespace(namespace)
# Remove Resource
resource_id = _generate_resource_id(namespace)
self.master_template.remove_resource_by_id(resource_id)
def _remove_from_environment(self, namespace):
# Remove Parameters
self.environment.remove_parameters_by_namespace(namespace)
# Remove Resource Registry Entry
resource_alias = ns_utils.apply_resource_alias_namespace(namespace)
self.environment.remove_registry_entry_by_alias(resource_alias)
def _generate_resource_id(namespace):
"""Generates the ID of the resource to be added to the plan's master
template when a new template is added.
:type namespace: str
:rtype: str
"""
return namespace + '-resource'

View File

View File

@ -0,0 +1,169 @@
# -*- encoding: utf-8 -*-
#
# 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 unittest
import yaml
from tuskar.templates import composer
from tuskar.templates import heat
class ComposerTests(unittest.TestCase):
def test_compose_template(self):
# Test
sample = self._sample_template()
composed = composer.compose_template(sample)
# Verify
self.assertTrue(isinstance(composed, str))
# Check that it can both be parsed back as YAML and use the resulting
# dict in the assertions
template = yaml.safe_load(composed)
# Verify Overall Structure
self.assertEqual(5, len(template))
self.assertTrue('heat_template_version' in template)
self.assertTrue('description' in template)
self.assertTrue('parameters' in template)
self.assertTrue('resources' in template)
self.assertTrue('outputs' in template)
# Verify Top-Level Attributes
self.assertEqual('2013-05-23', template['heat_template_version'])
self.assertEqual('template-desc', template['description'])
# Verify Parameters
self.assertEqual(2, len(template['parameters']))
self.assertTrue('p1' in template['parameters'])
self.assertEqual('t1', template['parameters']['p1']['type'])
self.assertEqual('desc-1', template['parameters']['p1']['description'])
self.assertEqual('l1', template['parameters']['p1']['label'])
self.assertEqual('def-1', template['parameters']['p1']['default'])
self.assertEqual(True, template['parameters']['p1']['hidden'])
self.assertTrue('p2' in template['parameters'])
self.assertEqual('t2', template['parameters']['p2']['type'])
self.assertTrue('description' not in template['parameters']['p2'])
self.assertTrue('label' not in template['parameters']['p2'])
self.assertTrue('default' not in template['parameters']['p2'])
self.assertTrue('hidden' not in template['parameters']['p2'])
# Verify Resources
self.assertEqual(2, len(template['resources']))
self.assertTrue('r1' in template['resources'])
self.assertEqual('t1', template['resources']['r1']['type'])
self.assertEqual('m1', template['resources']['r1']['metadata'])
self.assertEqual('r2', template['resources']['r1']['depends_on'])
self.assertEqual({'u1': 'u2'},
template['resources']['r1']['update_policy'])
self.assertEqual({'d1': 'd2'},
template['resources']['r1']['deletion_policy'])
self.assertTrue('r2' in template['resources'])
self.assertEqual('t2', template['resources']['r2']['type'])
self.assertTrue('metadata' not in template['resources']['r2'])
self.assertTrue('depends_on' not in template['resources']['r2'])
self.assertTrue('update_policy' not in template['resources']['r2'])
self.assertTrue('deletion_policy' not in template['resources']['r2'])
# Verify Outputs
self.assertEqual(2, len(template['outputs']))
self.assertTrue('n1' in template['outputs'])
self.assertEqual('v1', template['outputs']['n1']['value'])
self.assertEqual('desc-1', template['outputs']['n1']['description'])
self.assertTrue('n2' in template['outputs'])
self.assertEqual('v2', template['outputs']['n2']['value'])
self.assertTrue('description' not in template['outputs']['n2'])
def test_compose_environment(self):
# Test
sample = self._sample_environment()
composed = composer.compose_environment(sample)
# Verify
self.assertTrue(isinstance(composed, str))
# Check that it can both be parsed back as YAML and use the resulting
# dict in the assertions
template = yaml.safe_load(composed)
# Verify Overall Structure
self.assertEqual(2, len(template))
self.assertTrue('parameters' in template)
self.assertTrue('resource_registry' in template)
# Verify Parameters
self.assertEqual(2, len(template['parameters']))
self.assertTrue('n1' in template['parameters'])
self.assertEqual('v1', template['parameters']['n1'])
self.assertTrue('n2' in template['parameters'])
self.assertEqual('v2', template['parameters']['n2'])
# Verify Resource Registry
self.assertEqual(2, len(template['resource_registry']))
self.assertTrue('a1' in template['resource_registry'])
self.assertEqual('f1', template['resource_registry']['a1'])
self.assertTrue('a2' in template['resource_registry'])
self.assertEqual('f2', template['resource_registry']['a2'])
def _sample_template(self):
t = heat.Template(description='template-desc')
# Complex Parameter
param = heat.Parameter('p1', 't1', description='desc-1', label='l1',
default='def-1', hidden=True)
param.add_constraint(heat.ParameterConstraint('t1', 'def-1',
description='desc-1'))
t.add_parameter(param)
# Simple Parameter
t.add_parameter(heat.Parameter('p2', 't2'))
# Complex Resource
resource = heat.Resource('r1', 't1', metadata='m1', depends_on='r2',
update_policy={'u1': 'u2'},
deletion_policy={'d1': 'd2'})
t.add_resource(resource)
# Simple Resource
t.add_resource(heat.Resource('r2', 't2'))
# Complex Output
t.add_output(heat.Output('n1', 'v1', description='desc-1'))
# Simple Output
t.add_output(heat.Output('n2', 'v2'))
return t
def _sample_environment(self):
e = heat.Environment()
e.add_parameter(heat.EnvironmentParameter('n1', 'v1'))
e.add_parameter(heat.EnvironmentParameter('n2', 'v2'))
e.add_registry_entry(heat.RegistryEntry('a1', 'f1'))
e.add_registry_entry(heat.RegistryEntry('a2', 'f2'))
return e

View File

@ -0,0 +1,386 @@
# -*- encoding: utf-8 -*-
#
# 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 unittest
from tuskar.templates import heat
from tuskar.templates import namespace as ns
class TemplateTests(unittest.TestCase):
def test_default_version(self):
self.assertEqual(heat.DEFAULT_VERSION, '2013-05-23')
def test_init(self):
# Test
t = heat.Template(description='test template')
str(t) # should not raise an exception
# Verify
self.assertEqual(t.version, heat.DEFAULT_VERSION)
self.assertEqual(t.description, 'test template')
self.assertEqual(0, len(t.parameters))
self.assertEqual(0, len(t.parameter_groups))
self.assertEqual(0, len(t.resources))
self.assertEqual(0, len(t.outputs))
def test_add_remove_parameter(self):
t = heat.Template()
p = heat.Parameter('test-param', 'test-type')
# Test Add
t.add_parameter(p)
self.assertEqual(1, len(t.parameters))
self.assertEqual(p, t.parameters[0])
# Test Remove
t.remove_parameter(p)
self.assertEqual(0, len(t.parameters))
def test_remove_parameters_by_namespace(self):
# Setup
t = heat.Template()
p1 = heat.Parameter(ns.apply_template_namespace('ns1', 'foo'), 't')
p2 = heat.Parameter(ns.apply_template_namespace('ns2', 'bar'), 't')
p3 = heat.Parameter(ns.apply_template_namespace('ns1', 'baz'), 't')
t.add_parameter(p1)
t.add_parameter(p2)
t.add_parameter(p3)
# Test
t.remove_parameters_by_namespace('ns1')
# Verify
self.assertEqual(1, len(t.parameters))
self.assertEqual(p2, t.parameters[0])
def test_remove_parameter_not_found(self):
t = heat.Template()
self.assertRaises(ValueError, t.remove_parameter,
heat.Parameter('n', 't'))
def test_add_remove_parameter_group(self):
t = heat.Template()
pg = heat.ParameterGroup('test-label', 'test-desc')
# Test Add
t.add_parameter_group(pg)
self.assertEqual(1, len(t.parameter_groups))
self.assertEqual(pg, t.parameter_groups[0])
# Test Remove
t.remove_parameter_group(pg)
self.assertEqual(0, len(t.parameter_groups))
def test_add_remove_resource(self):
t = heat.Template()
r = heat.Resource('id', 't')
# Test Add
t.add_resource(r)
self.assertEqual(1, len(t.resources))
self.assertEqual(r, t.resources[0])
# Test Remove
t.remove_resource(r)
self.assertEqual(0, len(t.resources))
def test_remove_resource_by_id(self):
# Test
t = heat.Template()
t.add_resource(heat.Resource('id1', 't1'))
t.add_resource(heat.Resource('id2', 't2'))
t.remove_resource_by_id('id1')
# Verify
self.assertEqual(1, len(t.resources))
self.assertEqual(t.resources[0].resource_type, 't2')
def test_add_remove_output(self):
t = heat.Template()
o = heat.Output('n', 'v')
# Test Add
t.add_output(o)
self.assertEqual(1, len(t.outputs))
self.assertEqual(o, t.outputs[0])
def test_remove_outputs_by_namespace(self):
# Setup
t = heat.Template()
o1 = heat.Output(ns.apply_template_namespace('ns1', 'foo'), 'v')
o2 = heat.Output(ns.apply_template_namespace('ns2', 'bar'), 'v')
o3 = heat.Output(ns.apply_template_namespace('ns1', 'foo'), 'v')
t.add_output(o1)
t.add_output(o2)
t.add_output(o3)
# Test
t.remove_outputs_by_namespace('ns1')
# Verify
self.assertEqual(1, len(t.outputs))
self.assertEqual(o2, t.outputs[0])
def test_remove_output_not_found(self):
t = heat.Template()
self.assertRaises(ValueError, t.remove_output, heat.Output('n', 'v'))
class ParameterGroupTests(unittest.TestCase):
def test_init(self):
# Test
g = heat.ParameterGroup('test-label', 'test-desc')
str(g) # should not raise an exception
# Verify
self.assertEqual(g.label, 'test-label')
self.assertEqual(g.description, 'test-desc')
self.assertEqual(0, len(g.parameter_names))
def test_add_remove_property_name(self):
g = heat.ParameterGroup('l', 'd')
# Test Add
g.add_parameter_name('p1')
self.assertEqual(1, len(g.parameter_names))
self.assertEqual('p1', g.parameter_names[0])
# Test Remove
g.remove_parameter_name('p1')
self.assertEqual(0, len(g.parameter_names))
def test_remove_name_not_found(self):
g = heat.ParameterGroup('l', 'd')
g.remove_parameter_name('n1') # should not error
class ParameterTests(unittest.TestCase):
def test_init(self):
# Test
p = heat.Parameter('test-name', 'test-type', description='test-desc',
label='test-label', default='test-default',
hidden='test-hidden')
str(p) # should not error
# Verify
self.assertEqual('test-name', p.name)
self.assertEqual('test-type', p.param_type)
self.assertEqual('test-desc', p.description)
self.assertEqual('test-label', p.label)
self.assertEqual('test-default', p.default)
self.assertEqual('test-hidden', p.hidden)
def test_add_remove_constraint(self):
p = heat.Parameter('n', 't')
c = heat.ParameterConstraint('t', 'd')
# Test Add
p.add_constraint(c)
self.assertEqual(1, len(p.constraints))
self.assertEqual(c, p.constraints[0])
# Test Remove
p.remove_constraint(c)
self.assertEqual(0, len(p.constraints))
def test_remove_constraint_not_found(self):
p = heat.Parameter('n', 't')
self.assertRaises(ValueError, p.remove_constraint,
heat.ParameterConstraint('t', 'd'))
class ParameterConstraintTests(unittest.TestCase):
def test_init(self):
# Test
c = heat.ParameterConstraint('test-type', 'test-def',
description='test-desc')
str(c) # should not error
# Verify
self.assertEqual('test-type', c.constraint_type)
self.assertEqual('test-def', c.definition)
self.assertEqual('test-desc', c.description)
class ResourceTests(unittest.TestCase):
def test_init(self):
# Test
r = heat.Resource('test-id',
'test-type',
metadata='test-meta',
depends_on='test-depends',
update_policy='test-update',
deletion_policy='test-delete')
str(r) # should not error
# Verify
self.assertEqual('test-id', r.resource_id)
self.assertEqual('test-type', r.resource_type)
self.assertEqual('test-meta', r.metadata)
self.assertEqual('test-depends', r.depends_on)
self.assertEqual('test-update', r.update_policy)
self.assertEqual('test-delete', r.deletion_policy)
def test_add_remove_property(self):
r = heat.Resource('i', 't')
p = heat.ResourceProperty('n', 'v')
# Test Add
r.add_property(p)
self.assertEqual(1, len(r.properties))
self.assertEqual(p, r.properties[0])
# Test Remove
r.remove_property(p)
self.assertEqual(0, len(r.properties))
def test_remove_property_not_found(self):
r = heat.Resource('i', 't')
self.assertRaises(ValueError, r.remove_property,
heat.ResourceProperty('n', 'v'))
class ResourcePropertyTests(unittest.TestCase):
def test_init(self):
# Test
p = heat.ResourceProperty('test-name', 'test-value')
str(p) # should not error
# Verify
self.assertEqual('test-name', p.name)
self.assertEqual('test-value', p.value)
class OutputTests(unittest.TestCase):
def test_init(self):
# Test
o = heat.Output('test-name', 'test-value', description='test-desc')
str(o) # should not error
# Verify
self.assertEqual('test-name', o.name)
self.assertEqual('test-value', o.value)
self.assertEqual('test-desc', o.description)
class EnvironmentTests(unittest.TestCase):
def test_init(self):
# Test
e = heat.Environment()
str(e) # should not error
def test_add_remove_parameter(self):
e = heat.Environment()
p = heat.EnvironmentParameter('n', 'v')
# Test Add
e.add_parameter(p)
self.assertEqual(1, len(e.parameters))
self.assertEqual(p, e.parameters[0])
# Test Remove
e.remove_parameter(p)
self.assertEqual(0, len(e.parameters))
def test_remove_parameter_not_found(self):
e = heat.Environment()
self.assertRaises(ValueError, e.remove_parameter,
heat.EnvironmentParameter('n', 'v'))
def test_remove_parameters_by_namespace(self):
# Setup
e = heat.Environment()
p1 = heat.EnvironmentParameter(
ns.apply_template_namespace('ns1', 'n1'), 'v')
p2 = heat.EnvironmentParameter(
ns.apply_template_namespace('ns2', 'n2'), 'v')
p3 = heat.EnvironmentParameter(
ns.apply_template_namespace('ns1', 'n3'), 'v')
e.add_parameter(p1)
e.add_parameter(p2)
e.add_parameter(p3)
# Test
e.remove_parameters_by_namespace('ns1')
# Verify
self.assertEqual(1, len(e.parameters))
self.assertEqual(p2, e.parameters[0])
def test_add_remove_registry_entry(self):
e = heat.Environment()
re = heat.RegistryEntry('a', 'f')
# Test Add
e.add_registry_entry(re)
self.assertEqual(1, len(e.registry_entries))
self.assertEqual(re, e.registry_entries[0])
# Test Remove
e.remove_registry_entry(re)
self.assertEqual(0, len(e.registry_entries))
def test_remove_registry_entry_not_found(self):
e = heat.Environment()
self.assertRaises(ValueError, e.remove_registry_entry,
heat.RegistryEntry('a', 'f'))
def test_remove_registry_entry_by_namespace(self):
# Setup
e = heat.Environment()
e.add_registry_entry(heat.RegistryEntry('a1', 'f1'))
e.add_registry_entry(heat.RegistryEntry('a2', 'f2'))
e.add_registry_entry(heat.RegistryEntry('a1', 'f3'))
# Test
e.remove_registry_entry_by_alias('a1')
# Verify
self.assertEqual(1, len(e.registry_entries))
self.assertEqual(e.registry_entries[0].filename, 'f2')
class EnvironmentParameterTests(unittest.TestCase):
def test_init(self):
# Test
p = heat.EnvironmentParameter('test-name', 'test-value')
str(p) # should not error
# Verify
self.assertEqual('test-name', p.name)
self.assertEqual('test-value', p.value)
class ModuleMethodTests(unittest.TestCase):
def test_safe_strip(self):
self.assertEqual('foo', heat._safe_strip(' foo '))
self.assertEqual(None, heat._safe_strip(None))

View File

@ -0,0 +1,43 @@
# -*- encoding: utf-8 -*-
#
# 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 unittest
from tuskar.templates import namespace
class NamespaceTests(unittest.TestCase):
def test_apply_template_namespace(self):
namespaced = namespace.apply_template_namespace('test-ns', 'test-name')
self.assertEqual(namespaced, 'test-ns::test-name')
self.assertTrue(namespace.matches_template_namespace('test-ns',
namespaced))
def test_remove_template_namespace(self):
stripped = namespace.remove_template_namespace('test-ns::test-name')
self.assertEqual(stripped, 'test-name')
def test_matches_template_namespace(self):
value = 'test-ns::test-name'
self.assertTrue(namespace.matches_template_namespace('test-ns', value))
self.assertFalse(namespace.matches_template_namespace('fake', value))
def test_apply_resource_alias_namespace(self):
namespaced = namespace.apply_resource_alias_namespace('compute')
self.assertEqual(namespaced, 'Tuskar::compute')
def test_remove_resource_alias_namespace(self):
stripped = namespace.remove_template_namespace('Tuskar::controller')
self.assertEqual(stripped, 'controller')

View File

@ -0,0 +1,170 @@
# -*- encoding: utf-8 -*-
#
# 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 datetime
import unittest
from tuskar.templates import heat
from tuskar.templates import parser
TEST_TEMPLATE = """
heat_template_version: 2013-05-23
description: Test provider resource foo
parameters:
key_name:
type: string
description : Name of a KeyPair
hidden: true
label: Key
instance_type:
type: string
description: Instance type
default: m1.small
constraints:
- allowed_values: [m1.small, m1.medium, m1.large]
description: instance_type must be one of m1.small or m1.medium
image_id:
type: string
description: ID of the image to use
default: 3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b
resources:
foo_instance:
type: OS::Nova::Server
properties:
image: { get_param: image_id }
flavor: { get_param: instance_type }
key_name: { get_param: key_name }
outputs:
foo_ip:
description: IP of the created foo instance
value: { get_attr: [foo_instance, first_address] }
"""
TEST_ENVIRONMENT = """
parameters:
key_name: heat_key
instance_type: m1.small
image_id: 3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b
resource_registry:
Tuskar::Foo: provider-foo.yaml
Tuskar::Bar: provider-bar.yaml
"""
class ParserTests(unittest.TestCase):
def test_parse_template(self):
# Test
t = parser.parse_template(TEST_TEMPLATE)
# Verify
self.assertTrue(isinstance(t, heat.Template))
self.assertEqual(t.version, datetime.date(2013, 5, 23))
self.assertEqual(t.description, 'Test provider resource foo')
self.assertEqual(3, len(t.parameters))
ordered_params = sorted(t.parameters, key=lambda x: x.name)
# Image ID Parameter
self.assertEqual('image_id', ordered_params[0].name)
self.assertEqual('string', ordered_params[0].param_type)
self.assertEqual('ID of the image to use',
ordered_params[0].description)
self.assertEqual('3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b',
ordered_params[0].default)
self.assertEqual(None, ordered_params[0].hidden)
self.assertEqual(None, ordered_params[0].label)
self.assertEqual(0, len(ordered_params[0].constraints))
# Instance Type Parameter
self.assertEqual('instance_type', ordered_params[1].name)
self.assertEqual('string', ordered_params[1].param_type)
self.assertEqual('Instance type', ordered_params[1].description)
self.assertEqual('m1.small', ordered_params[1].default)
self.assertEqual(None, ordered_params[1].hidden)
self.assertEqual(None, ordered_params[1].label)
self.assertEqual(1, len(ordered_params[1].constraints))
c = ordered_params[1].constraints[0]
self.assertEqual('instance_type must be one of m1.small or m1.medium',
c.description)
self.assertEqual('allowed_values', c.constraint_type)
self.assertEqual(['m1.small', 'm1.medium', 'm1.large'], c.definition)
# Key Name Parameter
self.assertEqual('key_name', ordered_params[2].name)
self.assertEqual('string', ordered_params[2].param_type)
self.assertEqual('Name of a KeyPair', ordered_params[2].description)
self.assertEqual(None, ordered_params[2].default)
self.assertEqual(True, ordered_params[2].hidden)
self.assertEqual('Key', ordered_params[2].label)
self.assertEqual(0, len(ordered_params[2].constraints))
# Resources
self.assertEqual(1, len(t.resources))
self.assertEqual('foo_instance', t.resources[0].resource_id)
self.assertEqual('OS::Nova::Server', t.resources[0].resource_type)
self.assertEqual(3, len(t.resources[0].properties))
resource_props = sorted(t.resources[0].properties,
key=lambda x: x.name)
self.assertEqual('flavor', resource_props[0].name)
self.assertEqual({'get_param': 'instance_type'},
resource_props[0].value)
self.assertEqual('image', resource_props[1].name)
self.assertEqual({'get_param': 'image_id'},
resource_props[1].value)
self.assertEqual('key_name', resource_props[2].name)
self.assertEqual({'get_param': 'key_name'},
resource_props[2].value)
# Outputs
self.assertEqual(1, len(t.outputs))
self.assertEqual('foo_ip', t.outputs[0].name)
self.assertEqual({'get_attr': ['foo_instance', 'first_address']},
t.outputs[0].value)
def test_parse_environment(self):
# Test
e = parser.parse_environment(TEST_ENVIRONMENT)
# Verify
self.assertTrue(isinstance(e, heat.Environment))
# Parameters
self.assertEqual(3, len(e.parameters))
ordered_params = sorted(e.parameters, key=lambda x: x.name)
self.assertEqual('image_id', ordered_params[0].name)
self.assertEqual('3e6270da-fbf7-4aef-bc78-6d0cfc3ad11b',
ordered_params[0].value)
self.assertEqual('instance_type', ordered_params[1].name)
self.assertEqual('m1.small', ordered_params[1].value)
self.assertEqual('key_name', ordered_params[2].name)
self.assertEqual('heat_key', ordered_params[2].value)
# Resource Registry
self.assertEqual(2, len(e.registry_entries))
ordered_entries = sorted(e.registry_entries, key=lambda x: x.alias)
self.assertEqual('Tuskar::Bar', ordered_entries[0].alias)
self.assertEqual('provider-bar.yaml', ordered_entries[0].filename)
self.assertEqual('Tuskar::Foo', ordered_entries[1].alias)
self.assertEqual('provider-foo.yaml', ordered_entries[1].filename)

View File

@ -0,0 +1,135 @@
# -*- encoding: utf-8 -*-
#
# 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 unittest
from tuskar.templates import heat
from tuskar.templates import namespace as ns_utils
from tuskar.templates import plan
class DeploymentPlanTests(unittest.TestCase):
def test_empty(self):
# Test
p = plan.DeploymentPlan(description='test-desc')
str(p) # should not error
# Verify
self.assertTrue(isinstance(p.master_template, heat.Template))
self.assertTrue(isinstance(p.environment, heat.Environment))
self.assertEqual('test-desc', p.master_template.description)
def test_existing_pieces(self):
# Test
t = heat.Template()
e = heat.Environment()
p = plan.DeploymentPlan(master_template=t, environment=e)
# Verify
self.assertTrue(p.master_template is t)
self.assertTrue(p.environment is e)
def test_add_template(self):
# Test
p = plan.DeploymentPlan()
t = self._generate_template()
p.add_template('ns1', t, 'template-1.yaml')
# Verify Master Template Parameters
self.assertEqual(2, len(p.master_template.parameters))
for original, added in zip(t.parameters, p.master_template.parameters):
self.assertTrue(added is not original)
expected_name = ns_utils.apply_template_namespace('ns1',
original.name)
self.assertEqual(added.name, expected_name)
self.assertEqual(added.param_type, original.param_type)
# Verify Resource
self.assertEqual(1, len(p.master_template.resources))
added = p.master_template.resources[0]
expected_id = plan._generate_resource_id('ns1')
self.assertEqual(added.resource_id, expected_id)
expected_type = ns_utils.apply_resource_alias_namespace('ns1')
self.assertEqual(added.resource_type, expected_type)
for param, prop in zip(t.parameters, added.properties):
v = ns_utils.apply_template_namespace('ns1', param.name)
expected_value = {'get_param': [v]}
self.assertEqual(prop.value, expected_value)
# Verify Outputs
self.assertEqual(2, len(p.master_template.outputs))
for original, added in zip(t.outputs, p.master_template.outputs):
self.assertTrue(added is not original)
expected_name = ns_utils.apply_template_namespace('ns1',
original.name)
expected_value = {'get_attr': [expected_id, original.name]}
self.assertEqual(added.name, expected_name)
self.assertEqual(added.value, expected_value)
# Verify Environment Parameters
self.assertEqual(2, len(p.environment.parameters))
for env_param, template_param in zip(p.environment.parameters,
t.parameters):
expected_name =\
ns_utils.apply_template_namespace('ns1', template_param.name)
self.assertEqual(env_param.name, expected_name)
self.assertEqual(env_param.value, '')
# Verify Resource Registry Entry
self.assertEqual(1, len(p.environment.registry_entries))
added = p.environment.registry_entries[0]
expected_alias = ns_utils.apply_resource_alias_namespace('ns1')
self.assertEqual(added.alias, expected_alias)
self.assertEqual(added.filename, 'template-1.yaml')
def test_remove_template(self):
# Setup & Sanity Check
p = plan.DeploymentPlan()
t = self._generate_template()
p.add_template('ns1', t, 'template-1.yaml')
p.add_template('ns2', t, 'template-2.yaml')
self.assertEqual(4, len(p.master_template.parameters))
self.assertEqual(4, len(p.master_template.outputs))
self.assertEqual(2, len(p.master_template.resources))
self.assertEqual(4, len(p.environment.parameters))
self.assertEqual(2, len(p.environment.registry_entries))
# Test
p.remove_template('ns1')
# Verify
self.assertEqual(2, len(p.master_template.parameters))
self.assertEqual(2, len(p.master_template.outputs))
self.assertEqual(1, len(p.master_template.resources))
self.assertEqual(2, len(p.environment.parameters))
self.assertEqual(1, len(p.environment.registry_entries))
def _generate_template(self):
t = heat.Template()
t.add_parameter(heat.Parameter('param-1', 'type-1'))
t.add_parameter(heat.Parameter('param-2', 'type-2'))
t.add_output(heat.Output('out-1', 'value-1'))
t.add_output(heat.Output('out-2', 'value-2'))
return t