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:
parent
f197b62676
commit
2b73047350
0
tuskar/templates/__init__.py
Normal file
0
tuskar/templates/__init__.py
Normal file
162
tuskar/templates/composer.py
Normal file
162
tuskar/templates/composer.py
Normal 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
448
tuskar/templates/heat.py
Normal 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
|
66
tuskar/templates/namespace.py
Normal file
66
tuskar/templates/namespace.py
Normal 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
169
tuskar/templates/parser.py
Normal 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
144
tuskar/templates/plan.py
Normal 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'
|
0
tuskar/tests/templates/__init__.py
Normal file
0
tuskar/tests/templates/__init__.py
Normal file
169
tuskar/tests/templates/test_composer.py
Normal file
169
tuskar/tests/templates/test_composer.py
Normal 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
|
386
tuskar/tests/templates/test_heat.py
Normal file
386
tuskar/tests/templates/test_heat.py
Normal 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))
|
43
tuskar/tests/templates/test_namespace.py
Normal file
43
tuskar/tests/templates/test_namespace.py
Normal 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')
|
170
tuskar/tests/templates/test_parser.py
Normal file
170
tuskar/tests/templates/test_parser.py
Normal 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)
|
135
tuskar/tests/templates/test_plan.py
Normal file
135
tuskar/tests/templates/test_plan.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user