From 16a300e3e79a147beeb0198c4ccaa22b13707c35 Mon Sep 17 00:00:00 2001 From: Bob HADDLETON Date: Fri, 4 Dec 2015 14:32:58 -0600 Subject: [PATCH] Add support for Simple NFV Profile * This patchset implements an extensions capability to allow the parser to easily support additional TOSCA profiles * The first supported extension is the TOSCA NFV profile Change-Id: I00606c8f0075c205da4bc221c3274e0921b630a8 Partially implements: blueprint tosca-nfv-support --- toscaparser/common/exception.py | 12 + toscaparser/elements/entity_type.py | 10 + toscaparser/extensions/__init__.py | 0 toscaparser/extensions/exttools.py | 88 +++++++ .../nfv/TOSCA_nfv_definition_1_0.yaml | 242 ++++++++++++++++++ toscaparser/extensions/nfv/__init__.py | 0 toscaparser/extensions/nfv/nfv.py | 17 ++ toscaparser/extensions/nfv/tests/__init__.py | 0 .../nfv/tests/data/tosca_helloworld_nfv.yaml | 28 ++ .../nfv/tests/test_tosca_nfv_tpl.py | 29 +++ toscaparser/tests/test_toscatpl.py | 5 +- toscaparser/tosca_template.py | 18 +- 12 files changed, 445 insertions(+), 4 deletions(-) create mode 100644 toscaparser/extensions/__init__.py create mode 100644 toscaparser/extensions/exttools.py create mode 100644 toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml create mode 100644 toscaparser/extensions/nfv/__init__.py create mode 100644 toscaparser/extensions/nfv/nfv.py create mode 100644 toscaparser/extensions/nfv/tests/__init__.py create mode 100644 toscaparser/extensions/nfv/tests/data/tosca_helloworld_nfv.yaml create mode 100644 toscaparser/extensions/nfv/tests/test_tosca_nfv_tpl.py diff --git a/toscaparser/common/exception.py b/toscaparser/common/exception.py index f41b65c6..8c09f484 100644 --- a/toscaparser/common/exception.py +++ b/toscaparser/common/exception.py @@ -105,6 +105,18 @@ class URLException(TOSCAException): msg_fmt = _('%(what)s') +class ToscaExtImportError(TOSCAException): + msg_fmt = _('Unable to import extension "%(ext_name)s". ' + 'Check to see that it exists and has no ' + 'language definition errors.') + + +class ToscaExtAttributeError(TOSCAException): + msg_fmt = _('Missing attribute in extension "%(ext_name)s". ' + 'Check to see that it has required attributes ' + '"%(attrs)s" defined.') + + class ExceptionCollector(object): exceptions = [] diff --git a/toscaparser/elements/entity_type.py b/toscaparser/elements/entity_type.py index 54aaa7e7..bd185c32 100644 --- a/toscaparser/elements/entity_type.py +++ b/toscaparser/elements/entity_type.py @@ -12,6 +12,7 @@ import logging import os +from toscaparser.extensions.exttools import ExtTools import toscaparser.utils.yamlparser log = logging.getLogger('tosca') @@ -113,3 +114,12 @@ class EntityType(object): inherited.update(value) value.update(inherited) return value + + +def update_definitions(version): + exttools = ExtTools() + extension_defs_file = exttools.get_defs_file(version) + + loader = toscaparser.utils.yamlparser.load_yaml + + EntityType.TOSCA_DEF.update(loader(extension_defs_file)) diff --git a/toscaparser/extensions/__init__.py b/toscaparser/extensions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/toscaparser/extensions/exttools.py b/toscaparser/extensions/exttools.py new file mode 100644 index 00000000..963b958b --- /dev/null +++ b/toscaparser/extensions/exttools.py @@ -0,0 +1,88 @@ +# +# 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 importlib +import logging +import os + +from toscaparser.common.exception import ToscaExtAttributeError +from toscaparser.common.exception import ToscaExtImportError + +log = logging.getLogger("tosca.model") + +REQUIRED_ATTRIBUTES = ['VERSION', 'DEFS_FILE'] + + +class ExtTools(object): + def __init__(self): + self.EXTENSION_INFO = self._load_extensions() + + def _load_extensions(self): + '''Dynamically load all the extensions .''' + extensions = {} + + # Use the absolute path of the class path + abs_path = os.path.dirname(os.path.abspath(__file__)) + + extdirs = [e for e in os.listdir(abs_path) if + not e.startswith('tests') and + not e.endswith('.pyc') and not e.endswith('.py')] + + for e in extdirs: + log.info(e) + extpath = abs_path + '/' + e + # Grab all the extension files in the given path + ext_files = [f for f in os.listdir(extpath) if f.endswith('.py') + and not f.startswith('__init__')] + + # For each module, pick out the target translation class + for f in ext_files: + log.info(f) + ext_name = 'toscaparser/extensions/' + e + '/' + f.strip('.py') + ext_name = ext_name.replace('/', '.') + try: + extinfo = importlib.import_module(ext_name) + version = getattr(extinfo, 'VERSION') + defs_file = extpath + '/' + getattr(extinfo, 'DEFS_FILE') + + # Sections is an optional attribute + sections = getattr(extinfo, 'SECTIONS', ()) + + extensions[version] = {'sections': sections, + 'defs_file': defs_file} + except ImportError: + raise ToscaExtImportError(ext_name=ext_name) + except AttributeError: + attrs = ', '.join(REQUIRED_ATTRIBUTES) + raise ToscaExtAttributeError(ext_name=ext_name, + attrs=attrs) + + return extensions + + def get_versions(self): + return self.EXTENSION_INFO.keys() + + def get_sections(self): + sections = {} + for version in self.EXTENSION_INFO.keys(): + sections[version] = self.EXTENSION_INFO[version]['sections'] + + return sections + + def get_defs_file(self, version): + versiondata = self.EXTENSION_INFO.get(version) + + if versiondata: + return versiondata.get('defs_file') + else: + return None diff --git a/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml b/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml new file mode 100644 index 00000000..9fca0f1c --- /dev/null +++ b/toscaparser/extensions/nfv/TOSCA_nfv_definition_1_0.yaml @@ -0,0 +1,242 @@ +# 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. + +########################################################################## +# The content of this file reflects TOSCA NFV Profile in YAML version +# 1.0.0. It describes the definition for TOSCA NFV types including Node Type, +# Relationship Type, Capability Type and Interfaces. +########################################################################## +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +########################################################################## +# Node Type. +# A Node Type is a reusable entity that defines the type of one or more +# Node Templates. +########################################################################## + +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 + +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 + occurrences: [ 0, 1 ] + +tosca.nodes.nfv.CP: + derived_from: tosca.nodes.network.Port + properties: + type: + type: string + required: false + requirements: + - virtualLink: + capability: tosca.capabilities.VirtualLinkable + - virtualBinding: + capability: tosca.capabilities.nfv.VirtualBindable + attributes: + IP_address: + type: string + required: false + +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 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.nfv.VirtualLinksTo: + derived_from: tosca.relationships.ConnectsTo + valid_target_types: [ tosca.capabilities.nfv.VirtualLinkable ] + +tosca.relationships.nfv.VirtualBindsTo: + derived_from: tosca.relationships.ConnectsTo + 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.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] + +########################################################################## +# 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.nfv.VirtualLinkable: + derived_from: tosca.capabilities.Root + +tosca.capabilities.nfv.VirtualBindable: + derived_from: tosca.capabilities.Root + +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.ActivePassive: + derived_from: tosca.capabilities.nfv.HA + +tosca.capabilities.nfv.Metric: + derived_from: tosca.capabilities.Root + +tosca.capabilities.nfv.Forwarder: + derived_from: tosca.capabilities.Root + +########################################################################## + # Interfaces Type. + # The Interfaces element describes a list of one or more interface + # definitions for a modelable entity (e.g., a Node or Relationship Type) + # as defined within the TOSCA Simple Profile specification. +########################################################################## + +########################################################################## + # Data Type. + # A Datatype is a complex data type declaration which contains other + # complex or simple data types. +########################################################################## + +########################################################################## + # Artifact Type. + # An Artifact Type is a reusable entity that defines the type of one or more + # files which Node Types or Node Templates can have dependent relationships + # and used during operations such as during installation or deployment. +########################################################################## + +########################################################################## + # Policy Type. + # TOSCA Policy Types represent logical grouping of TOSCA nodes that have + # an implied relationship and need to be orchestrated or managed together + # to achieve some result. +########################################################################## + +########################################################################## + # 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 + + 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 + + targets: + type: list + entry_schema: + type: string + required: false + description: list of Network Forwarding Path within the VNFFG + + requirements: + forwarder: + -capability: tosca.capabilities.nfv.Forwarder + diff --git a/toscaparser/extensions/nfv/__init__.py b/toscaparser/extensions/nfv/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/toscaparser/extensions/nfv/nfv.py b/toscaparser/extensions/nfv/nfv.py new file mode 100644 index 00000000..937afac3 --- /dev/null +++ b/toscaparser/extensions/nfv/nfv.py @@ -0,0 +1,17 @@ +# 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. + +# VERSION and DEFS_FILE are required for all extensions + +VERSION = 'tosca_simple_profile_for_nfv_1_0_0' + +DEFS_FILE = "TOSCA_nfv_definition_1_0.yaml" diff --git a/toscaparser/extensions/nfv/tests/__init__.py b/toscaparser/extensions/nfv/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/toscaparser/extensions/nfv/tests/data/tosca_helloworld_nfv.yaml b/toscaparser/extensions/nfv/tests/data/tosca_helloworld_nfv.yaml new file mode 100644 index 00000000..28235a00 --- /dev/null +++ b/toscaparser/extensions/nfv/tests/data/tosca_helloworld_nfv.yaml @@ -0,0 +1,28 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 + +description: Template for deploying a single server with predefined properties. + +topology_template: + node_templates: + VNF1: + type: tosca.nodes.nfv.VNF + properties: + id: vnf1 + vendor: acmetelco + version: 1.0 + + VDU1: + type: tosca.nodes.nfv.VDU + + CP1: + type: tosca.nodes.nfv.CP + properties: + type: vPort + requirements: + - virtualLink: PrivateNetwork + - virtualBinding: VDU1 + + PrivateNetwork: + type: tosca.nodes.nfv.VL + properties: + vendor: ACME Networks diff --git a/toscaparser/extensions/nfv/tests/test_tosca_nfv_tpl.py b/toscaparser/extensions/nfv/tests/test_tosca_nfv_tpl.py new file mode 100644 index 00000000..b166d835 --- /dev/null +++ b/toscaparser/extensions/nfv/tests/test_tosca_nfv_tpl.py @@ -0,0 +1,29 @@ +# 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 os + +from toscaparser.tests.base import TestCase +from toscaparser.tosca_template import ToscaTemplate + + +class ToscaNFVTemplateTest(TestCase): + + '''TOSCA NFV template.''' + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/tosca_helloworld_nfv.yaml") + tosca = ToscaTemplate(tosca_tpl) + + def test_version(self): + self.assertEqual(self.tosca.version, + "tosca_simple_profile_for_nfv_1_0_0") diff --git a/toscaparser/tests/test_toscatpl.py b/toscaparser/tests/test_toscatpl.py index 4372a83d..56cfa74f 100644 --- a/toscaparser/tests/test_toscatpl.py +++ b/toscaparser/tests/test_toscatpl.py @@ -465,8 +465,9 @@ class ToscaTemplateTest(TestCase): "data/test_multiple_validation_errors.yaml") self.assertRaises(exception.ValidationError, ToscaTemplate, tosca_tpl, None) - err1_msg = _('The template version "tosca_simple_yaml_1" is invalid. ' - 'Valid versions are "tosca_simple_yaml_1_0".') + valid_versions = ', '.join(ToscaTemplate.VALID_TEMPLATE_VERSIONS) + err1_msg = (_('The template version "tosca_simple_yaml_1" is invalid. ' + 'Valid versions are "%s".') % valid_versions) exception.ExceptionCollector.assertExceptionMessage( exception.InvalidTemplateVersion, err1_msg) diff --git a/toscaparser/tosca_template.py b/toscaparser/tosca_template.py index f58ea820..bcf8e239 100644 --- a/toscaparser/tosca_template.py +++ b/toscaparser/tosca_template.py @@ -19,6 +19,8 @@ from toscaparser.common.exception import InvalidTemplateVersion from toscaparser.common.exception import MissingRequiredFieldError from toscaparser.common.exception import UnknownFieldError from toscaparser.common.exception import ValidationError +from toscaparser.elements.entity_type import update_definitions +from toscaparser.extensions.exttools import ExtTools import toscaparser.imports from toscaparser.prereq.csar import CSAR from toscaparser.topology_template import TopologyTemplate @@ -38,7 +40,8 @@ SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME, 'template_version', 'description', 'imports', 'dsl_definitions', 'node_types', 'relationship_types', 'relationship_templates', 'capability_types', 'artifact_types', 'datatype_definitions') -# Special key names + +# Sections that are specific to individual template definitions SPECIAL_SECTIONS = (METADATA) = ('metadata') log = logging.getLogger("tosca.model") @@ -47,9 +50,16 @@ YAML_LOADER = toscaparser.utils.yamlparser.load_yaml class ToscaTemplate(object): + exttools = ExtTools() VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0'] + VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions()) + + ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS} + + ADDITIONAL_SECTIONS.update(exttools.get_sections()) + '''Load the template data.''' def __init__(self, path, parsed_params=None, a_file=True): ExceptionCollector.start() @@ -171,7 +181,8 @@ class ToscaTemplate(object): self.version = version for name in self.tpl: - if name not in SECTIONS and name not in SPECIAL_SECTIONS: + if (name not in SECTIONS and + name not in self.ADDITIONAL_SECTIONS.get(version, ())): ExceptionCollector.appendException( UnknownFieldError(what='Template', field=name)) @@ -181,6 +192,9 @@ class ToscaTemplate(object): InvalidTemplateVersion( what=version, valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS))) + else: + if version != 'tosca_simple_yaml_1_0': + update_definitions(version) def _get_path(self, path): if path.lower().endswith('.yaml'):