Implement substitution mappings

Add validation for substitute_mappings and a test example.
The substitution is responsibility of an orchestrator so
work needs to be done in heat-translator to translate
substitute_mapping in HOT.

Partially example of substitute_mappings is given in
the test/data/topology_template/subsystem.yaml

Refer to the spec section 'Using node template substitution
for model composition' and related sections to read detail
on the substitute_mappings.

The related heat-translator blueprint is
https://blueprints.launchpad.net/heat-translator/+spec/
nested-templates

Partially-Implements: blueprint tosca-substitution-mappings

Co-Authored-By: Sahdev Zala <spzala@us.ibm.com>

Change-Id: I7baadef4f0b4c7cd426f710f4c74c198d02bc030
Signed-off-by: shangxdy <shang.xiaodong@zte.com.cn>
This commit is contained in:
shangxdy 2016-07-21 22:41:11 +08:00
parent aa23c05f04
commit 24c71e28e8
13 changed files with 520 additions and 64 deletions

View File

@ -22,12 +22,13 @@ class TypeValidation(object):
DSL_DEFINITIONS, NODE_TYPES, REPOSITORIES,
DATA_TYPES, ARTIFACT_TYPES, GROUP_TYPES,
RELATIONSHIP_TYPES, CAPABILITY_TYPES,
INTERFACE_TYPES, POLICY_TYPES) = \
INTERFACE_TYPES, POLICY_TYPES,
TOPOLOGY_TEMPLATE) = \
('tosca_definitions_version', 'description', 'imports',
'dsl_definitions', 'node_types', 'repositories',
'data_types', 'artifact_types', 'group_types',
'relationship_types', 'capability_types',
'interface_types', 'policy_types')
'interface_types', 'policy_types', 'topology_template')
VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0']
exttools = ExtTools()
VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())

View File

@ -724,11 +724,12 @@ def get_function(tosca_tpl, node_template, raw_function):
parsing was unsuccessful.
"""
if is_function(raw_function):
func_name = list(raw_function.keys())[0]
if func_name in function_mappings:
func = function_mappings[func_name]
func_args = list(raw_function.values())[0]
if not isinstance(func_args, list):
func_args = [func_args]
return func(tosca_tpl, node_template, func_name, func_args)
if isinstance(raw_function, dict):
func_name = list(raw_function.keys())[0]
if func_name in function_mappings:
func = function_mappings[func_name]
func_args = list(raw_function.values())[0]
if not isinstance(func_args, list):
func_args = [func_args]
return func(tosca_tpl, node_template, func_name, func_args)
return raw_function

View File

@ -37,6 +37,7 @@ class ImportsLoader(object):
tpl=None):
self.importslist = importslist
self.custom_defs = {}
self.nested_tosca_tpls = []
if not path and not tpl:
msg = _('Input tosca template is not provided.')
log.warning(msg)
@ -56,6 +57,9 @@ class ImportsLoader(object):
def get_custom_defs(self):
return self.custom_defs
def get_nested_tosca_tpls(self):
return self.nested_tosca_tpls
def _validate_and_load_imports(self):
imports_names = set()
@ -77,8 +81,8 @@ class ImportsLoader(object):
ValidationError(message=msg))
imports_names.add(import_name)
custom_type = self._load_import_template(import_name,
import_uri)
full_file_name, custom_type = self._load_import_template(
import_name, import_uri)
namespace_prefix = None
if isinstance(import_uri, dict):
namespace_prefix = import_uri.get(
@ -87,13 +91,15 @@ class ImportsLoader(object):
TypeValidation(custom_type, import_def)
self._update_custom_def(custom_type, namespace_prefix)
else: # old style of imports
custom_type = self._load_import_template(None,
import_def)
full_file_name, custom_type = self._load_import_template(
None, import_def)
if custom_type:
TypeValidation(
custom_type, import_def)
self._update_custom_def(custom_type, None)
self._update_nested_tosca_tpls(full_file_name, custom_type)
def _update_custom_def(self, custom_type, namespace_prefix):
outer_custom_types = {}
for type_def in self.type_definition_list:
@ -113,6 +119,11 @@ class ImportsLoader(object):
else:
self.custom_defs.update(outer_custom_types)
def _update_nested_tosca_tpls(self, full_file_name, custom_tpl):
if full_file_name and custom_tpl:
topo_tpl = {full_file_name: custom_tpl}
self.nested_tosca_tpls.append(topo_tpl)
def _validate_import_keys(self, import_name, import_uri_def):
if self.FILE not in import_uri_def.keys():
log.warning(_('Missing keyname "file" in import "%(name)s".')
@ -173,10 +184,10 @@ class ImportsLoader(object):
% {'import_name': import_name})
log.error(msg)
ExceptionCollector.appendException(ValidationError(message=msg))
return
return None, None
if toscaparser.utils.urlutils.UrlUtils.validate_url(file_name):
return YAML_LOADER(file_name, False)
return file_name, YAML_LOADER(file_name, False)
elif not repository:
import_template = None
if self.path:
@ -188,7 +199,7 @@ class ImportsLoader(object):
% {'name': file_name, 'template': self.path})
log.error(msg)
ExceptionCollector.appendException(ImportError(msg))
return
return None, None
import_template = toscaparser.utils.urlutils.UrlUtils.\
join_url(self.path, file_name)
a_file = False
@ -231,7 +242,7 @@ class ImportsLoader(object):
% {'name': file_name})
log.error(msg)
ExceptionCollector.appendException(ImportError(msg))
return
return None, None
if not import_template:
log.error(_('Import "%(name)s" is not valid.') %
@ -239,14 +250,14 @@ class ImportsLoader(object):
ExceptionCollector.appendException(
ImportError(_('Import "%s" is not valid.') %
import_uri_def))
return
return YAML_LOADER(import_template, a_file)
return None, None
return import_template, YAML_LOADER(import_template, a_file)
if short_import_notation:
log.error(_('Import "%(name)s" is not valid.') % import_uri_def)
ExceptionCollector.appendException(
ImportError(_('Import "%s" is not valid.') % import_uri_def))
return
return None, None
full_url = ""
if repository:
@ -264,10 +275,10 @@ class ImportsLoader(object):
% {'n_uri': repository, 'tpl': import_name})
log.error(msg)
ExceptionCollector.appendException(ImportError(msg))
return
return None, None
if toscaparser.utils.urlutils.UrlUtils.validate_url(full_url):
return YAML_LOADER(full_url, False)
return full_url, YAML_LOADER(full_url, False)
else:
msg = (_('repository url "%(n_uri)s" is not valid in import '
'definition "%(tpl)s".')

View File

@ -48,6 +48,7 @@ class NodeTemplate(EntityTemplate):
self.available_rel_tpls = available_rel_tpls
self.available_rel_types = available_rel_types
self._relationships = {}
self.sub_mapping_tosca_template = None
@property
def relationships(self):

View File

@ -0,0 +1,157 @@
# 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 logging
from toscaparser.common.exception import ExceptionCollector
from toscaparser.common.exception import InvalidNodeTypeError
from toscaparser.common.exception import MissingRequiredFieldError
from toscaparser.common.exception import UnknownFieldError
log = logging.getLogger('tosca')
class SubstitutionMappings(object):
'''SubstitutionMappings class declaration
SubstitutionMappings exports the topology template as an
implementation of a Node type.
'''
SECTIONS = (NODE_TYPE, REQUIREMENTS, CAPABILITIES) = \
('node_type', 'requirements', 'capabilities')
def __init__(self, sub_mapping_def, nodetemplates, inputs, outputs,
sub_mapped_node_template, custom_defs):
self.nodetemplates = nodetemplates
self.sub_mapping_def = sub_mapping_def
self.inputs = inputs or []
self.outputs = outputs or []
self.sub_mapped_node_template = sub_mapped_node_template
self.custom_defs = custom_defs or {}
self._validate()
self._capabilities = None
self._requirements = None
@property
def type(self):
if self.sub_mapping_def:
return self.sub_mapping_def['node_type']
@classmethod
def get_node_type(cls, sub_mapping_def):
if isinstance(sub_mapping_def, dict):
return sub_mapping_def.get(cls.NODE_TYPE)
@property
def node_type(self):
return self.sub_mapping_def.get(self.NODE_TYPE)
@property
def capabilities(self):
return self.sub_mapping_def.get(self.CAPABILITIES)
@property
def requirements(self):
return self.sub_mapping_def.get(self.REQUIREMENTS)
def _validate(self):
self._validate_keys()
self._validate_type()
self._validate_inputs()
self._validate_capabilities()
self._validate_requirements()
self._validate_outputs()
def _validate_keys(self):
"""validate the keys of substitution mappings."""
for key in self.sub_mapping_def.keys():
if key not in self.SECTIONS:
ExceptionCollector.appendException(
UnknownFieldError(what='SubstitutionMappings',
field=key))
def _validate_type(self):
"""validate the node_type of substitution mappings."""
node_type = self.sub_mapping_def.get(self.NODE_TYPE)
if not node_type:
ExceptionCollector.appendException(
MissingRequiredFieldError(
what=_('SubstitutionMappings used in topology_template'),
required=self.NODE_TYPE))
node_type_def = self.custom_defs.get(node_type)
if not node_type_def:
ExceptionCollector.appendException(
InvalidNodeTypeError(what=node_type_def))
def _validate_inputs(self):
"""validate the inputs of substitution mappings."""
# The inputs in service template which defines substutition mappings
# must be in properties of node template which is mapped or provide
# defualt value. Currently the input.name is not restrict to be the
# same as properte's name in specification, but they should be equal
# for current implementation.
property_names = list(self.sub_mapped_node_template
.get_properties().keys()
if self.sub_mapped_node_template else [])
for input in self.inputs:
if input.name not in property_names and input.default is None:
ExceptionCollector.appendException(
UnknownFieldError(what='SubstitutionMappings',
field=input.name))
def _validate_capabilities(self):
"""validate the capabilities of substitution mappings."""
# The capabilites must be in node template wchich be mapped.
tpls_capabilities = self.sub_mapping_def.get(self.CAPABILITIES)
node_capabiliteys = self.sub_mapped_node_template.get_capabilities() \
if self.sub_mapped_node_template else None
for cap in node_capabiliteys.keys() if node_capabiliteys else []:
if (tpls_capabilities and
cap not in list(tpls_capabilities.keys())):
pass
# ExceptionCollector.appendException(
# UnknownFieldError(what='SubstitutionMappings',
# field=cap))
def _validate_requirements(self):
"""validate the requirements of substitution mappings."""
# The requirements must be in node template wchich be mapped.
tpls_requirements = self.sub_mapping_def.get(self.REQUIREMENTS)
node_requirements = self.sub_mapped_node_template.requirements \
if self.sub_mapped_node_template else None
for req in node_requirements if node_requirements else []:
if (tpls_requirements and
req not in list(tpls_requirements.keys())):
pass
# ExceptionCollector.appendException(
# UnknownFieldError(what='SubstitutionMappings',
# field=req))
def _validate_outputs(self):
"""validate the outputs of substitution mappings."""
pass
# The outputs in service template which defines substutition mappings
# must be in atrributes of node template wchich be mapped.
# outputs_names = self.sub_mapped_node_template.get_properties().
# keys() if self.sub_mapped_node_template else None
# for name in outputs_names:
# if name not in [output.name for input in self.outputs]:
# ExceptionCollector.appendException(
# UnknownFieldError(what='SubstitutionMappings',
# field=name))

View File

@ -0,0 +1,90 @@
tosca_definitions_version: tosca_simple_yaml_1_0
description: >
Database subsystem, which is service template with topology_template,
act as a nested system inside another system and also act as stand
alone service template.
imports:
- definitions.yaml
topology_template:
description: Template of a database including its hosting stack.
inputs:
user:
type: string
description: the user name of database.
default: test
port:
type: integer
description: the port of database.
default: 3306
name:
type: string
description: the name of database.
default: test
my_cpus:
type: integer
description: Number of CPUs for the server.
default: 2
constraints:
- valid_values: [ 1, 2, 4, 8 ]
substitution_mappings:
node_type: example.DatabaseSubsystem
capabilities:
database_endpoint: [ db_app, database_endpoint ]
node_templates:
db_app:
type: tosca.nodes.Database
properties:
user: { get_input: user }
port: { get_input: port }
name: { get_input: name }
capabilities:
database_endpoint:
properties:
port: 1234
requirements:
- host:
node: dbms
dbms:
type: tosca.nodes.DBMS
properties:
port: 3306
root_password: 123456789
requirements:
- host:
node: server
server:
type: tosca.nodes.Compute
capabilities:
host:
properties:
disk_size: 10 GB
num_cpus: { get_input: my_cpus }
mem_size: 4096 MB
os:
properties:
architecture: x86_64
type: Linux
distribution: Ubuntu
version: 14.04
outputs:
receiver_ip:
description: private IP address of the database application
value: { get_attribute: [ server, private_address ] }
# It seems current _process_intrisic_function can not handle more than 2 arguments, save it for later
# receiver_port:
# description: Port of the message receiver endpoint
# value: { get_attribute: [ app, data_endpoint, port_name ] }
groups:
dbserver_group:
members: [ dbms, server ]
type: tosca.groups.Root

View File

@ -1,6 +1,28 @@
tosca_definitions_version: tosca_simple_yaml_1_0
node_types:
example.QueuingSubsystem:
derived_from: tosca.nodes.SoftwareComponent
properties:
server_ip:
type: string
server_port:
type: integer
attributes:
server_ip:
type: string
server_port:
type: integer
requirements:
- receiver1:
node: example.TransactionSubsystem
capability: example.capabilities.Receiver
relationship: tosca.relationships.ConnectsTo
- receiver2:
node: example.TransactionSubsystem
capability: example.capabilities.Receiver
relationship: tosca.relationships.ConnectsTo
example.TransactionSubsystem:
properties:
mq_server_ip:
@ -17,18 +39,8 @@ node_types:
type: example.capabilities.Receiver
requirements:
- database_endpoint:
capability: tosca.capabilities.Endpoint.Database
node: tosca.nodes.Database
relationship: tosca.relationships.ConnectsTo
example.QueuingSubsystem:
derived_from: tosca.nodes.SoftwareComponent
requirements:
- receiver1:
node: example.TransactionSubsystem
relationship: tosca.relationships.ConnectsTo
- receiver2:
node: example.TransactionSubsystem
capability: tosca.capabilities.Endpoint.Database
relationship: tosca.relationships.ConnectsTo
example.DatabaseSubsystem:
@ -44,6 +56,11 @@ node_types:
capabilities:
message_receiver:
type: example.capabilities.Receiver
requirements:
- database:
node: tosca.nodes.Database
capability: tosca.capabilities.Endpoint.Database
relationship: tosca.relationships.ConnectsTo
capability_types:
example.capabilities.Receiver:

View File

@ -0,0 +1,75 @@
tosca_definitions_version: tosca_simple_yaml_1_0
description: >
Queue subsystem, which is service template with topology_template,
act as a nested system inside another system and also act as stand
alone service template.
imports:
- definitions.yaml
topology_template:
description: Template of a database including its hosting stack.
inputs:
server_ip:
type: string
description: IP address of the message queuing server to receive messages from.
default: 127.0.0.1
server_port:
type: integer
description: Port to be used for receiving messages.
default: 8080
my_cpus:
type: integer
description: Number of CPUs for the server.
default: 2
constraints:
- valid_values: [ 1, 2, 4, 8 ]
substitution_mappings:
node_type: example.QueuingSubsystem
# capabilities:
# message_receiver: [ app, message_receiver ]
requirements:
receiver1: [ tran_app, receiver1 ]
receiver2: [ tran_app, receiver2 ]
node_templates:
tran_app:
type: example.QueuingSubsystem
properties:
server_ip: { get_input: server_ip }
server_port: { get_input: server_port }
requirements:
- host:
node: server
server:
type: tosca.nodes.Compute
capabilities:
host:
properties:
disk_size: 10 GB
num_cpus: { get_input: my_cpus }
mem_size: 4096 MB
os:
properties:
architecture: x86_64
type: Linux
distribution: Ubuntu
version: 14.04
outputs:
receiver_ip:
description: private IP address of the message receiver application
value: { get_attribute: [ server, private_address ] }
# It seems current _process_intrisic_function can not handle more than 2 arguments, save it for later
# receiver_port:
# description: Port of the message receiver endpoint
# value: { get_attribute: [ app, data_endpoint, port_name ] }
groups:
tran_server_group:
members: [ tran_app, server ]
type: tosca.groups.Root

View File

@ -1,19 +1,34 @@
tosca_definitions_version: tosca_simple_yaml_1_0
imports:
- definitions.yaml
- queuingsubsystem.yaml
- transactionsubsystem.yaml
- databasesubsystem.yaml
topology_template:
description: Template of online transaction processing service.
inputs:
mq_server_ip:
type: string
default: 127.0.0.1
description: IP address of the message queuing server to receive messages from.
mq_server_port:
type: integer
default1: 8080
description: Port to be used for receiving messages.
node_templates:
mq:
type: example.QueuingSubsystem
# properties:
# to be updated when substitution_mapping is implemented
# to be updated when substitution_mapping is validated later
properties:
server_ip: { get_input: mq_server_ip }
server_port: { get_input: mq_server_port }
# capabilities:
# message_queue_endpoint:
# to be updated when substitution_mapping is implemented
# to be updated when substitution_mapping is validated later
requirements:
- receiver1: trans1
- receiver2: trans2
@ -21,37 +36,33 @@ topology_template:
trans1:
type: example.TransactionSubsystem
properties:
# TODO to be updated when substitution_mapping is implemented
# mq_server_ip: { get_attribute: [ mq, server_ip ] }
# for now, we will use the loopback address to avoid errors as
# this property is required in the schema
mq_server_ip: 127.0.0.1
receiver_port: 8080
# mq_server_ip: 127.0.0.1
mq_server_ip: { get_attribute: [ mq, server_ip ] }
# receiver_port: 8080
receiver_port: { get_attribute: [ mq, server_port ] }
# capabilities:
# message_receiver:
# to be updated when substitution_mapping is implemented
# to be updated when substitution_mapping is validated later
requirements:
- database_endpoint: dbsys
trans2:
type: example.TransactionSubsystem
properties:
# TODO to be updated when substitution_mapping is implemented
# mq_server_ip: { get_attribute: [ mq, server_ip ] }
# for now, we will use the loopback address to avoid errors as
# this property is required in the schema
mq_server_ip: 127.0.0.1
receiver_port: 8080
# mq_server_ip: 127.0.0.1
mq_server_ip: { get_attribute: [ mq, server_ip ] }
# receiver_port: 8080
receiver_port: { get_attribute: [ mq, server_port ] }
# capabilities:
# message_receiver:
# to be updated when substitution_mapping is implemented
# to be updated when substitution_mapping is validated later
requirements:
- database_endpoint: dbsys
dbsys:
type: example.DatabaseSubsystem
# properties:
# to be updated when substitution_mapping is implemented
# to be updated when substitution_mapping is validated later
# capabilities:
# database_endpoint:
# to be updated when substitution_mapping is implemented
# to be updated when substitution_mapping is validated later

View File

@ -1,7 +1,9 @@
tosca_definitions_version: tosca_simple_yaml_1_0
description: >
Service template with topology_template, act as a nested system inside another system.
Transaction subsystem, which is service template with topology_template,
act as a nested system inside another system and also act as stand
alone service template.
imports:
- definitions.yaml
@ -13,12 +15,15 @@ topology_template:
mq_server_ip:
type: string
description: IP address of the message queuing server to receive messages from.
default: 127.0.0.1
receiver_port:
type: string
type: integer
description: Port to be used for receiving messages.
default: 8080
my_cpus:
type: integer
description: Number of CPUs for the server.
default: 2
constraints:
- valid_values: [ 1, 2, 4, 8 ]

View File

@ -27,7 +27,7 @@ class TopologyTemplateTest(TestCase):
'''TOSCA template.'''
self.tosca_tpl_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/topology_template/subsystem.yaml")
"data/topology_template/transactionsubsystem.yaml")
self.tpl = YAML_LOADER(self.tosca_tpl_path)
self.topo_tpl = self.tpl.get('topology_template')
self.imports = self.tpl.get('imports')
@ -157,4 +157,8 @@ class TopologyTemplateTest(TestCase):
tpl_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/topology_template/system.yaml")
self.assertIsNotNone(ToscaTemplate(tpl_path))
system_tosca_template = ToscaTemplate(tpl_path)
self.assertIsNotNone(system_tosca_template)
self.assertEqual(
len(system_tosca_template.
nested_tosca_templates_with_topology), 4)

View File

@ -22,6 +22,7 @@ from toscaparser.parameters import Input
from toscaparser.parameters import Output
from toscaparser.policy import Policy
from toscaparser.relationship_template import RelationshipTemplate
from toscaparser.substitution_mappings import SubstitutionMappings
from toscaparser.tpl_relationship_graph import ToscaGraph
from toscaparser.utils.gettextutils import _
@ -41,8 +42,10 @@ class TopologyTemplate(object):
'''Load the template data.'''
def __init__(self, template, custom_defs,
rel_types=None, parsed_params=None):
rel_types=None, parsed_params=None,
sub_mapped_node_template=None):
self.tpl = template
self.sub_mapped_node_template = sub_mapped_node_template
if self.tpl:
self.custom_defs = custom_defs
self.rel_types = rel_types
@ -58,6 +61,7 @@ class TopologyTemplate(object):
self.groups = self._groups()
self.policies = self._policies()
self._process_intrinsic_functions()
self.substitution_mappings = self._substitution_mappings()
def _inputs(self):
inputs = []
@ -105,7 +109,15 @@ class TopologyTemplate(object):
return outputs
def _substitution_mappings(self):
pass
tpl_substitution_mapping = self._tpl_substitution_mappings()
# if tpl_substitution_mapping and self.sub_mapped_node_template:
if tpl_substitution_mapping:
return SubstitutionMappings(tpl_substitution_mapping,
self.nodetemplates,
self.inputs,
self.outputs,
self.sub_mapped_node_template,
self.custom_defs)
def _policies(self):
policies = []
@ -177,13 +189,16 @@ class TopologyTemplate(object):
# topology template can act like node template
# it is exposed by substitution_mappings.
def nodetype(self):
pass
return self.substitution_mappings.node_type \
if self.substitution_mappings else None
def capabilities(self):
pass
return self.substitution_mappings.capabilities \
if self.substitution_mappings else None
def requirements(self):
pass
return self.substitution_mappings.requirements \
if self.substitution_mappings else None
def _tpl_description(self):
description = self.tpl.get(DESCRIPTION)
@ -278,3 +293,9 @@ class TopologyTemplate(object):
func = functions.get_function(self, self.outputs, output.value)
if isinstance(func, functions.GetAttribute):
output.attrs[output.VALUE] = func
@classmethod
def get_sub_mapping_node_type(cls, topology_tpl):
if topology_tpl and isinstance(topology_tpl, dict):
submap_tpl = topology_tpl.get(SUBSTITUION_MAPPINGS)
return SubstitutionMappings.get_node_type(submap_tpl)

View File

@ -65,11 +65,14 @@ class ToscaTemplate(object):
'''Load the template data.'''
def __init__(self, path=None, parsed_params=None, a_file=True,
yaml_dict_tpl=None):
ExceptionCollector.start()
self.a_file = a_file
self.input_path = None
self.path = None
self.tpl = None
self.nested_tosca_tpls_with_topology = {}
self.nested_tosca_templates_with_topology = []
if path:
self.input_path = path
self.path = self._get_path(path)
@ -101,6 +104,7 @@ class ToscaTemplate(object):
self.relationship_templates = self._relationship_templates()
self.nodetemplates = self._nodetemplates()
self.outputs = self._outputs()
self._handle_nested_tosca_templates_with_topology()
self.graph = ToscaGraph(self.nodetemplates)
ExceptionCollector.stop()
@ -110,7 +114,8 @@ class ToscaTemplate(object):
return TopologyTemplate(self._tpl_topology_template(),
self._get_all_custom_defs(),
self.relationship_types,
self.parsed_params)
self.parsed_params,
None)
def _inputs(self):
return self.topology_template.inputs
@ -188,9 +193,14 @@ class ToscaTemplate(object):
imports = self._tpl_imports()
if imports:
custom_defs = toscaparser.imports.\
custom_service = toscaparser.imports.\
ImportsLoader(imports, self.path,
type_defs, self.tpl).get_custom_defs()
type_defs, self.tpl)
nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
custom_defs = custom_service.get_custom_defs()
if not custom_defs:
return
@ -202,6 +212,33 @@ class ToscaTemplate(object):
custom_defs.update(inner_custom_types)
return custom_defs
def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
for tpl in nested_tosca_tpls:
filename, tosca_tpl = list(tpl.items())[0]
if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
filename not in list(
self.nested_tosca_tpls_with_topology.keys())):
self.nested_tosca_tpls_with_topology.update(tpl)
def _handle_nested_tosca_templates_with_topology(self):
for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
for nodetemplate in self.nodetemplates:
if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
topology_tpl = tosca_tpl.get(TOPOLOGY_TEMPLATE)
topology_with_sub_mapping = TopologyTemplate(
topology_tpl,
self._get_all_custom_defs(),
self.relationship_types,
self.parsed_params,
nodetemplate)
if topology_with_sub_mapping.substitution_mappings:
# Record nested topo templates in top level template
self.nested_tosca_templates_with_topology.\
append(topology_with_sub_mapping)
# Set substitution mapping object for mapped node
nodetemplate.sub_mapping_tosca_template = \
topology_with_sub_mapping.substitution_mappings
def _validate_field(self):
version = self._tpl_version()
if not version:
@ -264,3 +301,28 @@ class ToscaTemplate(object):
msg = _('The pre-parsed input successfully passed validation.')
log.info(msg)
def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
"""Return True if the nodetemple is substituted."""
if (nodetemplate and not nodetemplate.sub_mapping_tosca_template and
self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
and len(nodetemplate.interfaces) < 1):
return True
else:
return False
def get_sub_mapping_node_type(self, tosca_tpl):
"""Return substitution mappings node type."""
if tosca_tpl:
return TopologyTemplate.get_sub_mapping_node_type(
tosca_tpl.get(TOPOLOGY_TEMPLATE))
def has_substitution_mappings(self):
"""Return True if the template has valid substitution mappings."""
return self.topology_template is not None and \
self.topology_template.substitution_mappings is not None
def has_nested_templates(self):
"""Return True if the tosca template has nested templates."""
return self.nested_tosca_templates_with_topology is not None and \
len(self.nested_tosca_templates_with_topology) >= 1