diff options
author | 2016-09-14 14:36:33 +0800 | |
---|---|---|
committer | 2016-09-14 16:00:06 +0800 | |
commit | f977b94e012828cd2a905b9b841aff33c4b732f8 (patch) | |
tree | f175b52c811f48335aeaa82d0c20baab8a99881d | |
parent | ae543033a8c4af9c291261fec55859c2f1191a1a (diff) |
Update the upstream of tosco-parser and heat-translator to stable
release 0.6/0.5
Currently the parser is based on dev branch of upstream projects,
include tosco-parser and heat-translator, for the colorado release of
parser, it should be based on a stable version, so it's necessary to
update the upstream version to 0.6/tosca-parser and 0.5/heat-translator.
JIRA:PARSER-106
Change-Id: I8fb043068d25188c47e5648e1b66184446ac82d6
Signed-off-by: shangxdy <shang.xiaodong@zte.com.cn>
29 files changed, 1502 insertions, 1457 deletions
diff --git a/tosca2heat/heat-translator/test-requirements.txt b/tosca2heat/heat-translator/test-requirements.txt index 70a261b..17a507c 100644 --- a/tosca2heat/heat-translator/test-requirements.txt +++ b/tosca2heat/heat-translator/test-requirements.txt @@ -4,11 +4,11 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD -fixtures>=3.0.0 # Apache-2.0/BSD +fixtures<2.0,>=1.3.1 # Apache-2.0/BSD oslotest>=1.10.0 # Apache-2.0 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD -sphinx!=1.3b1,<1.3,>=1.2.1 # BSD +sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT diff --git a/tosca2heat/heat-translator/tox.ini b/tosca2heat/heat-translator/tox.ini index 9ee67a0..be57d3e 100644 --- a/tosca2heat/heat-translator/tox.ini +++ b/tosca2heat/heat-translator/tox.ini @@ -27,11 +27,6 @@ commands = python setup.py build_sphinx [testenv:debug] commands = oslo_debug_helper -t translator/tests {posargs} -[testenv:py27-tosca-parser-master] -commands = - ./run_py27-tosca-parser-master.sh - python setup.py test --slowest --testr-args='{posargs}' - [flake8] # H803 skipped on purpose per list discussion. # E123, E125 skipped as they are invalid PEP-8. diff --git a/tosca2heat/heat-translator/translator/common/utils.py b/tosca2heat/heat-translator/translator/common/utils.py index 8e4b690..459b5ee 100644 --- a/tosca2heat/heat-translator/translator/common/utils.py +++ b/tosca2heat/heat-translator/translator/common/utils.py @@ -18,7 +18,6 @@ import numbers import os import re import requests -import six from six.moves.urllib.parse import urlparse import yaml @@ -263,17 +262,12 @@ class UrlUtils(object): def str_to_num(value): """Convert a string representation of a number into a numeric type.""" - if isinstance(value, numbers.Number) \ - or isinstance(value, six.integer_types) \ - or isinstance(value, float): + if isinstance(value, numbers.Number): return value try: return int(value) except ValueError: - try: - return float(value) - except ValueError: - return None + return float(value) def check_for_env_variables(): diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py index a41208a..ad77fb3 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py @@ -21,8 +21,5 @@ class HotOutput(object): self.description = description def get_dict_output(self): - if self.description: - return {self.name: {'value': self.value, - 'description': self.description}} - else: - return {self.name: {'value': self.value}} + return {self.name: {'value': self.value, + 'description': self.description}} diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py index 7b83906..54e0d96 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py @@ -103,7 +103,7 @@ class HotResource(object): # scenarios and cannot be fixed or hard coded here operations_deploy_sequence = ['create', 'configure', 'start'] - operations = HotResource.get_all_operations(self.nodetemplate) + operations = HotResource._get_all_operations(self.nodetemplate) # create HotResource for each operation used for deployment: # create, start, configure @@ -140,15 +140,7 @@ class HotResource(object): # hosting_server is None if requirements is None hosting_on_server = (hosting_server.name if hosting_server else None) - base_type = HotResource.get_base_type( - self.nodetemplate.type_definition).type - # handle interfaces directly defined on a compute - if hosting_on_server is None \ - and base_type == 'tosca.nodes.Compute': - hosting_on_server = self.name - - if operation.name == reserve_current and \ - base_type != 'tosca.nodes.Compute': + if operation.name == reserve_current: deploy_resource = self self.name = deploy_name self.type = 'OS::Heat::SoftwareDeployment' @@ -156,7 +148,7 @@ class HotResource(object): 'server': {'get_resource': hosting_on_server}, 'signal_transport': 'HEAT_SIGNAL'} - deploy_lookup[operation] = self + deploy_lookup[operation.name] = self else: sd_config = {'config': {'get_resource': config_name}, 'server': {'get_resource': @@ -168,7 +160,7 @@ class HotResource(object): 'OS::Heat::SoftwareDeployment', sd_config) hot_resources.append(deploy_resource) - deploy_lookup[operation] = deploy_resource + deploy_lookup[operation.name] = deploy_resource lifecycle_inputs = self._get_lifecycle_inputs(operation) if lifecycle_inputs: deploy_resource.properties['input_values'] = \ @@ -178,34 +170,24 @@ class HotResource(object): # in operations_deploy_sequence # TODO(anyone): find some better way to encode this implicit sequence group = {} - op_index_max = -1 for op, hot in deploy_lookup.items(): # position to determine potential preceding nodes - op_index = operations_deploy_sequence.index(op.name) - if op_index > op_index_max: - op_index_max = op_index - for preceding_op_name in \ + op_index = operations_deploy_sequence.index(op) + for preceding_op in \ reversed(operations_deploy_sequence[:op_index]): - preceding_hot = deploy_lookup.get( - operations.get(preceding_op_name)) + preceding_hot = deploy_lookup.get(preceding_op) if preceding_hot: hot.depends_on.append(preceding_hot) hot.depends_on_nodes.append(preceding_hot) group[preceding_hot] = hot break - if op_index_max >= 0: - last_deploy = deploy_lookup.get(operations.get( - operations_deploy_sequence[op_index_max])) - else: - last_deploy = None - # save this dependency chain in the set of HOT resources self.group_dependencies.update(group) for hot in hot_resources: hot.group_dependencies.update(group) - return hot_resources, deploy_lookup, last_deploy + return hot_resources def handle_connectsto(self, tosca_source, tosca_target, hot_source, hot_target, config_location, operation): @@ -353,7 +335,7 @@ class HotResource(object): return tosca_props @staticmethod - def get_all_operations(node): + def _get_all_operations(node): operations = {} for operation in node.interfaces: operations[operation.name] = operation diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py index 0403562..4263c4d 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py @@ -77,7 +77,6 @@ class HotTemplate(object): dict_output.update({self.OUTPUTS: all_outputs}) yaml.add_representer(OrderedDict, self.represent_ordereddict) - yaml.add_representer(dict, self.represent_ordereddict) yaml_string = yaml.dump(dict_output, default_flow_style=False) # get rid of the '' from yaml.dump around numbers yaml_string = yaml_string.replace('\'', '') diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py index 743074b..408ee8b 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py @@ -16,6 +16,7 @@ from mock import patch from toscaparser.nodetemplate import NodeTemplate from toscaparser.tests.base import TestCase +from toscaparser.utils.gettextutils import _ import toscaparser.utils.yamlparser from translator.hot.tosca.tosca_compute import ToscaCompute @@ -26,12 +27,22 @@ class ToscaComputeTest(TestCase): nodetemplates = (toscaparser.utils.yamlparser. simple_parse(tpl_snippet)['node_templates']) name = list(nodetemplates.keys())[0] - nodetemplate = NodeTemplate(name, nodetemplates) - nodetemplate.validate() - toscacompute = ToscaCompute(nodetemplate) - toscacompute.handle_properties() + try: + nodetemplate = NodeTemplate(name, nodetemplates) + nodetemplate.validate() + toscacompute = ToscaCompute(nodetemplate) + toscacompute.handle_properties() + if not self._compare_properties(toscacompute.properties, + expectedprops): + raise Exception(_("Hot Properties are not" + " same as expected properties")) + except Exception: + # for time being rethrowing. Will be handled future based + # on new development in Glance and Graffiti + raise - self.assertDictEqual(expectedprops, toscacompute.properties) + def _compare_properties(self, hotprops, expectedprops): + return all(item in hotprops.items() for item in expectedprops.items()) def test_node_compute_with_host_and_os_capabilities(self): tpl_snippet = ''' diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py index 924ff9d..d4b2f44 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py @@ -67,5 +67,5 @@ class ToscaBlockStorage(HotResource): # attribute for the matching resource. Unless there is additional # runtime support, this should be a one to one mapping. if attribute == 'volume_id': - attr['get_resource'] = self.name + attr['get_resource'] = args[0] return attr diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py index 2242c2d..71b9822 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py @@ -50,4 +50,4 @@ class ToscaBlockStorageAttachment(HotResource): self.properties.pop('device') def handle_life_cycle(self): - return None, None, None + pass diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py index 16d0518..8a959d1 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py @@ -84,14 +84,6 @@ class ToscaCompute(HotResource): ('architecture', 'distribution', 'type', 'version') toscatype = 'tosca.nodes.Compute' - ALLOWED_NOVA_SERVER_PROPS = \ - ('admin_pass', 'availability_zone', 'block_device_mapping', - 'block_device_mapping_v2', 'config_drive', 'diskConfig', 'flavor', - 'flavor_update_policy', 'image', 'image_update_policy', 'key_name', - 'metadata', 'name', 'networks', 'personality', 'reservation_id', - 'scheduler_hints', 'security_groups', 'software_config_transport', - 'user_data', 'user_data_format', 'user_data_update_policy') - def __init__(self, nodetemplate): super(ToscaCompute, self).__init__(nodetemplate, type='OS::Nova::Server') @@ -107,8 +99,7 @@ class ToscaCompute(HotResource): self.properties['software_config_transport'] = 'POLL_SERVER_HEAT' tosca_props = self.get_tosca_props() for key, value in tosca_props.items(): - if key in self.ALLOWED_NOVA_SERVER_PROPS: - self.properties[key] = value + self.properties[key] = value # To be reorganized later based on new development in Glance and Graffiti def translate_compute_flavor_and_image(self, @@ -264,7 +255,7 @@ class ToscaCompute(HotResource): images = IMAGES if translator.common.utils.check_for_env_variables(): resp = self._populate_image_dict() - if resp and len(resp.keys()) > 0: + if len(resp.keys()) > 0: images = resp match_all = images.keys() architecture = properties.get(self.ARCHITECTURE) diff --git a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py index 4dd9556..0aefd48 100644 --- a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py +++ b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py @@ -16,8 +16,6 @@ import logging import os import six -from collections import OrderedDict -from toscaparser.functions import Concat from toscaparser.functions import GetAttribute from toscaparser.functions import GetInput from toscaparser.functions import GetProperty @@ -27,7 +25,6 @@ from toscaparser.utils.gettextutils import _ from translator.common.exception import ToscaClassAttributeError from translator.common.exception import ToscaClassImportError from translator.common.exception import ToscaModImportError -from translator.common import utils from translator.conf.config import ConfigProvider as translatorConfig from translator.hot.syntax.hot_resource import HotResource from translator.hot.tosca.tosca_block_storage_attachment import ( @@ -135,8 +132,6 @@ log = logging.getLogger('heat-translator') TOSCA_TO_HOT_TYPE = _generate_type_map() -BASE_TYPES = six.string_types + six.integer_types + (dict, OrderedDict) - class TranslateNodeTemplates(object): '''Translate TOSCA NodeTemplates to Heat Resources.''' @@ -151,9 +146,6 @@ class TranslateNodeTemplates(object): log.debug(_('Mapping between TOSCA nodetemplate and HOT resource.')) self.hot_lookup = {} self.policies = self.tosca.topology_template.policies - # stores the last deploy of generated behavior for a resource - # useful to satisfy underlying dependencies between interfaces - self.last_deploy_map = {} def translate(self): return self._translate_nodetemplates() @@ -231,14 +223,9 @@ class TranslateNodeTemplates(object): # into multiple HOT resources and may change their name lifecycle_resources = [] for resource in self.hot_resources: - expanded_resources, deploy_lookup, last_deploy = resource.\ - handle_life_cycle() - if expanded_resources: - lifecycle_resources += expanded_resources - if deploy_lookup: - self.hot_lookup.update(deploy_lookup) - if last_deploy: - self.last_deploy_map[resource] = last_deploy + expanded = resource.handle_life_cycle() + if expanded: + lifecycle_resources += expanded self.hot_resources += lifecycle_resources # Handle configuration from ConnectsTo relationship in the TOSCA node: @@ -270,9 +257,7 @@ class TranslateNodeTemplates(object): # if the source of dependency is a server and the # relationship type is 'tosca.relationships.HostedOn', # add dependency as properties.server - base_type = HotResource.get_base_type( - node_depend.type_definition) - if base_type.type == 'tosca.nodes.Compute' and \ + if node_depend.type == 'tosca.nodes.Compute' and \ node.related[node_depend].type == \ node.type_definition.HOSTEDON: self.hot_lookup[node].properties['server'] = \ @@ -285,13 +270,6 @@ class TranslateNodeTemplates(object): self.hot_lookup[node].depends_on_nodes.append( self.hot_lookup[node_depend].top_of_chain()) - last_deploy = self.last_deploy_map.get( - self.hot_lookup[node_depend]) - if last_deploy and \ - last_deploy not in self.hot_lookup[node].depends_on: - self.hot_lookup[node].depends_on.append(last_deploy) - self.hot_lookup[node].depends_on_nodes.append(last_deploy) - # handle hosting relationship for resource in self.hot_resources: resource.handle_hosting() @@ -321,202 +299,53 @@ class TranslateNodeTemplates(object): inputs = resource.properties.get('input_values') if inputs: for name, value in six.iteritems(inputs): - inputs[name] = self.translate_param_value(value, resource) - - # remove resources without type defined - # for example a SoftwareComponent without interfaces - # would fall in this case - to_remove = [] - for resource in self.hot_resources: - if resource.type is None: - to_remove.append(resource) - - for resource in to_remove: - self.hot_resources.remove(resource) + inputs[name] = self._translate_input(value, resource) return self.hot_resources - def translate_param_value(self, param_value, resource): - tosca_template = None - if resource: - tosca_template = resource.nodetemplate - + def _translate_input(self, input_value, resource): get_property_args = None - if isinstance(param_value, GetProperty): - get_property_args = param_value.args + if isinstance(input_value, GetProperty): + get_property_args = input_value.args # to remove when the parser is fixed to return GetProperty - elif isinstance(param_value, dict) and 'get_property' in param_value: - get_property_args = param_value['get_property'] + if isinstance(input_value, dict) and 'get_property' in input_value: + get_property_args = input_value['get_property'] if get_property_args is not None: - tosca_target, prop_name, prop_arg = \ - self.decipher_get_operation(get_property_args, - tosca_template) - if tosca_target: - prop_value = tosca_target.get_property_value(prop_name) - if prop_value: - prop_value = self.translate_param_value( - prop_value, resource) - return self._unfold_value(prop_value, prop_arg) - get_attr_args = None - if isinstance(param_value, GetAttribute): - get_attr_args = param_value.result().args - # to remove when the parser is fixed to return GetAttribute - elif isinstance(param_value, dict) and 'get_attribute' in param_value: - get_attr_args = param_value['get_attribute'] - if get_attr_args is not None: + hot_target = self._find_hot_resource_for_tosca( + get_property_args[0], resource) + if hot_target: + props = hot_target.get_tosca_props() + prop_name = get_property_args[1] + if prop_name in props: + return props[prop_name] + elif isinstance(input_value, GetAttribute): # for the attribute # get the proper target type to perform the translation - tosca_target, attr_name, attr_arg = \ - self.decipher_get_operation(get_attr_args, tosca_template) - attr_args = [] - if attr_arg: - attr_args += attr_arg - if tosca_target: - if tosca_target in self.hot_lookup: - attr_value = self.hot_lookup[tosca_target].\ - get_hot_attribute(attr_name, attr_args) - attr_value = self.translate_param_value( - attr_value, resource) - return self._unfold_value(attr_value, attr_arg) - elif isinstance(param_value, dict) and 'get_artifact' in param_value: - get_artifact_args = param_value['get_artifact'] - tosca_target, artifact_name, _ = \ - self.decipher_get_operation(get_artifact_args, - tosca_template) - - if tosca_target: - artifacts = self.get_all_artifacts(tosca_target) - if artifact_name in artifacts: - artifact = artifacts[artifact_name] - if artifact.get('type', None) == 'tosca.artifacts.File': - return {'get_file': artifact.get('file')} - get_input_args = None - if isinstance(param_value, GetInput): - get_input_args = param_value.args - elif isinstance(param_value, dict) and 'get_input' in param_value: - get_input_args = param_value['get_input'] - if get_input_args is not None: - if isinstance(get_input_args, list) \ - and len(get_input_args) == 1: - return {'get_param': self.translate_param_value( - get_input_args[0], resource)} - else: - return {'get_param': self.translate_param_value( - get_input_args, resource)} - elif isinstance(param_value, dict) \ - and 'get_operation_output' in param_value: - res = self._translate_get_operation_output_function( - param_value['get_operation_output'], tosca_template) - if res: - return res - concat_list = None - if isinstance(param_value, Concat): - concat_list = param_value.args - elif isinstance(param_value, dict) and 'concat' in param_value: - concat_list = param_value['concat'] - if concat_list is not None: - res = self._translate_concat_function(concat_list, resource) - if res: - return res - - if isinstance(param_value, list): - translated_list = [] - for elem in param_value: - translated_elem = self.translate_param_value(elem, resource) - if translated_elem: - translated_list.append(translated_elem) - return translated_list - - if isinstance(param_value, BASE_TYPES): - return param_value - - return None - - def _translate_concat_function(self, concat_list, resource): - str_replace_template = '' - str_replace_params = {} - index = 0 - for elem in concat_list: - str_replace_template += '$s' + str(index) - str_replace_params['$s' + str(index)] = \ - self.translate_param_value(elem, resource) - index += 1 - - return {'str_replace': { - 'template': str_replace_template, - 'params': str_replace_params - }} - - def _translate_get_operation_output_function(self, args, tosca_template): - tosca_target = self._find_tosca_node(args[0], - tosca_template) - if tosca_target and len(args) >= 4: - operations = HotResource.get_all_operations(tosca_target) - # ignore Standard interface name, - # it is the only one supported in the translator anyway - op_name = args[2] - output_name = args[3] - if op_name in operations: - operation = operations[op_name] - if operation in self.hot_lookup: - matching_deploy = self.hot_lookup[operation] - matching_config_name = matching_deploy.\ - properties['config']['get_resource'] - matching_config = self.find_hot_resource( - matching_config_name) - if matching_config: - outputs = matching_config.properties.get('outputs') - if outputs is None: - outputs = [] - outputs.append({'name': output_name}) - matching_config.properties['outputs'] = outputs - return {'get_attr': [ - matching_deploy.name, - output_name - ]} - - @staticmethod - def _unfold_value(value, value_arg): - if value_arg is not None: - if isinstance(value, dict): - val = value.get(value_arg) - if val is not None: - return val - - index = utils.str_to_num(value_arg) - if isinstance(value, list) and index is not None: - return value[index] - return value - - def decipher_get_operation(self, args, current_tosca_node): - tosca_target = self._find_tosca_node(args[0], - current_tosca_node) - new_target = None - if tosca_target and len(args) > 2: - cap_or_req_name = args[1] - cap = tosca_target.get_capability(cap_or_req_name) - if cap: - new_target = cap + args = input_value.result().args + hot_target = self._find_hot_resource_for_tosca(args[0], resource) + + return hot_target.get_hot_attribute(args[1], args) + # most of artifacts logic should move to the parser + elif isinstance(input_value, dict) and 'get_artifact' in input_value: + get_artifact_args = input_value['get_artifact'] + + hot_target = self._find_hot_resource_for_tosca( + get_artifact_args[0], resource) + artifacts = TranslateNodeTemplates.get_all_artifacts( + hot_target.nodetemplate) + + if get_artifact_args[1] in artifacts: + artifact = artifacts[get_artifact_args[1]] + if artifact.get('type', None) == 'tosca.artifacts.File': + return {'get_file': artifact.get('file')} + elif isinstance(input_value, GetInput): + if isinstance(input_value.args, list) \ + and len(input_value.args) == 1: + return {'get_param': input_value.args[0]} else: - for req in tosca_target.requirements: - if cap_or_req_name in req: - new_target = self._find_tosca_node( - req[cap_or_req_name]) - cap = new_target.get_capability(cap_or_req_name) - if cap: - new_target = cap - break - - if new_target: - tosca_target = new_target - - prop_name = args[2] - prop_arg = args[3] if len(args) >= 4 else None - else: - prop_name = args[1] - prop_arg = args[2] if len(args) >= 3 else None + return {'get_param': input_value.args} - return tosca_target, prop_name, prop_arg + return input_value @staticmethod def get_all_artifacts(nodetemplate): @@ -591,29 +420,23 @@ class TranslateNodeTemplates(object): if resource.name == name: return resource - def _find_tosca_node(self, tosca_name, current_tosca_template=None): - tosca_node = None - if tosca_name == 'SELF': - tosca_node = current_tosca_template - if tosca_name == 'HOST' and current_tosca_template: - for req in current_tosca_template.requirements: - if 'host' in req: - tosca_node = self._find_tosca_node(req['host']) - - if tosca_node is None: - for node in self.nodetemplates: - if node.name == tosca_name: - tosca_node = node - break - return tosca_node + def _find_tosca_node(self, tosca_name): + for node in self.nodetemplates: + if node.name == tosca_name: + return node def _find_hot_resource_for_tosca(self, tosca_name, current_hot_resource=None): - current_tosca_resource = current_hot_resource.nodetemplate \ - if current_hot_resource else None - tosca_node = self._find_tosca_node(tosca_name, current_tosca_resource) - if tosca_node: - return self.hot_lookup[tosca_node] + if tosca_name == 'SELF': + return current_hot_resource + if tosca_name == 'HOST' and current_hot_resource is not None: + for req in current_hot_resource.nodetemplate.requirements: + if 'host' in req: + return self._find_hot_resource_for_tosca(req['host']) + + for node in self.nodetemplates: + if node.name == tosca_name: + return self.hot_lookup[node] return None diff --git a/tosca2heat/heat-translator/translator/hot/translate_outputs.py b/tosca2heat/heat-translator/translator/hot/translate_outputs.py index 87ec02a..4197cdd 100644 --- a/tosca2heat/heat-translator/translator/hot/translate_outputs.py +++ b/tosca2heat/heat-translator/translator/hot/translate_outputs.py @@ -33,8 +33,16 @@ class TranslateOutputs(object): def _translate_outputs(self): hot_outputs = [] for output in self.outputs: - hot_value = self.nodes.translate_param_value(output.value, None) - if hot_value is not None: - hot_outputs.append(HotOutput(output.name, hot_value, + if output.value.name == 'get_attribute': + get_parameters = output.value.args + hot_target = self.nodes.find_hot_resource(get_parameters[0]) + hot_value = hot_target.get_hot_attribute(get_parameters[1], + get_parameters) + hot_outputs.append(HotOutput(output.name, + hot_value, + output.description)) + else: + hot_outputs.append(HotOutput(output.name, + output.value, output.description)) return hot_outputs diff --git a/tosca2heat/heat-translator/translator/tests/test_tosca_hot_translation.py b/tosca2heat/heat-translator/translator/tests/test_tosca_hot_translation.py index 3e69d7a..bd98904 100644 --- a/tosca2heat/heat-translator/translator/tests/test_tosca_hot_translation.py +++ b/tosca2heat/heat-translator/translator/tests/test_tosca_hot_translation.py @@ -23,48 +23,35 @@ from translator.tests.base import TestCase class ToscaHotTranslationTest(TestCase): - def _test_successful_translation(self, tosca_file, hot_file, params=None): - if not params: - params = {} + def test_hot_translate_single_server(self): + tosca_file = '../tests/data/tosca_single_server.yaml' + hot_file = '../tests/data/hot_output/hot_single_server.yaml' + params = {'cpus': 1} diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, hot_file, params) self.assertEqual({}, diff, '<difference> : ' + json.dumps(diff, indent=4, separators=(', ', ': '))) - def _test_failed_translation(self, tosca_file, hot_file, params, msg, - msg_path, error_raise, error_collect): - if msg_path: - path = os.path.normpath(os.path.join( - os.path.dirname(os.path.realpath(__file__)), tosca_file)) - msg = msg % path - self.assertRaises( - error_raise, - TranslationUtils.compare_tosca_translation_with_hot, - tosca_file, hot_file, params) - ExceptionCollector.assertExceptionMessage(error_collect, msg) - - def test_hot_translate_single_server(self): - tosca_file = '../tests/data/tosca_single_server.yaml' - hot_file = '../tests/data/hot_output/hot_single_server.yaml' - params = {'cpus': 1} - self._test_successful_translation(tosca_file, hot_file, params) - def test_hot_translate_single_server_with_defaults(self): tosca_file = \ '../tests/data/tosca_single_server_with_defaults.yaml' - hot_file_with_input = '../tests/data/hot_output/' \ 'hot_single_server_with_defaults_with_input.yaml' - params1 = {'cpus': '1'} - self._test_successful_translation(tosca_file, hot_file_with_input, - params1) - hot_file_without_input = '../tests/data/hot_output/' \ 'hot_single_server_with_defaults_without_input.yaml' + + params1 = {'cpus': '1'} + diff1 = TranslationUtils.compare_tosca_translation_with_hot( + tosca_file, hot_file_with_input, params1) + self.assertEqual({}, diff1, '<difference> : ' + + json.dumps(diff1, indent=4, separators=(', ', ': '))) + params2 = {} - self._test_successful_translation(tosca_file, hot_file_without_input, - params2) + diff2 = TranslationUtils.compare_tosca_translation_with_hot( + tosca_file, hot_file_without_input, params2) + self.assertEqual({}, diff2, '<difference> : ' + + json.dumps(diff2, indent=4, separators=(', ', ': '))) def test_hot_translate_wordpress_single_instance(self): tosca_file = '../tests/data/tosca_single_instance_wordpress.yaml' @@ -76,17 +63,29 @@ class ToscaHotTranslationTest(TestCase): 'db_root_pwd': 'passw0rd', 'db_port': 3366, 'cpus': 8} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_helloworld(self): tosca_file = '../tests/data/tosca_helloworld.yaml' hot_file = '../tests/data/hot_output/hot_hello_world.yaml' - self._test_successful_translation(tosca_file, hot_file) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + {}) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_host_assignment(self): tosca_file = '../tests/data/test_host_assignment.yaml' hot_file = '../tests/data/hot_output/hot_host_assignment.yaml' - self._test_successful_translation(tosca_file, hot_file) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + {}) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_elk(self): tosca_file = '../tests/data/tosca_elk.yaml' @@ -94,7 +93,11 @@ class ToscaHotTranslationTest(TestCase): params = {'github_url': 'http://github.com/paypal/rest-api-sample-app-nodejs.git', 'my_cpus': 4} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_nodejs_mongodb_two_instances(self): tosca_file = '../tests/data/tosca_nodejs_mongodb_two_instances.yaml' @@ -103,7 +106,11 @@ class ToscaHotTranslationTest(TestCase): params = {'github_url': 'http://github.com/paypal/rest-api-sample-app-nodejs.git', 'my_cpus': 4} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_blockstorage_with_attachment(self): tosca_file = '../tests/data/storage/' \ @@ -114,7 +121,11 @@ class ToscaHotTranslationTest(TestCase): 'storage_location': '/dev/vdc', 'storage_size': '2000 MB', 'storage_snapshot_id': 'ssid'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_blockstorage_with_custom_relationship_type(self): tosca_file = '../tests/data/storage/' \ @@ -125,7 +136,11 @@ class ToscaHotTranslationTest(TestCase): 'storage_location': '/dev/vdc', 'storage_size': '1 GB', 'storage_snapshot_id': 'ssid'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_blockstorage_with_relationship_template(self): tosca_file = '../tests/data/storage/' \ @@ -135,7 +150,11 @@ class ToscaHotTranslationTest(TestCase): params = {'cpus': 1, 'storage_location': '/dev/vdc', 'storage_size': '1 GB'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_blockstorage_with_attachment_notation1(self): tosca_file = '../tests/data/storage/' \ @@ -148,11 +167,19 @@ class ToscaHotTranslationTest(TestCase): 'storage_location': 'some_folder', 'storage_size': '1 GB', 'storage_snapshot_id': 'ssid'} - + diff1 = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file1, + params) try: - self._test_successful_translation(tosca_file, hot_file1, params) + self.assertEqual({}, diff1, '<difference> : ' + + json.dumps(diff1, indent=4, + separators=(', ', ': '))) except Exception: - self._test_successful_translation(tosca_file, hot_file2, params) + diff2 = TranslationUtils.compare_tosca_translation_with_hot( + tosca_file, hot_file2, params) + self.assertEqual({}, diff2, '<difference> : ' + + json.dumps(diff2, indent=4, + separators=(', ', ': '))) def test_hot_translate_blockstorage_with_attachment_notation2(self): tosca_file = '../tests/data/storage/' \ @@ -165,10 +192,19 @@ class ToscaHotTranslationTest(TestCase): 'storage_location': '/dev/vdc', 'storage_size': '1 GB', 'storage_snapshot_id': 'ssid'} + diff1 = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file1, + params) try: - self._test_successful_translation(tosca_file, hot_file1, params) + self.assertEqual({}, diff1, '<difference> : ' + + json.dumps(diff1, indent=4, + separators=(', ', ': '))) except Exception: - self._test_successful_translation(tosca_file, hot_file2, params) + diff2 = TranslationUtils.compare_tosca_translation_with_hot( + tosca_file, hot_file2, params) + self.assertEqual({}, diff2, '<difference> : ' + + json.dumps(diff2, indent=4, + separators=(', ', ': '))) def test_hot_translate_multiple_blockstorage_with_attachment(self): tosca_file = '../tests/data/storage/' \ @@ -181,23 +217,40 @@ class ToscaHotTranslationTest(TestCase): 'storage_location': '/dev/vdc', 'storage_size': '1 GB', 'storage_snapshot_id': 'ssid'} + diff1 = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file1, + params) try: - self._test_successful_translation(tosca_file, hot_file1, params) + self.assertEqual({}, diff1, '<difference> : ' + + json.dumps(diff1, indent=4, + separators=(', ', ': '))) except Exception: - self._test_successful_translation(tosca_file, hot_file2, params) + diff2 = TranslationUtils.compare_tosca_translation_with_hot( + tosca_file, hot_file2, params) + self.assertEqual({}, diff2, '<difference> : ' + + json.dumps(diff2, indent=4, + separators=(', ', ': '))) def test_hot_translate_single_object_store(self): tosca_file = '../tests/data/storage/tosca_single_object_store.yaml' hot_file = '../tests/data/hot_output/hot_single_object_store.yaml' params = {'objectstore_name': 'myobjstore'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_one_server_one_network(self): tosca_file = '../tests/data/network/tosca_one_server_one_network.yaml' hot_file = '../tests/data/hot_output/network/' \ 'hot_one_server_one_network.yaml' params = {'network_name': 'private_net'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_server_on_existing_network(self): tosca_file = '../tests/data/network/' \ @@ -205,7 +258,11 @@ class ToscaHotTranslationTest(TestCase): hot_file = '../tests/data/hot_output/network/' \ 'hot_server_on_existing_network.yaml' params = {'network_name': 'private_net'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_two_servers_one_network(self): tosca_file = '../tests/data/network/tosca_two_servers_one_network.yaml' @@ -215,7 +272,11 @@ class ToscaHotTranslationTest(TestCase): 'network_cidr': '10.0.0.0/24', 'network_start_ip': '10.0.0.100', 'network_end_ip': '10.0.0.150'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_one_server_three_networks(self): tosca_file = '../tests/data/network/' \ @@ -223,20 +284,32 @@ class ToscaHotTranslationTest(TestCase): hot_file = '../tests/data/hot_output/network/' \ 'hot_one_server_three_networks.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_software_component(self): tosca_file = '../tests/data/tosca_software_component.yaml' hot_file = '../tests/data/hot_output/hot_software_component.yaml' params = {'cpus': '1', 'download_url': 'http://www.software.com/download'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_web_application(self): tosca_file = '../tests/data/tosca_web_application.yaml' hot_file = '../tests/data/hot_output/hot_web_application.yaml' params = {'cpus': '2', 'context_root': 'my_web_app'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_template_with_url_import(self): tosca_file = '../tests/data/' \ @@ -249,7 +322,11 @@ class ToscaHotTranslationTest(TestCase): 'db_root_pwd': 'passw0rd', 'db_port': 3366, 'cpus': 8} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_template_by_url_with_local_import(self): tosca_file = 'https://raw.githubusercontent.com/openstack/' \ @@ -263,7 +340,11 @@ class ToscaHotTranslationTest(TestCase): 'db_root_pwd': 'passw0rd', 'db_port': 3366, 'cpus': 8} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_template_by_url_with_local_abspath_import(self): tosca_file = 'https://raw.githubusercontent.com/openstack/' \ @@ -278,15 +359,17 @@ class ToscaHotTranslationTest(TestCase): 'db_root_pwd': 'passw0rd', 'db_port': 3366, 'cpus': 8} + + self.assertRaises( + ValidationError, + TranslationUtils.compare_tosca_translation_with_hot, + tosca_file, hot_file, params) expected_msg = _('Absolute file name "/tmp/wordpress.yaml" cannot be ' 'used in a URL-based input template "https://raw.' 'githubusercontent.com/openstack/heat-translator/' 'master/translator/tests/data/tosca_single_instance_' 'wordpress_with_local_abspath_import.yaml".') - msg_path = False - self._test_failed_translation(tosca_file, hot_file, params, - expected_msg, msg_path, ValidationError, - ImportError) + ExceptionCollector.assertExceptionMessage(ImportError, expected_msg) def test_hot_translate_template_by_url_with_url_import(self): tosca_url = 'https://raw.githubusercontent.com/openstack/' \ @@ -300,12 +383,20 @@ class ToscaHotTranslationTest(TestCase): 'db_root_pwd': 'passw0rd', 'db_port': 3366, 'cpus': 8} - self._test_successful_translation(tosca_url, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_url, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_translate_hello_world_csar(self): tosca_file = '../tests/data/csar_hello_world.zip' hot_file = '../tests/data/hot_output/hot_hello_world.yaml' - self._test_successful_translation(tosca_file, hot_file) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + {}) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_translate_single_instance_wordpress_csar(self): tosca_file = '../tests/data/csar_single_instance_wordpress.zip' @@ -317,7 +408,11 @@ class ToscaHotTranslationTest(TestCase): 'db_root_pwd': 'passw0rd', 'db_port': 3366, 'cpus': 8} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_translate_elk_csar_from_url(self): tosca_file = 'https://github.com/openstack/heat-translator/raw/' \ @@ -326,103 +421,150 @@ class ToscaHotTranslationTest(TestCase): params = {'github_url': 'http://github.com/paypal/rest-api-sample-app-nodejs.git', 'my_cpus': 4} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_translate_csar_not_zip(self): tosca_file = '../tests/data/csar_not_zip.zip' hot_file = '' params = {} - expected_msg = _('"%s" is not a valid zip file.') - msg_path = True - self._test_failed_translation(tosca_file, hot_file, params, - expected_msg, msg_path, ValidationError, - ValidationError) + + self.assertRaises( + ValidationError, + TranslationUtils.compare_tosca_translation_with_hot, + tosca_file, hot_file, params) + path = os.path.normpath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), tosca_file)) + expected_msg = _('"%s" is not a valid zip file.') % path + ExceptionCollector.assertExceptionMessage(ValidationError, + expected_msg) def test_translate_csar_metadata_not_yaml(self): tosca_file = '../tests/data/csar_metadata_not_yaml.zip' hot_file = '' params = {} + + self.assertRaises( + ValidationError, + TranslationUtils.compare_tosca_translation_with_hot, + tosca_file, hot_file, params) + path = os.path.normpath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), tosca_file)) expected_msg = _('The file "TOSCA-Metadata/TOSCA.meta" in the CSAR ' - '"%s" does not contain valid YAML content.') - msg_path = True - self._test_failed_translation(tosca_file, hot_file, params, - expected_msg, msg_path, ValidationError, - ValidationError) + '"%s" does not contain valid YAML content.') % path + ExceptionCollector.assertExceptionMessage(ValidationError, + expected_msg) def test_translate_csar_wrong_metadata_file(self): tosca_file = '../tests/data/csar_wrong_metadata_file.zip' hot_file = '' params = {} + + self.assertRaises( + ValidationError, + TranslationUtils.compare_tosca_translation_with_hot, + tosca_file, hot_file, params) + path = os.path.normpath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), tosca_file)) expected_msg = _('"%s" is not a valid CSAR as it does not contain the ' 'required file "TOSCA.meta" in the folder ' - '"TOSCA-Metadata".') - msg_path = True - self._test_failed_translation(tosca_file, hot_file, params, - expected_msg, msg_path, ValidationError, - ValidationError) + '"TOSCA-Metadata".') % path + ExceptionCollector.assertExceptionMessage(ValidationError, + expected_msg) def test_translate_csar_wordpress_invalid_import_path(self): tosca_file = '../tests/data/csar_wordpress_invalid_import_path.zip' hot_file = '' params = {} + + self.assertRaises( + ValidationError, + TranslationUtils.compare_tosca_translation_with_hot, + tosca_file, hot_file, params) expected_msg = _('Import ' '"Invalid_import_path/wordpress.yaml" is not valid.') - msg_path = False - self._test_failed_translation(tosca_file, hot_file, params, - expected_msg, msg_path, ValidationError, - ImportError) + ExceptionCollector.assertExceptionMessage(ImportError, expected_msg) def test_translate_csar_wordpress_invalid_script_url(self): tosca_file = '../tests/data/csar_wordpress_invalid_script_url.zip' hot_file = '' params = {} + + self.assertRaises( + ValidationError, + TranslationUtils.compare_tosca_translation_with_hot, + tosca_file, hot_file, params) expected_msg = _('The resource at ' '"https://raw.githubusercontent.com/openstack/' 'heat-translator/master/translator/tests/data/' 'custom_types/wordpress1.yaml" cannot be accessed.') - msg_path = False - self._test_failed_translation(tosca_file, hot_file, params, - expected_msg, msg_path, ValidationError, - URLException) + ExceptionCollector.assertExceptionMessage(URLException, expected_msg) def test_hot_translate_flavor_image(self): tosca_file = '../tests/data/test_tosca_flavor_and_image.yaml' hot_file = '../tests/data/hot_output/hot_flavor_and_image.yaml' - self._test_successful_translation(tosca_file, hot_file) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + {}) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_flavor_image_params(self): tosca_file = '../tests/data/test_tosca_flavor_and_image.yaml' hot_file = '../tests/data/hot_output/hot_flavor_and_image_params.yaml' params = {'key_name': 'paramkey'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_custom_type(self): tosca_file = '../tests/data/test_tosca_custom_type.yaml' hot_file = '../tests/data/hot_output/' \ 'hot_custom_type.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_custom_type_with_override(self): tosca_file = '../tests/data/test_tosca_custom_type_with_override.yaml' hot_file = '../tests/data/hot_output/' \ 'hot_custom_type_with_override.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_custom_type_with_param_override(self): tosca_file = '../tests/data/test_tosca_custom_type_with_override.yaml' hot_file = '../tests/data/hot_output/' \ 'hot_custom_type_with_param_override.yaml' params = {'install_path': '/home/custom/from/cli'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_artifact(self): tosca_file = '../tests/data/test_tosca_artifact.yaml' hot_file = '../tests/data/hot_output/' \ 'hot_artifact.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_without_tosca_os_version(self): tosca_file = '../tests/data/' \ @@ -430,13 +572,21 @@ class ToscaHotTranslationTest(TestCase): hot_file = '../tests/data/hot_output/' \ 'hot_single_server_without_tosca_os_version.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_helloworld_with_userkey(self): tosca_file = '../tests/data/tosca_helloworld.yaml' hot_file = '../tests/data/hot_output/hot_hello_world_userkey.yaml' params = {'key_name': 'userkey'} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_custom_networks_nodes_inline(self): tosca_file = '../tests/data/network/' \ @@ -444,7 +594,11 @@ class ToscaHotTranslationTest(TestCase): hot_file = '../tests/data/hot_output/network/' \ 'hot_custom_network_nodes.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_custom_networks_nodes_imports(self): tosca_file = '../tests/data/network/' \ @@ -452,46 +606,38 @@ class ToscaHotTranslationTest(TestCase): hot_file = '../tests/data/hot_output/network/' \ 'hot_custom_network_nodes.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_nfv_sample(self): tosca_file = '../tests/data/test_tosca_nfv_sample.yaml' hot_file = '../tests/data/hot_output/hot_nfv_sample.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) - - def test_hot_translate_nfv_vRNC(self): - tosca_file = "../tests/data/vRNC/Definitions/vRNC.yaml" - hot_file = '../tests/data/hot_output/hot_vRNC.yaml' - params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_translate_policy(self): tosca_file = '../tests/data/tosca_policies.yaml' hot_file = '../tests/data/hot_output/hot_policies.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) def test_hot_script_types(self): tosca_file = '../tests/data/test_tosca_script_types.yaml' hot_file = '../tests/data/hot_output/hot_script_types.yaml' params = {} - self._test_successful_translation(tosca_file, hot_file, params) - - def test_hot_interface_on_compute(self): - tosca_file = '../tests/data/test_tosca_interface_on_compute.yaml' - hot_file = '../tests/data/hot_output/hot_interface_on_compute.yaml' - params = {} - self._test_successful_translation(tosca_file, hot_file, params) - - def test_hot_get_functions_semantic(self): - tosca_file = '../tests/data/test_tosca_get_functions_semantic.yaml' - hot_file = '../tests/data/hot_output/hot_get_functions_semantic.yaml' - params = {} - self._test_successful_translation(tosca_file, hot_file, params) - - def test_hot_exchange_public_ssh_key(self): - tosca_file = '../tests/data/tosca_exchange_public_ssh_key.yaml' - hot_file = '../tests/data/hot_output/hot_exchange_public_ssh_key.yaml' - params = {} - self._test_successful_translation(tosca_file, hot_file, params) + diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, + hot_file, + params) + self.assertEqual({}, diff, '<difference> : ' + + json.dumps(diff, indent=4, separators=(', ', ': '))) diff --git a/tosca2heat/tosca-parser/setup.cfg b/tosca2heat/tosca-parser/setup.cfg index 747d88c..c109228 100644 --- a/tosca2heat/tosca-parser/setup.cfg +++ b/tosca2heat/tosca-parser/setup.cfg @@ -17,6 +17,7 @@ classifier = Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 [files] packages = diff --git a/tosca2heat/tosca-parser/test-requirements.txt b/tosca2heat/tosca-parser/test-requirements.txt index 70a261b..1bb6623 100644 --- a/tosca2heat/tosca-parser/test-requirements.txt +++ b/tosca2heat/tosca-parser/test-requirements.txt @@ -3,7 +3,6 @@ # process, which may cause wedges in the gate later. hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 -discover # BSD fixtures>=3.0.0 # Apache-2.0/BSD oslotest>=1.10.0 # Apache-2.0 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 diff --git a/tosca2heat/tosca-parser/toscaparser/common/exception.py b/tosca2heat/tosca-parser/toscaparser/common/exception.py index 34abe77..f67a277 100644 --- a/tosca2heat/tosca-parser/toscaparser/common/exception.py +++ b/tosca2heat/tosca-parser/toscaparser/common/exception.py @@ -67,6 +67,11 @@ class TOSCAException(Exception): TOSCAException._FATAL_EXCEPTION_FORMAT_ERRORS = flag +class UnsupportedTypeError(TOSCAException): + msg_fmt = _('Type "%(what)s" is valid TOSCA type' + ' but not supported at this time.') + + class MissingRequiredFieldError(TOSCAException): msg_fmt = _('%(what)s is missing required field "%(required)s".') diff --git a/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml b/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml index ede5fa5..6f3b331 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml +++ b/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml @@ -22,685 +22,688 @@ tosca_definitions_version: tosca_simple_yaml_1_0 # A Node Type is a reusable entity that defines the type of one or more # Node Templates. ########################################################################## -tosca.nodes.Root: - description: > - The TOSCA root node all other TOSCA base node types derive from. - attributes: - tosca_id: - type: string - tosca_name: - type: string - state: - type: string - capabilities: - feature: - type: tosca.capabilities.Node - requirements: - - dependency: - capability: tosca.capabilities.Node - node: tosca.nodes.Root - relationship: tosca.relationships.DependsOn - occurrences: [ 0, UNBOUNDED ] - interfaces: - Standard: - type: tosca.interfaces.node.lifecycle.Standard - -tosca.nodes.Compute: - derived_from: tosca.nodes.Root - attributes: - private_address: - type: string - public_address: - type: string - networks: - type: map - entry_schema: - type: tosca.datatypes.network.NetworkInfo - ports: - type: map - entry_schema: - type: tosca.datatypes.network.PortInfo - capabilities: +node_types: + tosca.nodes.Root: + description: > + The TOSCA root node all other TOSCA base node types derive from. + attributes: + tosca_id: + type: string + tosca_name: + type: string + state: + type: string + capabilities: + feature: + type: tosca.capabilities.Node + requirements: + - dependency: + capability: tosca.capabilities.Node + node: tosca.nodes.Root + relationship: tosca.relationships.DependsOn + occurrences: [ 0, UNBOUNDED ] + interfaces: + Standard: + type: tosca.interfaces.node.lifecycle.Standard + + tosca.nodes.Compute: + derived_from: tosca.nodes.Root + attributes: + private_address: + type: string + public_address: + type: string + networks: + type: map + entry_schema: + type: tosca.datatypes.network.NetworkInfo + ports: + type: map + entry_schema: + type: tosca.datatypes.network.PortInfo + capabilities: + host: + type: tosca.capabilities.Container + binding: + type: tosca.capabilities.network.Bindable + os: + type: tosca.capabilities.OperatingSystem + scalable: + type: tosca.capabilities.Scalable + requirements: + - local_storage: + capability: tosca.capabilities.Attachment + node: tosca.nodes.BlockStorage + relationship: tosca.relationships.AttachesTo + occurrences: [0, UNBOUNDED] + + tosca.nodes.SoftwareComponent: + derived_from: tosca.nodes.Root + properties: + # domain-specific software component version + component_version: + type: version + required: false + description: > + Software component version. + admin_credential: + type: tosca.datatypes.Credential + required: false + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.Compute + relationship: tosca.relationships.HostedOn + + tosca.nodes.DBMS: + derived_from: tosca.nodes.SoftwareComponent + properties: + port: + required: false + type: integer + description: > + The port the DBMS service will listen to for data and requests. + root_password: + required: false + type: string + description: > + The root password for the DBMS service. + capabilities: host: - type: tosca.capabilities.Container - binding: - type: tosca.capabilities.network.Bindable - os: - type: tosca.capabilities.OperatingSystem - scalable: - type: tosca.capabilities.Scalable - requirements: - - local_storage: - capability: tosca.capabilities.Attachment - node: tosca.nodes.BlockStorage - relationship: tosca.relationships.AttachesTo - occurrences: [0, UNBOUNDED] + type: tosca.capabilities.Container + valid_source_types: [tosca.nodes.Database] + + tosca.nodes.Database: + derived_from: tosca.nodes.Root + properties: + user: + required: false + type: string + description: > + User account name for DB administration + port: + required: false + type: integer + description: > + The port the database service will use to listen for incoming data and + requests. + name: + required: false + type: string + description: > + The name of the database. + password: + required: false + type: string + description: > + The password for the DB user account + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.DBMS + relationship: tosca.relationships.HostedOn + capabilities: + database_endpoint: + type: tosca.capabilities.Endpoint.Database + + tosca.nodes.WebServer: + derived_from: tosca.nodes.SoftwareComponent + capabilities: + data_endpoint: + type: tosca.capabilities.Endpoint + admin_endpoint: + type: tosca.capabilities.Endpoint.Admin + host: + type: tosca.capabilities.Container + valid_source_types: [tosca.nodes.WebApplication] -tosca.nodes.SoftwareComponent: - derived_from: tosca.nodes.Root - properties: - # domain-specific software component version - component_version: - type: version - required: false - description: > - Software component version. - admin_credential: - type: tosca.datatypes.Credential - required: false - requirements: - - host: - capability: tosca.capabilities.Container - node: tosca.nodes.Compute - relationship: tosca.relationships.HostedOn - -tosca.nodes.DBMS: - derived_from: tosca.nodes.SoftwareComponent - properties: - port: - required: false - type: integer - description: > - The port the DBMS service will listen to for data and requests. - root_password: - required: false - type: string - description: > - The root password for the DBMS service. - capabilities: - host: - type: tosca.capabilities.Container - valid_source_types: [tosca.nodes.Database] - -tosca.nodes.Database: - derived_from: tosca.nodes.Root - properties: - user: - required: false - type: string - description: > - User account name for DB administration - port: - required: false - type: integer - description: > - The port the database service will use to listen for incoming data and - requests. - name: - required: false - type: string - description: > - The name of the database. - password: - required: false - type: string - description: > - The password for the DB user account - requirements: - - host: - capability: tosca.capabilities.Container - node: tosca.nodes.DBMS - relationship: tosca.relationships.HostedOn - capabilities: - database_endpoint: - type: tosca.capabilities.Endpoint.Database - -tosca.nodes.WebServer: - derived_from: tosca.nodes.SoftwareComponent - capabilities: - data_endpoint: - type: tosca.capabilities.Endpoint - admin_endpoint: - type: tosca.capabilities.Endpoint.Admin - host: - type: tosca.capabilities.Container - valid_source_types: [tosca.nodes.WebApplication] - -tosca.nodes.WebApplication: - derived_from: tosca.nodes.Root - properties: - context_root: - type: string - required: false - requirements: - - host: - capability: tosca.capabilities.Container - node: tosca.nodes.WebServer - relationship: tosca.relationships.HostedOn - capabilities: - app_endpoint: - type: tosca.capabilities.Endpoint - -tosca.nodes.BlockStorage: - derived_from: tosca.nodes.Root - properties: - size: - type: scalar-unit.size - constraints: - - greater_or_equal: 1 MB - volume_id: - type: string - required: false - snapshot_id: - type: string - required: false - attributes: - volume_id: - type: string - capabilities: - attachment: - type: tosca.capabilities.Attachment - -tosca.nodes.network.Network: - derived_from: tosca.nodes.Root - description: > - The TOSCA Network node represents a simple, logical network service. - properties: - ip_version: - type: integer - required: false - default: 4 - constraints: - - valid_values: [ 4, 6 ] - description: > - The IP version of the requested network. Valid values are 4 for ipv4 - or 6 for ipv6. - cidr: - type: string - required: false - description: > - The cidr block of the requested network. - start_ip: - type: string - required: false - description: > - The IP address to be used as the start of a pool of addresses within - the full IP range derived from the cidr block. - end_ip: - type: string - required: false - description: > - The IP address to be used as the end of a pool of addresses within - the full IP range derived from the cidr block. - gateway_ip: - type: string - required: false - description: > - The gateway IP address. - network_name: - type: string - required: false - description: > - An identifier that represents an existing Network instance in the - underlying cloud infrastructure or can be used as the name of the - newly created network. If network_name is provided and no other - properties are provided (with exception of network_id), then an - existing network instance will be used. If network_name is provided - alongside with more properties then a new network with this name will - be created. - network_id: - type: string - required: false - description: > - An identifier that represents an existing Network instance in the - underlying cloud infrastructure. This property is mutually exclusive - with all other properties except network_name. This can be used alone - or together with network_name to identify an existing network. - segmentation_id: - type: string - required: false - description: > - A segmentation identifier in the underlying cloud infrastructure. - E.g. VLAN ID, GRE tunnel ID, etc.. - network_type: - type: string - required: false - description: > - It specifies the nature of the physical network in the underlying - cloud infrastructure. Examples are flat, vlan, gre or vxlan. - For flat and vlan types, physical_network should be provided too. - physical_network: - type: string - required: false - description: > - It identifies the physical network on top of which the network is - implemented, e.g. physnet1. This property is required if network_type - is flat or vlan. - dhcp_enabled: - type: boolean - required: false - default: true - description: > - Indicates should DHCP service be enabled on the network or not. - capabilities: - link: - type: tosca.capabilities.network.Linkable - -tosca.nodes.network.Port: - derived_from: tosca.nodes.Root - description: > - The TOSCA Port node represents a logical entity that associates between - Compute and Network normative types. The Port node type effectively - represents a single virtual NIC on the Compute node instance. - properties: - ip_address: - type: string - required: false - description: > - Allow the user to set a static IP. - order: - type: integer - required: false - default: 0 - constraints: - - greater_or_equal: 0 - description: > - The order of the NIC on the compute instance (e.g. eth2). - is_default: - type: boolean - required: false - default: false - description: > - If is_default=true this port will be used for the default gateway - route. Only one port that is associated to single compute node can - set as is_default=true. - ip_range_start: - type: string - required: false - description: > - Defines the starting IP of a range to be allocated for the compute - instances that are associated with this Port. - ip_range_end: - type: string - required: false - description: > - Defines the ending IP of a range to be allocated for the compute - instances that are associated with this Port. - attributes: - ip_address: - type: string - requirements: - - binding: - description: > - Binding requirement expresses the relationship between Port and - Compute nodes. Effectively it indicates that the Port will be - attached to specific Compute node instance - capability: tosca.capabilities.network.Bindable - relationship: tosca.relationships.network.BindsTo - node: tosca.nodes.Compute - - link: - description: > - Link requirement expresses the relationship between Port and Network - nodes. It indicates which network this port will connect to. - capability: tosca.capabilities.network.Linkable - relationship: tosca.relationships.network.LinksTo - node: tosca.nodes.network.Network - -tosca.nodes.ObjectStorage: - derived_from: tosca.nodes.Root - description: > - The TOSCA ObjectStorage node represents storage that provides the ability - to store data as objects (or BLOBs of data) without consideration for the - underlying filesystem or devices - properties: - name: - type: string - required: true - description: > - The logical name of the object store (or container). - size: - type: scalar-unit.size - required: false - constraints: - - greater_or_equal: 0 GB - description: > - The requested initial storage size. - maxsize: - type: scalar-unit.size - required: false - constraints: - - greater_or_equal: 0 GB - description: > - The requested maximum storage size. - capabilities: - storage_endpoint: - type: tosca.capabilities.Endpoint - -tosca.nodes.LoadBalancer: - derived_from: tosca.nodes.Root - properties: - algorithm: - type: string - required: false - status: experimental - capabilities: - client: - type: tosca.capabilities.Endpoint.Public - occurrences: [0, UNBOUNDED] - description: the Floating (IP) client’s on the public network can connect to - requirements: - - application: - capability: tosca.capabilities.Endpoint - relationship: tosca.relationships.RoutesTo + tosca.nodes.WebApplication: + derived_from: tosca.nodes.Root + properties: + context_root: + type: string + required: false + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.WebServer + relationship: tosca.relationships.HostedOn + capabilities: + app_endpoint: + type: tosca.capabilities.Endpoint + + tosca.nodes.BlockStorage: + derived_from: tosca.nodes.Root + properties: + size: + type: scalar-unit.size + constraints: + - greater_or_equal: 1 MB + volume_id: + type: string + required: false + snapshot_id: + type: string + required: false + attributes: + volume_id: + type: string + capabilities: + attachment: + type: tosca.capabilities.Attachment + + tosca.nodes.network.Network: + derived_from: tosca.nodes.Root + description: > + The TOSCA Network node represents a simple, logical network service. + properties: + ip_version: + type: integer + required: false + default: 4 + constraints: + - valid_values: [ 4, 6 ] + description: > + The IP version of the requested network. Valid values are 4 for ipv4 + or 6 for ipv6. + cidr: + type: string + required: false + description: > + The cidr block of the requested network. + start_ip: + type: string + required: false + description: > + The IP address to be used as the start of a pool of addresses within + the full IP range derived from the cidr block. + end_ip: + type: string + required: false + description: > + The IP address to be used as the end of a pool of addresses within + the full IP range derived from the cidr block. + gateway_ip: + type: string + required: false + description: > + The gateway IP address. + network_name: + type: string + required: false + description: > + An identifier that represents an existing Network instance in the + underlying cloud infrastructure or can be used as the name of the + newly created network. If network_name is provided and no other + properties are provided (with exception of network_id), then an + existing network instance will be used. If network_name is provided + alongside with more properties then a new network with this name will + be created. + network_id: + type: string + required: false + description: > + An identifier that represents an existing Network instance in the + underlying cloud infrastructure. This property is mutually exclusive + with all other properties except network_name. This can be used alone + or together with network_name to identify an existing network. + segmentation_id: + type: string + required: false + description: > + A segmentation identifier in the underlying cloud infrastructure. + E.g. VLAN ID, GRE tunnel ID, etc.. + network_type: + type: string + required: false + description: > + It specifies the nature of the physical network in the underlying + cloud infrastructure. Examples are flat, vlan, gre or vxlan. + For flat and vlan types, physical_network should be provided too. + physical_network: + type: string + required: false + description: > + It identifies the physical network on top of which the network is + implemented, e.g. physnet1. This property is required if network_type + is flat or vlan. + dhcp_enabled: + type: boolean + required: false + default: true + description: > + Indicates should DHCP service be enabled on the network or not. + capabilities: + link: + type: tosca.capabilities.network.Linkable + + tosca.nodes.network.Port: + derived_from: tosca.nodes.Root + description: > + The TOSCA Port node represents a logical entity that associates between + Compute and Network normative types. The Port node type effectively + represents a single virtual NIC on the Compute node instance. + properties: + ip_address: + type: string + required: false + description: > + Allow the user to set a static IP. + order: + type: integer + required: false + default: 0 + constraints: + - greater_or_equal: 0 + description: > + The order of the NIC on the compute instance (e.g. eth2). + is_default: + type: boolean + required: false + default: false + description: > + If is_default=true this port will be used for the default gateway + route. Only one port that is associated to single compute node can + set as is_default=true. + ip_range_start: + type: string + required: false + description: > + Defines the starting IP of a range to be allocated for the compute + instances that are associated with this Port. + ip_range_end: + type: string + required: false + description: > + Defines the ending IP of a range to be allocated for the compute + instances that are associated with this Port. + attributes: + ip_address: + type: string + requirements: + - binding: + description: > + Binding requirement expresses the relationship between Port and + Compute nodes. Effectively it indicates that the Port will be + attached to specific Compute node instance + capability: tosca.capabilities.network.Bindable + relationship: tosca.relationships.network.BindsTo + node: tosca.nodes.Compute + - link: + description: > + Link requirement expresses the relationship between Port and Network + nodes. It indicates which network this port will connect to. + capability: tosca.capabilities.network.Linkable + relationship: tosca.relationships.network.LinksTo + node: tosca.nodes.network.Network + + tosca.nodes.ObjectStorage: + derived_from: tosca.nodes.Root + description: > + The TOSCA ObjectStorage node represents storage that provides the ability + to store data as objects (or BLOBs of data) without consideration for the + underlying filesystem or devices + properties: + name: + type: string + required: true + description: > + The logical name of the object store (or container). + size: + type: scalar-unit.size + required: false + constraints: + - greater_or_equal: 0 GB + description: > + The requested initial storage size. + maxsize: + type: scalar-unit.size + required: false + constraints: + - greater_or_equal: 0 GB + description: > + The requested maximum storage size. + capabilities: + storage_endpoint: + type: tosca.capabilities.Endpoint + + tosca.nodes.LoadBalancer: + derived_from: tosca.nodes.Root + properties: + algorithm: + type: string + required: false + status: experimental + capabilities: + client: + type: tosca.capabilities.Endpoint.Public occurrences: [0, UNBOUNDED] - description: Connection to one or more load balanced applications - -tosca.nodes.Container.Application: - derived_from: tosca.nodes.Root - requirements: - - host: - capability: tosca.capabilities.Container - node: tosca.nodes.Container.Runtime - relationship: tosca.relationships.HostedOn - -tosca.nodes.Container.Runtime: - derived_from: tosca.nodes.SoftwareComponent - capabilities: - host: - type: tosca.capabilities.Container - scalable: - type: tosca.capabilities.Scalable - -tosca.nodes.Container.Application.Docker: - derived_from: tosca.nodes.Container.Application - requirements: - - host: - capability: tosca.capabilities.Container.Docker + description: the Floating (IP) client’s on the public network can connect to + requirements: + - application: + capability: tosca.capabilities.Endpoint + relationship: tosca.relationships.RoutesTo + occurrences: [0, UNBOUNDED] + description: Connection to one or more load balanced applications + + tosca.nodes.Container.Application: + derived_from: tosca.nodes.Root + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.Container.Runtime + relationship: tosca.relationships.HostedOn + + tosca.nodes.Container.Runtime: + derived_from: tosca.nodes.SoftwareComponent + capabilities: + host: + type: tosca.capabilities.Container + scalable: + type: tosca.capabilities.Scalable + + tosca.nodes.Container.Application.Docker: + derived_from: tosca.nodes.Container.Application + requirements: + - host: + capability: tosca.capabilities.Container.Docker ########################################################################## # Relationship Type. # A Relationship Type is a reusable entity that defines the type of one # or more relationships between Node Types or Node Templates. ########################################################################## -tosca.relationships.Root: - description: > - The TOSCA root Relationship Type all other TOSCA base Relationship Types - derive from. - attributes: - tosca_id: - type: string - tosca_name: - type: string - interfaces: - Configure: - type: tosca.interfaces.relationship.Configure - -tosca.relationships.DependsOn: - derived_from: tosca.relationships.Root - -tosca.relationships.HostedOn: - derived_from: tosca.relationships.Root - valid_target_types: [ tosca.capabilities.Container ] - -tosca.relationships.ConnectsTo: - derived_from: tosca.relationships.Root - valid_target_types: [ tosca.capabilities.Endpoint ] - credential: - type: tosca.datatypes.Credential - required: false - -tosca.relationships.AttachesTo: - derived_from: tosca.relationships.Root - valid_target_types: [ tosca.capabilities.Attachment ] - properties: - location: - required: true - type: string - constraints: - - min_length: 1 - device: +relationship_types: + tosca.relationships.Root: + description: > + The TOSCA root Relationship Type all other TOSCA base Relationship Types + derive from. + attributes: + tosca_id: + type: string + tosca_name: + type: string + interfaces: + Configure: + type: tosca.interfaces.relationship.Configure + + tosca.relationships.DependsOn: + derived_from: tosca.relationships.Root + + tosca.relationships.HostedOn: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.Container ] + + tosca.relationships.ConnectsTo: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.Endpoint ] + credential: + type: tosca.datatypes.Credential required: false - type: string -tosca.relationships.RoutesTo: - derived_from: tosca.relationships.ConnectsTo - valid_target_types: [ tosca.capabilities.Endpoint ] + tosca.relationships.AttachesTo: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.Attachment ] + properties: + location: + required: true + type: string + constraints: + - min_length: 1 + device: + required: false + type: string -tosca.relationships.network.LinksTo: - derived_from: tosca.relationships.DependsOn - valid_target_types: [ tosca.capabilities.network.Linkable ] + tosca.relationships.RoutesTo: + derived_from: tosca.relationships.ConnectsTo + valid_target_types: [ tosca.capabilities.Endpoint ] -tosca.relationships.network.BindsTo: - derived_from: tosca.relationships.DependsOn - valid_target_types: [ tosca.capabilities.network.Bindable ] + tosca.relationships.network.LinksTo: + derived_from: tosca.relationships.DependsOn + valid_target_types: [ tosca.capabilities.network.Linkable ] + + tosca.relationships.network.BindsTo: + derived_from: tosca.relationships.DependsOn + valid_target_types: [ tosca.capabilities.network.Bindable ] ########################################################################## # Capability Type. # A Capability Type is a reusable entity that describes a kind of # capability that a Node Type can declare to expose. ########################################################################## -tosca.capabilities.Root: - description: > - The TOSCA root Capability Type all other TOSCA base Capability Types - derive from. - -tosca.capabilities.Node: - derived_from: tosca.capabilities.Root - -tosca.capabilities.Container: - derived_from: tosca.capabilities.Root - properties: - num_cpus: - required: false - type: integer - constraints: - - greater_or_equal: 1 - cpu_frequency: - required: false - type: scalar-unit.frequency - constraints: - - greater_or_equal: 0.1 GHz - disk_size: - required: false - type: scalar-unit.size - constraints: - - greater_or_equal: 0 MB - mem_size: - required: false - type: scalar-unit.size - constraints: - - greater_or_equal: 0 MB - -tosca.capabilities.Endpoint: - derived_from: tosca.capabilities.Root - properties: - protocol: - type: string - required: true - default: tcp - port: - type: tosca.datatypes.network.PortDef - required: false - secure: - type: boolean - required: false - default: false - url_path: - type: string - required: false - port_name: - type: string - required: false - network_name: - type: string - required: false - initiator: - type: string - required: false - default: source - constraints: - - valid_values: [source, target, peer] - ports: - type: map - required: false - constraints: - - min_length: 1 - entry_schema: - type: tosca.datatypes.network.PortSpec - attributes: - ip_address: - type: string - -tosca.capabilities.Endpoint.Admin: - derived_from: tosca.capabilities.Endpoint - properties: - secure: - type: boolean - default: true - constraints: - - equal: true - -tosca.capabilities.Endpoint.Public: - derived_from: tosca.capabilities.Endpoint - properties: - # Change the default network_name to use the first public network found - network_name: - type: string - default: PUBLIC - constraints: - - equal: PUBLIC - floating: - description: > - Indicates that the public address should be allocated from a pool of - floating IPs that are associated with the network. - type: boolean - default: false - status: experimental - dns_name: - description: The optional name to register with DNS - type: string - required: false - status: experimental - -tosca.capabilities.Scalable: - derived_from: tosca.capabilities.Root - properties: - min_instances: - type: integer - required: true - default: 1 - description: > - This property is used to indicate the minimum number of instances - that should be created for the associated TOSCA Node Template by - a TOSCA orchestrator. - max_instances: - type: integer - required: true - default: 1 - description: > - This property is used to indicate the maximum number of instances - that should be created for the associated TOSCA Node Template by - a TOSCA orchestrator. - default_instances: - type: integer - required: false - description: > - An optional property that indicates the requested default number - of instances that should be the starting number of instances a - TOSCA orchestrator should attempt to allocate. - The value for this property MUST be in the range between the values - set for min_instances and max_instances properties. - -tosca.capabilities.Endpoint.Database: - derived_from: tosca.capabilities.Endpoint - -tosca.capabilities.Attachment: - derived_from: tosca.capabilities.Root - -tosca.capabilities.network.Linkable: - derived_from: tosca.capabilities.Root - description: > - A node type that includes the Linkable capability indicates that it can - be pointed by tosca.relationships.network.LinksTo relationship type, which - represents an association relationship between Port and Network node types. - -tosca.capabilities.network.Bindable: - derived_from: tosca.capabilities.Root - description: > - A node type that includes the Bindable capability indicates that it can - be pointed by tosca.relationships.network.BindsTo relationship type, which - represents a network association relationship between Port and Compute node - types. - -tosca.capabilities.OperatingSystem: - derived_from: tosca.capabilities.Root - properties: - architecture: - required: false - type: string - description: > - The host Operating System (OS) architecture. - type: - required: false - type: string - description: > - The host Operating System (OS) type. - distribution: - required: false - type: string - description: > - The host Operating System (OS) distribution. Examples of valid values - for an “type” of “Linux” would include: - debian, fedora, rhel and ubuntu. - version: - required: false - type: version - description: > - The host Operating System version. - -tosca.capabilities.Container.Docker: - derived_from: tosca.capabilities.Container - properties: - version: - type: list - required: false - entry_schema: +capability_types: + tosca.capabilities.Root: + description: > + The TOSCA root Capability Type all other TOSCA base Capability Types + derive from. + + tosca.capabilities.Node: + derived_from: tosca.capabilities.Root + + tosca.capabilities.Container: + derived_from: tosca.capabilities.Root + properties: + num_cpus: + required: false + type: integer + constraints: + - greater_or_equal: 1 + cpu_frequency: + required: false + type: scalar-unit.frequency + constraints: + - greater_or_equal: 0.1 GHz + disk_size: + required: false + type: scalar-unit.size + constraints: + - greater_or_equal: 0 MB + mem_size: + required: false + type: scalar-unit.size + constraints: + - greater_or_equal: 0 MB + + tosca.capabilities.Endpoint: + derived_from: tosca.capabilities.Root + properties: + protocol: + type: string + required: true + default: tcp + port: + type: tosca.datatypes.network.PortDef + required: false + secure: + type: boolean + required: false + default: false + url_path: + type: string + required: false + port_name: + type: string + required: false + network_name: + type: string + required: false + initiator: + type: string + required: false + default: source + constraints: + - valid_values: [source, target, peer] + ports: + type: map + required: false + constraints: + - min_length: 1 + entry_schema: + type: tosca.datatypes.network.PortSpec + attributes: + ip_address: + type: string + + tosca.capabilities.Endpoint.Admin: + derived_from: tosca.capabilities.Endpoint + properties: + secure: + type: boolean + default: true + constraints: + - equal: true + + tosca.capabilities.Endpoint.Public: + derived_from: tosca.capabilities.Endpoint + properties: + # Change the default network_name to use the first public network found + network_name: + type: string + default: PUBLIC + constraints: + - equal: PUBLIC + floating: + description: > + Indicates that the public address should be allocated from a pool of + floating IPs that are associated with the network. + type: boolean + default: false + status: experimental + dns_name: + description: The optional name to register with DNS + type: string + required: false + status: experimental + + tosca.capabilities.Scalable: + derived_from: tosca.capabilities.Root + properties: + min_instances: + type: integer + required: true + default: 1 + description: > + This property is used to indicate the minimum number of instances + that should be created for the associated TOSCA Node Template by + a TOSCA orchestrator. + max_instances: + type: integer + required: true + default: 1 + description: > + This property is used to indicate the maximum number of instances + that should be created for the associated TOSCA Node Template by + a TOSCA orchestrator. + default_instances: + type: integer + required: false + description: > + An optional property that indicates the requested default number + of instances that should be the starting number of instances a + TOSCA orchestrator should attempt to allocate. + The value for this property MUST be in the range between the values + set for min_instances and max_instances properties. + + tosca.capabilities.Endpoint.Database: + derived_from: tosca.capabilities.Endpoint + + tosca.capabilities.Attachment: + derived_from: tosca.capabilities.Root + + tosca.capabilities.network.Linkable: + derived_from: tosca.capabilities.Root + description: > + A node type that includes the Linkable capability indicates that it can + be pointed by tosca.relationships.network.LinksTo relationship type, which + represents an association relationship between Port and Network node types. + + tosca.capabilities.network.Bindable: + derived_from: tosca.capabilities.Root + description: > + A node type that includes the Bindable capability indicates that it can + be pointed by tosca.relationships.network.BindsTo relationship type, which + represents a network association relationship between Port and Compute node + types. + + tosca.capabilities.OperatingSystem: + derived_from: tosca.capabilities.Root + properties: + architecture: + required: false + type: string + description: > + The host Operating System (OS) architecture. + type: + required: false + type: string + description: > + The host Operating System (OS) type. + distribution: + required: false + type: string + description: > + The host Operating System (OS) distribution. Examples of valid values + for an “type” of “Linux” would include: + debian, fedora, rhel and ubuntu. + version: + required: false type: version - description: > - The Docker version capability. - publish_all: - type: boolean - default: false - required: false - description: > - Indicates that all ports (ranges) listed in the dockerfile - using the EXPOSE keyword be published. - publish_ports: - type: list - entry_schema: - type: PortSpec - required: false - description: > - List of ports mappings from source (Docker container) - to target (host) ports to publish. - expose_ports: - type: list - entry_schema: - type: PortSpec - required: false - description: > - List of ports mappings from source (Docker container) to expose - to other Docker containers (not accessible outside host). - volumes: - type: list - entry_schema: + description: > + The host Operating System version. + + tosca.capabilities.Container.Docker: + derived_from: tosca.capabilities.Container + properties: + version: + type: list + required: false + entry_schema: + type: version + description: > + The Docker version capability. + publish_all: + type: boolean + default: false + required: false + description: > + Indicates that all ports (ranges) listed in the dockerfile + using the EXPOSE keyword be published. + publish_ports: + type: list + entry_schema: + type: PortSpec + required: false + description: > + List of ports mappings from source (Docker container) + to target (host) ports to publish. + expose_ports: + type: list + entry_schema: + type: PortSpec + required: false + description: > + List of ports mappings from source (Docker container) to expose + to other Docker containers (not accessible outside host). + volumes: + type: list + entry_schema: + type: string + required: false + description: > + The dockerfile VOLUME command which is used to enable access + from the Docker container to a directory on the host machine. + host_id: type: string - required: false - description: > - The dockerfile VOLUME command which is used to enable access - from the Docker container to a directory on the host machine. - host_id: - type: string - required: false - description: > - The optional identifier of an existing host resource - that should be used to run this container on. - volume_id: - type: string - required: false - description: > - The optional identifier of an existing storage volume (resource) - that should be used to create the container's mount point(s) on. + required: false + description: > + The optional identifier of an existing host resource + that should be used to run this container on. + volume_id: + type: string + required: false + description: > + The optional identifier of an existing storage volume (resource) + that should be used to create the container's mount point(s) on. ########################################################################## # Interfaces Type. @@ -708,129 +711,131 @@ tosca.capabilities.Container.Docker: # definitions for a modelable entity (e.g., a Node or Relationship Type) # as defined within the TOSCA Simple Profile specification. ########################################################################## -tosca.interfaces.node.lifecycle.Standard: - create: - description: Standard lifecycle create operation. - configure: - description: Standard lifecycle configure operation. - start: - description: Standard lifecycle start operation. - stop: - description: Standard lifecycle stop operation. - delete: - description: Standard lifecycle delete operation. - -tosca.interfaces.relationship.Configure: - pre_configure_source: - description: Operation to pre-configure the source endpoint. - pre_configure_target: - description: Operation to pre-configure the target endpoint. - post_configure_source: - description: Operation to post-configure the source endpoint. - post_configure_target: - description: Operation to post-configure the target endpoint. - add_target: - description: Operation to add a target node. - remove_target: - description: Operation to remove a target node. - add_source: > - description: Operation to notify the target node of a source node which - is now available via a relationship. - description: - target_changed: > - description: Operation to notify source some property or attribute of the - target changed +interface_types: + tosca.interfaces.node.lifecycle.Standard: + create: + description: Standard lifecycle create operation. + configure: + description: Standard lifecycle configure operation. + start: + description: Standard lifecycle start operation. + stop: + description: Standard lifecycle stop operation. + delete: + description: Standard lifecycle delete operation. + + tosca.interfaces.relationship.Configure: + pre_configure_source: + description: Operation to pre-configure the source endpoint. + pre_configure_target: + description: Operation to pre-configure the target endpoint. + post_configure_source: + description: Operation to post-configure the source endpoint. + post_configure_target: + description: Operation to post-configure the target endpoint. + add_target: + description: Operation to add a target node. + remove_target: + description: Operation to remove a target node. + add_source: > + description: Operation to notify the target node of a source node which + is now available via a relationship. + description: + target_changed: > + description: Operation to notify source some property or attribute of the + target changed ########################################################################## # Data Type. # A Datatype is a complex data type declaration which contains other # complex or simple data types. ########################################################################## -tosca.datatypes.Root: - description: > - The TOSCA root Data Type all other TOSCA base Data Types derive from - -tosca.datatypes.network.NetworkInfo: - derived_from: tosca.datatypes.Root - properties: - network_name: - type: string - network_id: - type: string - addresses: - type: list - entry_schema: - type: string - -tosca.datatypes.network.PortInfo: - derived_from: tosca.datatypes.Root - properties: - port_name: - type: string - port_id: - type: string - network_id: - type: string - mac_address: - type: string - addresses: - type: list - entry_schema: - type: string - -tosca.datatypes.network.PortDef: - derived_from: tosca.datatypes.Root - type: integer - constraints: - - in_range: [ 1, 65535 ] - -tosca.datatypes.network.PortSpec: - derived_from: tosca.datatypes.Root - properties: - protocol: - type: string - required: true - default: tcp - constraints: - - valid_values: [ udp, tcp, igmp ] - target: - type: PortDef - required: false - target_range: - type: range - required: false - constraints: - - in_range: [ 1, 65535 ] - source: - type: PortDef - required: false - source_range: - type: range - required: false - constraints: - - in_range: [ 1, 65535 ] - -tosca.datatypes.Credential: - derived_from: tosca.datatypes.Root - properties: - protocol: - type: string - required: false - token_type: - type: string - default: password - required: true - token: - type: string - required: true - keys: - type: map - entry_schema: +data_types: + tosca.datatypes.Root: + description: > + The TOSCA root Data Type all other TOSCA base Data Types derive from + + tosca.datatypes.network.NetworkInfo: + derived_from: tosca.datatypes.Root + properties: + network_name: type: string - required: false - user: - type: string - required: false + network_id: + type: string + addresses: + type: list + entry_schema: + type: string + + tosca.datatypes.network.PortInfo: + derived_from: tosca.datatypes.Root + properties: + port_name: + type: string + port_id: + type: string + network_id: + type: string + mac_address: + type: string + addresses: + type: list + entry_schema: + type: string + + tosca.datatypes.network.PortDef: + derived_from: tosca.datatypes.Root + type: integer + constraints: + - in_range: [ 1, 65535 ] + + tosca.datatypes.network.PortSpec: + derived_from: tosca.datatypes.Root + properties: + protocol: + type: string + required: true + default: tcp + constraints: + - valid_values: [ udp, tcp, igmp ] + target: + type: PortDef + required: false + target_range: + type: range + required: false + constraints: + - in_range: [ 1, 65535 ] + source: + type: PortDef + required: false + source_range: + type: range + required: false + constraints: + - in_range: [ 1, 65535 ] + + tosca.datatypes.Credential: + derived_from: tosca.datatypes.Root + properties: + protocol: + type: string + required: false + token_type: + type: string + default: password + required: true + token: + type: string + required: true + keys: + type: map + entry_schema: + type: string + required: false + user: + type: string + required: false ########################################################################## # Artifact Type. @@ -838,56 +843,57 @@ tosca.datatypes.Credential: # files which Node Types or Node Templates can have dependent relationships # and used during operations such as during installation or deployment. ########################################################################## -tosca.artifacts.Root: - description: > - The TOSCA Artifact Type all other TOSCA Artifact Types derive from - properties: - version: version - -tosca.artifacts.File: - derived_from: tosca.artifacts.Root - -tosca.artifacts.Deployment: - derived_from: tosca.artifacts.Root - description: TOSCA base type for deployment artifacts - -tosca.artifacts.Deployment.Image: - derived_from: tosca.artifacts.Deployment - -tosca.artifacts.Deployment.Image.VM: - derived_from: tosca.artifacts.Deployment.Image - -tosca.artifacts.Implementation: - derived_from: tosca.artifacts.Root - description: TOSCA base type for implementation artifacts - -tosca.artifacts.Implementation.Bash: - derived_from: tosca.artifacts.Implementation - description: Script artifact for the Unix Bash shell - mime_type: application/x-sh - file_ext: [ sh ] - -tosca.artifacts.Implementation.Python: - derived_from: tosca.artifacts.Implementation - description: Artifact for the interpreted Python language - mime_type: application/x-python - file_ext: [ py ] - -tosca.artifacts.Deployment.Image.Container.Docker: - derived_from: tosca.artifacts.Deployment.Image - description: Docker container image - -tosca.artifacts.Deployment.Image.VM.ISO: - derived_from: tosca.artifacts.Deployment.Image - description: Virtual Machine (VM) image in ISO disk format - mime_type: application/octet-stream - file_ext: [ iso ] - -tosca.artifacts.Deployment.Image.VM.QCOW2: - derived_from: tosca.artifacts.Deployment.Image - description: Virtual Machine (VM) image in QCOW v2 standard disk format - mime_type: application/octet-stream - file_ext: [ qcow2 ] +artifact_types: + tosca.artifacts.Root: + description: > + The TOSCA Artifact Type all other TOSCA Artifact Types derive from + properties: + version: version + + tosca.artifacts.File: + derived_from: tosca.artifacts.Root + + tosca.artifacts.Deployment: + derived_from: tosca.artifacts.Root + description: TOSCA base type for deployment artifacts + + tosca.artifacts.Deployment.Image: + derived_from: tosca.artifacts.Deployment + + tosca.artifacts.Deployment.Image.VM: + derived_from: tosca.artifacts.Deployment.Image + + tosca.artifacts.Implementation: + derived_from: tosca.artifacts.Root + description: TOSCA base type for implementation artifacts + + tosca.artifacts.Implementation.Bash: + derived_from: tosca.artifacts.Implementation + description: Script artifact for the Unix Bash shell + mime_type: application/x-sh + file_ext: [ sh ] + + tosca.artifacts.Implementation.Python: + derived_from: tosca.artifacts.Implementation + description: Artifact for the interpreted Python language + mime_type: application/x-python + file_ext: [ py ] + + tosca.artifacts.Deployment.Image.Container.Docker: + derived_from: tosca.artifacts.Deployment.Image + description: Docker container image + + tosca.artifacts.Deployment.Image.VM.ISO: + derived_from: tosca.artifacts.Deployment.Image + description: Virtual Machine (VM) image in ISO disk format + mime_type: application/octet-stream + file_ext: [ iso ] + + tosca.artifacts.Deployment.Image.VM.QCOW2: + derived_from: tosca.artifacts.Deployment.Image + description: Virtual Machine (VM) image in QCOW v2 standard disk format + mime_type: application/octet-stream + file_ext: [ qcow2 ] ########################################################################## # Policy Type. @@ -895,38 +901,39 @@ tosca.artifacts.Deployment.Image.VM.QCOW2: # an implied relationship and need to be orchestrated or managed together # to achieve some result. ########################################################################## -tosca.policies.Root: - description: The TOSCA Policy Type all other TOSCA Policy Types derive from. - -tosca.policies.Placement: - derived_from: tosca.policies.Root - description: The TOSCA Policy Type definition that is used to govern - placement of TOSCA nodes or groups of nodes. - -tosca.policies.Placement.Colocate: - derived_from: tosca.policies.Placement - description: The TOSCA Policy Type definition that is used to govern - colocate placement of TOSCA nodes or groups of nodes. - -tosca.policies.Placement.Antilocate: - derived_from: tosca.policies.Placement - description: The TOSCA Policy Type definition that is used to govern - anti-locate placement of TOSCA nodes or groups of nodes. - -tosca.policies.Scaling: - derived_from: tosca.policies.Root - description: The TOSCA Policy Type definition that is used to govern - scaling of TOSCA nodes or groups of nodes. - -tosca.policies.Update: - derived_from: tosca.policies.Root - description: The TOSCA Policy Type definition that is used to govern - update of TOSCA nodes or groups of nodes. - -tosca.policies.Performance: - derived_from: tosca.policies.Root - description: The TOSCA Policy Type definition that is used to declare - performance requirements for TOSCA nodes or groups of nodes. +policy_types: + tosca.policies.Root: + description: The TOSCA Policy Type all other TOSCA Policy Types derive from. + + tosca.policies.Placement: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to govern + placement of TOSCA nodes or groups of nodes. + + tosca.policies.Placement.Colocate: + derived_from: tosca.policies.Placement + description: The TOSCA Policy Type definition that is used to govern + colocate placement of TOSCA nodes or groups of nodes. + + tosca.policies.Placement.Antilocate: + derived_from: tosca.policies.Placement + description: The TOSCA Policy Type definition that is used to govern + anti-locate placement of TOSCA nodes or groups of nodes. + + tosca.policies.Scaling: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to govern + scaling of TOSCA nodes or groups of nodes. + + tosca.policies.Update: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to govern + update of TOSCA nodes or groups of nodes. + + tosca.policies.Performance: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to declare + performance requirements for TOSCA nodes or groups of nodes. ########################################################################## # Group Type. @@ -934,8 +941,9 @@ tosca.policies.Performance: # implied membership relationship and may need to be orchestrated or # managed together to achieve some result. ########################################################################## -tosca.groups.Root: - description: The TOSCA Group Type all other TOSCA Group Types derive from - interfaces: - Standard: - type: tosca.interfaces.node.lifecycle.Standard +group_types: + tosca.groups.Root: + description: The TOSCA Group Type all other TOSCA Group Types derive from + interfaces: + Standard: + type: tosca.interfaces.node.lifecycle.Standard diff --git a/tosca2heat/tosca-parser/toscaparser/elements/artifacttype.py b/tosca2heat/tosca-parser/toscaparser/elements/artifacttype.py index 887e99a..bf379fc 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/artifacttype.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/artifacttype.py @@ -31,7 +31,6 @@ class ArtifactTypeDef(StatefulEntityType): parent_artif = self.parent_type.type if self.parent_type else None if parent_artif: while parent_artif != 'tosca.artifacts.Root': - # only support normative artifact, shall be modified future artifacts[parent_artif] = self.TOSCA_DEF[parent_artif] parent_artif = artifacts[parent_artif]['derived_from'] return artifacts @@ -44,8 +43,6 @@ class ArtifactTypeDef(StatefulEntityType): partifact_entity = self.derived_from(self.defs) if partifact_entity: return ArtifactTypeDef(partifact_entity, self.custom_def) - else: - return None def get_artifact(self, name): '''Return the definition of an artifact field by name.''' diff --git a/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py b/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py index 9b9787b..d7fcb18 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/entity_type.py @@ -29,6 +29,11 @@ class EntityType(object): ('derived_from', 'properties', 'attributes', 'requirements', 'interfaces', 'capabilities', 'type', 'artifacts') + TOSCA_DEF_SECTIONS = ['node_types', 'data_types', 'artifact_types', + 'group_types', 'relationship_types', + 'capability_types', 'interface_types', + 'policy_types'] + '''TOSCA definition file.''' TOSCA_DEF_FILE = os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -36,7 +41,15 @@ class EntityType(object): loader = toscaparser.utils.yamlparser.load_yaml - TOSCA_DEF = loader(TOSCA_DEF_FILE) + TOSCA_DEF_LOAD_AS_IS = loader(TOSCA_DEF_FILE) + + # Map of definition with pre-loaded values of TOSCA_DEF_FILE_SECTIONS + TOSCA_DEF = {} + for section in TOSCA_DEF_SECTIONS: + if section in TOSCA_DEF_LOAD_AS_IS.keys(): + value = TOSCA_DEF_LOAD_AS_IS[section] + for key in value.keys(): + TOSCA_DEF[key] = value[key] RELATIONSHIP_TYPE = (DEPENDSON, HOSTEDON, CONNECTSTO, ATTACHESTO, LINKSTO, BINDSTO) = \ @@ -148,5 +161,11 @@ def update_definitions(version): extension_defs_file = exttools.get_defs_file(version) loader = toscaparser.utils.yamlparser.load_yaml - - EntityType.TOSCA_DEF.update(loader(extension_defs_file)) + nfv_def_file = loader(extension_defs_file) + nfv_def = {} + for section in EntityType.TOSCA_DEF_SECTIONS: + if section in nfv_def_file.keys(): + value = nfv_def_file[section] + for key in value.keys(): + nfv_def[key] = value[key] + EntityType.TOSCA_DEF.update(nfv_def) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/grouptype.py b/tosca2heat/tosca-parser/toscaparser/elements/grouptype.py index 5587f05..02c285a 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/grouptype.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/grouptype.py @@ -59,8 +59,6 @@ class GroupType(StatefulEntityType): pgroup_entity = self.derived_from(self.defs) if pgroup_entity: return GroupType(pgroup_entity, self.custom_def) - else: - return None @property def description(self): diff --git a/tosca2heat/tosca-parser/toscaparser/elements/statefulentitytype.py b/tosca2heat/tosca-parser/toscaparser/elements/statefulentitytype.py index 47496f7..be9933e 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/statefulentitytype.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/statefulentitytype.py @@ -15,6 +15,7 @@ from toscaparser.common.exception import InvalidTypeError from toscaparser.elements.attribute_definition import AttributeDef from toscaparser.elements.entity_type import EntityType from toscaparser.elements.property_definition import PropertyDef +from toscaparser.unsupportedtype import UnsupportedType class StatefulEntityType(EntityType): @@ -31,17 +32,20 @@ class StatefulEntityType(EntityType): def __init__(self, entitytype, prefix, custom_def=None): entire_entitytype = entitytype - if not entitytype.startswith(self.TOSCA): - entire_entitytype = prefix + entitytype - if entire_entitytype in list(self.TOSCA_DEF.keys()): - self.defs = self.TOSCA_DEF[entire_entitytype] - entitytype = entire_entitytype - elif custom_def and entitytype in list(custom_def.keys()): - self.defs = custom_def[entitytype] - else: + if UnsupportedType.validate_type(entire_entitytype): self.defs = None - ExceptionCollector.appendException( - InvalidTypeError(what=entitytype)) + else: + if not entitytype.startswith(self.TOSCA): + entire_entitytype = prefix + entitytype + if entire_entitytype in list(self.TOSCA_DEF.keys()): + self.defs = self.TOSCA_DEF[entire_entitytype] + entitytype = entire_entitytype + elif custom_def and entitytype in list(custom_def.keys()): + self.defs = custom_def[entitytype] + else: + self.defs = None + ExceptionCollector.appendException( + InvalidTypeError(what=entitytype)) self.type = entitytype def get_properties_def_objects(self): diff --git a/tosca2heat/tosca-parser/toscaparser/entity_template.py b/tosca2heat/tosca-parser/toscaparser/entity_template.py index 7488c33..7ce8cec 100644 --- a/tosca2heat/tosca-parser/toscaparser/entity_template.py +++ b/tosca2heat/tosca-parser/toscaparser/entity_template.py @@ -21,6 +21,7 @@ from toscaparser.elements.nodetype import NodeType from toscaparser.elements.policytype import PolicyType from toscaparser.elements.relationshiptype import RelationshipType from toscaparser.properties import Property +from toscaparser.unsupportedtype import UnsupportedType from toscaparser.utils.gettextutils import _ @@ -44,8 +45,9 @@ class EntityTemplate(object): self.entity_tpl = template self.custom_def = custom_def self._validate_field(self.entity_tpl) + type = self.entity_tpl.get('type') + UnsupportedType.validate_type(type) if entity_name == 'node_type': - type = self.entity_tpl.get('type') self.type_definition = NodeType(type, custom_def) \ if type is not None else None if entity_name == 'relationship_type': @@ -57,10 +59,10 @@ class EntityTemplate(object): type = self.entity_tpl['relationship'] else: type = self.entity_tpl['type'] + UnsupportedType.validate_type(type) self.type_definition = RelationshipType(type, None, custom_def) if entity_name == 'policy_type': - type = self.entity_tpl.get('type') if not type: msg = (_('Policy definition of "%(pname)s" must have' ' a "type" ''attribute.') % dict(pname=name)) @@ -69,7 +71,6 @@ class EntityTemplate(object): self.type_definition = PolicyType(type, custom_def) if entity_name == 'group_type': - type = self.entity_tpl.get('type') self.type_definition = GroupType(type, custom_def) \ if type is not None else None self._properties = None diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml index dc986e5..365d70e 100644 --- a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml @@ -22,94 +22,94 @@ tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 # A Node Type is a reusable entity that defines the type of one or more # Node Templates. ########################################################################## +node_types: + tosca.nodes.nfv.VNF: + derived_from: tosca.nodes.Root # Or should this be its own top - level type? + properties: + id: + type: string + description: ID of this VNF + vendor: + type: string + description: name of the vendor who generate this VNF + version: + type: version + description: version of the software for this VNF + requirements: + - virtualLink: + capability: tosca.capabilities.nfv.VirtualLinkable + relationship: tosca.relationships.nfv.VirtualLinksTo + node: tosca.nodes.nfv.VL + + tosca.nodes.nfv.VDU: + derived_from: tosca.nodes.Compute + capabilities: + high_availability: + type: tosca.capabilities.nfv.HA + virtualbinding: + type: tosca.capabilities.nfv.VirtualBindable + monitoring_parameter: + type: tosca.capabilities.nfv.Metric + requirements: + - high_availability: + capability: tosca.capabilities.nfv.HA + relationship: tosca.relationships.nfv.HA + node: tosca.nodes.nfv.VDU + occurrences: [ 0, 1 ] + + tosca.nodes.nfv.CP: + derived_from: tosca.nodes.network.Port + properties: + type: + type: string + required: false + requirements: + - virtualLink: + capability: tosca.capabilities.nfv.VirtualLinkable + relationship: tosca.relationships.nfv.VirtualLinksTo + node: tosca.nodes.nfv.VL + - virtualBinding: + capability: tosca.capabilities.nfv.VirtualBindable + relationship: tosca.relationships.nfv.VirtualBindsTo + node: tosca.nodes.nfv.VDU + attributes: + address: + type: string -tosca.nodes.nfv.VNF: - derived_from: tosca.nodes.Root # Or should this be its own top - level type? - properties: - id: - type: string - description: ID of this VNF - vendor: - type: string - description: name of the vendor who generate this VNF - version: - type: version - description: version of the software for this VNF - requirements: - - virtualLink: - capability: tosca.capabilities.nfv.VirtualLinkable - relationship: tosca.relationships.nfv.VirtualLinksTo - node: tosca.nodes.nfv.VL - -tosca.nodes.nfv.VDU: - derived_from: tosca.nodes.Compute - capabilities: - high_availability: - type: tosca.capabilities.nfv.HA - virtualbinding: - type: tosca.capabilities.nfv.VirtualBindable - monitoring_parameter: - type: tosca.capabilities.nfv.Metric - requirements: - - high_availability: - capability: tosca.capabilities.nfv.HA - relationship: tosca.relationships.nfv.HA - node: tosca.nodes.nfv.VDU - occurrences: [ 0, 1 ] - -tosca.nodes.nfv.CP: - derived_from: tosca.nodes.network.Port - properties: - type: - type: string - required: false - requirements: - - virtualLink: - capability: tosca.capabilities.nfv.VirtualLinkable - relationship: tosca.relationships.nfv.VirtualLinksTo - node: tosca.nodes.nfv.VL - - virtualBinding: - capability: tosca.capabilities.nfv.VirtualBindable - relationship: tosca.relationships.nfv.VirtualBindsTo - node: tosca.nodes.nfv.VDU - attributes: - address: - type: string - -tosca.nodes.nfv.VL: - derived_from: tosca.nodes.network.Network - properties: - vendor: - type: string - required: true - description: name of the vendor who generate this VL - capabilities: - virtual_linkable: - type: tosca.capabilities.nfv.VirtualLinkable - -tosca.nodes.nfv.VL.ELine: - derived_from: tosca.nodes.nfv.VL - capabilities: - virtual_linkable: - occurrences: 2 - -tosca.nodes.nfv.VL.ELAN: - derived_from: tosca.nodes.nfv.VL - -tosca.nodes.nfv.VL.ETree: - derived_from: tosca.nodes.nfv.VL - -tosca.nodes.nfv.FP: - derived_from: tosca.nodes.Root - properties: - policy: - type: string - required: false - description: name of the vendor who generate this VL - requirements: - - forwarder: - capability: tosca.capabilities.nfv.Forwarder - relationship: tosca.relationships.nfv.ForwardsTo + tosca.nodes.nfv.VL: + derived_from: tosca.nodes.network.Network + properties: + vendor: + type: string + required: true + description: name of the vendor who generate this VL + capabilities: + virtual_linkable: + type: tosca.capabilities.nfv.VirtualLinkable + + tosca.nodes.nfv.VL.ELine: + derived_from: tosca.nodes.nfv.VL + capabilities: + virtual_linkable: + occurrences: 2 + + tosca.nodes.nfv.VL.ELAN: + derived_from: tosca.nodes.nfv.VL + + tosca.nodes.nfv.VL.ETree: + derived_from: tosca.nodes.nfv.VL + + tosca.nodes.nfv.FP: + derived_from: tosca.nodes.Root + properties: + policy: + type: string + required: false + description: name of the vendor who generate this VL + requirements: + - forwarder: + capability: tosca.capabilities.nfv.Forwarder + relationship: tosca.relationships.nfv.ForwardsTo ########################################################################## # Relationship Type. @@ -117,25 +117,26 @@ tosca.nodes.nfv.FP: # or more relationships between Node Types or Node Templates. ########################################################################## -tosca.relationships.nfv.VirtualLinksTo: - derived_from: tosca.relationships.network.LinksTo - valid_target_types: [ tosca.capabilities.nfv.VirtualLinkable ] +relationship_types: + tosca.relationships.nfv.VirtualLinksTo: + derived_from: tosca.relationships.network.LinksTo + valid_target_types: [ tosca.capabilities.nfv.VirtualLinkable ] -tosca.relationships.nfv.VirtualBindsTo: - derived_from: tosca.relationships.network.BindsTo - valid_target_types: [ tosca.capabilities.nfv.VirtualBindable ] + tosca.relationships.nfv.VirtualBindsTo: + derived_from: tosca.relationships.network.BindsTo + valid_target_types: [ tosca.capabilities.nfv.VirtualBindable ] -tosca.relationships.nfv.HA: - derived_from: tosca.relationships.Root - valid_target_types: [ tosca.capabilities.nfv.HA ] + tosca.relationships.nfv.HA: + derived_from: tosca.relationships.Root + valid_target_types: [ tosca.capabilities.nfv.HA ] -tosca.relationships.nfv.Monitor: - derived_from: tosca.relationships.ConnectsTo - valid_target_types: [ tosca.capabilities.nfv.Metric ] + tosca.relationships.nfv.Monitor: + derived_from: tosca.relationships.ConnectsTo + valid_target_types: [ tosca.capabilities.nfv.Metric ] -tosca.relationships.nfv.ForwardsTo: - derived_from: tosca.relationships.root - valid_target_types: [ tosca.capabilities.nfv.Forwarder] + tosca.relationships.nfv.ForwardsTo: + derived_from: tosca.relationships.root + valid_target_types: [ tosca.capabilities.nfv.Forwarder] ########################################################################## # Capability Type. @@ -143,27 +144,28 @@ tosca.relationships.nfv.ForwardsTo: # capability that a Node Type can declare to expose. ########################################################################## -tosca.capabilities.nfv.VirtualLinkable: - derived_from: tosca.capabilities.network.Linkable +capability_types: + tosca.capabilities.nfv.VirtualLinkable: + derived_from: tosca.capabilities.network.Linkable -tosca.capabilities.nfv.VirtualBindable: - derived_from: tosca.capabilities.network.Bindable + tosca.capabilities.nfv.VirtualBindable: + derived_from: tosca.capabilities.network.Bindable -tosca.capabilities.nfv.HA: - derived_from: tosca.capabilities.Root - valid_source_types: [ tosca.nodes.nfv.VDU ] + tosca.capabilities.nfv.HA: + derived_from: tosca.capabilities.Root + valid_source_types: [ tosca.nodes.nfv.VDU ] -tosca.capabilities.nfv.HA.ActiveActive: - derived_from: tosca.capabilities.nfv.HA + tosca.capabilities.nfv.HA.ActiveActive: + derived_from: tosca.capabilities.nfv.HA -tosca.capabilities.nfv.HA.ActivePassive: - derived_from: tosca.capabilities.nfv.HA + tosca.capabilities.nfv.HA.ActivePassive: + derived_from: tosca.capabilities.nfv.HA -tosca.capabilities.nfv.Metric: - derived_from: tosca.capabilities.Root + tosca.capabilities.nfv.Metric: + derived_from: tosca.capabilities.Root -tosca.capabilities.nfv.Forwarder: - derived_from: tosca.capabilities.Root + tosca.capabilities.nfv.Forwarder: + derived_from: tosca.capabilities.Root ########################################################################## # Interfaces Type. @@ -196,42 +198,43 @@ tosca.capabilities.nfv.Forwarder: # Group Type # ########################################################################## -tosca.groups.nfv.VNFFG: - derived_from: tosca.groups.Root - - properties: - vendor: - type: string - required: true - description: name of the vendor who generate this VNFFG - - version: - type: string - required: true - description: version of this VNFFG - - number_of_endpoints: - type: integer - required: true - description: count of the external endpoints included in this VNFFG - - dependent_virtual_link: - type: list - entry_schema: - type: string - required: true - description: Reference to a VLD used in this Forwarding Graph +group_types: + tosca.groups.nfv.VNFFG: + derived_from: tosca.groups.Root - connection_point: - type: list - entry_schema: + properties: + vendor: type: string - required: true - description: Reference to Connection Points forming the VNFFG + required: true + description: name of the vendor who generate this VNFFG - constituent_vnfs: - type: list - entry_schema: + version: type: string - required: true - description: Reference to a list of VNFD used in this VNF Forwarding Graph + required: true + description: version of this VNFFG + + number_of_endpoints: + type: integer + required: true + description: count of the external endpoints included in this VNFFG + + dependent_virtual_link: + type: list + entry_schema: + type: string + required: true + description: Reference to a VLD used in this Forwarding Graph + + connection_point: + type: list + entry_schema: + type: string + required: true + description: Reference to Connection Points forming the VNFFG + + constituent_vnfs: + type: list + entry_schema: + type: string + required: true + description: Reference to a list of VNFD used in this VNF Forwarding Graph diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py index e229d2f..e87b672 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatpl.py @@ -706,6 +706,8 @@ class ToscaTemplateTest(TestCase): tosca = ToscaTemplate(tosca_tpl) for policy in tosca.topology_template.policies: + self.assertTrue( + policy.is_derived_from("tosca.policies.Root")) if policy.name == 'my_compute_placement_policy': self.assertEqual('tosca.policies.Placement', policy.type) self.assertEqual(['my_server_1', 'my_server_2'], @@ -726,6 +728,8 @@ class ToscaTemplateTest(TestCase): tosca = ToscaTemplate(tosca_tpl) for policy in tosca.topology_template.policies: + self.assertTrue( + policy.is_derived_from("tosca.policies.Root")) if policy.name == 'my_groups_placement': self.assertEqual('mycompany.mytypes.myScalingPolicy', policy.type) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py index 57daf7e..5a8f37a 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py @@ -98,6 +98,22 @@ class ToscaTemplateValidationTest(TestCase): 'field "derived1_from". Refer to the definition to ' 'verify valid values.')) + def test_unsupported_type(self): + tpl_snippet = ''' + node_templates: + invalid_type: + type: tosca.test.invalidtype + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + ''' + tpl = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet)) + err = self.assertRaises(exception.UnsupportedTypeError, + TopologyTemplate, tpl, None) + expectedmessage = _('Type "tosca.test.invalidtype" is valid' + ' TOSCA type but not supported at this time.') + self.assertEqual(expectedmessage, err.__str__()) + def test_inputs(self): tpl_snippet1 = ''' inputs: diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_utils.py b/tosca2heat/tosca-parser/toscaparser/tests/test_utils.py index 215fa0a..fca024d 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_utils.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_utils.py @@ -29,6 +29,7 @@ class UrlUtilsTest(TestCase): self.assertFalse(self.url_utils.validate_url("github.com")) self.assertFalse(self.url_utils.validate_url("123")) self.assertFalse(self.url_utils.validate_url("a/b/c")) + self.assertTrue(self.url_utils.validate_url("file:///dir/file.ext")) def test_urlutils_join_url(self): self.assertEqual( diff --git a/tosca2heat/tosca-parser/toscaparser/topology_template.py b/tosca2heat/tosca-parser/toscaparser/topology_template.py index 100a06b..d7fd443 100644 --- a/tosca2heat/tosca-parser/toscaparser/topology_template.py +++ b/tosca2heat/tosca-parser/toscaparser/topology_template.py @@ -26,6 +26,7 @@ from toscaparser.substitution_mappings import SubstitutionMappings from toscaparser.tpl_relationship_graph import ToscaGraph from toscaparser.utils.gettextutils import _ + # Topology template key names SECTIONS = (DESCRIPTION, INPUTS, NODE_TEMPLATES, RELATIONSHIP_TEMPLATES, OUTPUTS, GROUPS, diff --git a/tosca2heat/tosca-parser/toscaparser/unsupportedtype.py b/tosca2heat/tosca-parser/toscaparser/unsupportedtype.py new file mode 100644 index 0000000..0d2f1d6 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/unsupportedtype.py @@ -0,0 +1,38 @@ +# 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 UnsupportedTypeError +from toscaparser.utils.gettextutils import _ + +log = logging.getLogger('tosca') + + +class UnsupportedType(object): + + un_supported_types = ['tosca.test.invalidtype', + 'tosca.nodes.Storage.ObjectStorage', + 'tosca.nodes.Storage.BlockStorage'] + + def __init__(self): + pass + + @staticmethod + def validate_type(entitytype): + if entitytype in UnsupportedType.un_supported_types: + ExceptionCollector.appendException(UnsupportedTypeError( + what=_('%s') + % entitytype)) + return True + else: + return False diff --git a/tosca2heat/tosca-parser/toscaparser/utils/urlutils.py b/tosca2heat/tosca-parser/toscaparser/utils/urlutils.py index 34b6032..546acca 100644 --- a/tosca2heat/tosca-parser/toscaparser/utils/urlutils.py +++ b/tosca2heat/tosca-parser/toscaparser/utils/urlutils.py @@ -35,7 +35,11 @@ class UrlUtils(object): URL. """ parsed = urlparse(path) - return bool(parsed.scheme) and bool(parsed.netloc) + if parsed.scheme == 'file': + # If the url uses the file scheme netloc will be "" + return True + else: + return bool(parsed.scheme) and bool(parsed.netloc) @staticmethod def join_url(url, relative_path): |