Enhanced tosca validation

Using new exception class instead of built-in exception types.
Added validation for tosca template field names.

Partially implements: blueprint tosca-validation
Implements: blueprint check-key-definition

Change-Id: Ib6aadffc8922154f5b86da29ea29c9b603f4cfd8
This commit is contained in:
Victor HU
2014-07-03 08:47:05 +00:00
parent 2f2e84123a
commit ae70ab835b
13 changed files with 819 additions and 50 deletions

View File

View File

@@ -0,0 +1,74 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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.
'''
TOSCA exception classes
'''
import logging
import sys
from translator.toscalib.utils.gettextutils import _
log = logging.getLogger(__name__)
class TOSCAException(Exception):
'''Base exception class for TOSCA
To correctly use this class, inherit from it and define
a 'msg_fmt' property.
'''
_FATAL_EXCEPTION_FORMAT_ERRORS = False
message = _('An unknown exception occurred.')
def __init__(self, **kwargs):
try:
self.message = self.msg_fmt % kwargs
except KeyError:
exc_info = sys.exc_info()
log.exception(_('Exception in string format operation: %s')
% exc_info[1])
if TOSCAException._FATAL_EXCEPTION_FORMAT_ERRORS:
raise exc_info[0]
def __str__(self):
return self.message
@staticmethod
def set_fatal_format_exception(flag):
if isinstance(flag, bool):
TOSCAException._FATAL_EXCEPTION_FORMAT_ERRORS = flag
class MissingRequiredFieldError(TOSCAException):
msg_fmt = _('%(what)s is missing required field: "%(required)s".')
class UnknownFieldError(TOSCAException):
msg_fmt = _('%(what)s contain(s) unknown field: "%(field)s", '
'refer to the TOSCA specs to verify valid values.')
class TypeMismatchError(TOSCAException):
msg_fmt = _('%(what)s must be of type: "%(type)s".')
class InvalidNodeTypeError(TOSCAException):
msg_fmt = _('Node type "%(what)s" is not a valid type.')

View File

@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.elements.statefulentitytype import StatefulEntityType
from translator.toscalib.functions import get_function
@@ -20,6 +21,8 @@ SECTIONS = (LIFECYCLE, CONFIGURE) = \
('tosca.interfaces.node.Lifecycle',
'tosca.interfaces.relationship.Configure')
INTERFACEVALUE = (IMPLEMENTATION, INPUT) = ('implementation', 'input')
class InterfacesDef(StatefulEntityType):
'''TOSCA built-in interfaces type.'''
@@ -41,8 +44,12 @@ class InterfacesDef(StatefulEntityType):
for i, j in self.value.items():
if i == 'implementation':
self.implementation = j
if i == 'input':
elif i == 'input':
self.input = self._create_input_functions(j)
else:
what = ('Interfaces of node template %s' %
self.node_template.name)
raise UnknownFieldError(what=what, field=i)
else:
self.implementation = value

View File

@@ -13,12 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.toscalib.common.exception import InvalidNodeTypeError
from translator.toscalib.elements.capabilitytype import CapabilityTypeDef
from translator.toscalib.elements.interfaces import InterfacesDef
from translator.toscalib.elements.property_definition import PropertyDef
from translator.toscalib.elements.relationshiptype import RelationshipType
from translator.toscalib.elements.statefulentitytype import StatefulEntityType
from translator.toscalib.utils.gettextutils import _
SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS,
@@ -37,8 +37,7 @@ class NodeType(StatefulEntityType):
elif custom_def and ntype in list(custom_def.keys()):
self.defs = custom_def[ntype]
else:
raise ValueError(_('Node type %(ntype)s is not a valid type.')
% {'ntype': ntype})
raise InvalidNodeTypeError(what=ntype)
self.type = ntype
self.related = {}
@@ -69,16 +68,7 @@ class NodeType(StatefulEntityType):
'''
relationship = {}
requires = self.requirements
parent_node = self.parent_type
if requires is None:
requires = self.get_value(REQUIREMENTS, None, True)
parent_node = parent_node.parent_type
if parent_node:
while parent_node.type != 'tosca.nodes.Root':
req = parent_node.get_value(REQUIREMENTS, None, True)
requires.extend(req)
parent_node = parent_node.parent_type
requires = self.get_all_requirements()
if requires:
for req in requires:
for key, value in req.items():
@@ -128,6 +118,19 @@ class NodeType(StatefulEntityType):
def requirements(self):
return self.get_value(REQUIREMENTS)
def get_all_requirements(self):
requires = self.requirements
parent_node = self.parent_type
if requires is None:
requires = self.get_value(REQUIREMENTS, None, True)
parent_node = parent_node.parent_type
if parent_node:
while parent_node.type != 'tosca.nodes.Root':
req = parent_node.get_value(REQUIREMENTS, None, True)
requires.extend(req)
parent_node = parent_node.parent_type
return requires
@property
def interfaces(self):
return self.get_value(INTERFACES)

View File

@@ -16,16 +16,20 @@
import logging
from translator.toscalib.common.exception import MissingRequiredFieldError
from translator.toscalib.common.exception import TypeMismatchError
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.elements.capabilitytype import CapabilityTypeDef
from translator.toscalib.elements.interfaces import InterfacesDef
from translator.toscalib.elements.interfaces import LIFECYCLE, CONFIGURE
from translator.toscalib.elements.nodetype import NodeType
from translator.toscalib.properties import Property
from translator.toscalib.utils.gettextutils import _
SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS,
INTERFACES, CAPABILITIES) = \
INTERFACES, CAPABILITIES, TYPE) = \
('derived_from', 'properties', 'requirements', 'interfaces',
'capabilities')
'capabilities', 'type')
log = logging.getLogger('tosca')
@@ -34,10 +38,11 @@ class NodeTemplate(object):
'''Node template from a Tosca profile.'''
def __init__(self, name, node_templates, custom_def=None):
self.name = name
self.node_type = NodeType(node_templates[name]['type'], custom_def)
self.node_templates = node_templates
self._validate_field()
self.node_template = node_templates[self.name]
self.type = self.node_template['type']
self.type = self.node_template[TYPE]
self.node_type = NodeType(self.type, custom_def)
self.related = {}
@property
@@ -95,30 +100,12 @@ class NodeTemplate(object):
def properties(self):
props = []
properties = self.node_type.get_value(PROPERTIES, self.node_template)
requiredprop = []
for p in self.node_type.properties_def:
if p.required:
requiredprop.append(p.name)
if properties:
#make sure it's not missing any property required by a node type
missingprop = []
for r in requiredprop:
if r not in properties.keys():
missingprop.append(r)
if missingprop:
raise ValueError(_("Node template %(tpl)s is missing "
"one or more required properties %(prop)s")
% {'tpl': self.name, 'prop': missingprop})
for name, value in properties.items():
for p in self.node_type.properties_def:
if p.name == name:
prop = Property(name, value, p.schema)
props.append(prop)
else:
if requiredprop:
raise ValueError(_("Node template %(tpl)s is missing"
"one or more required properties %(prop)s")
% {'tpl': self.name, 'prop': requiredprop})
return props
def _add_next(self, nodetpl, relationship):
@@ -151,5 +138,103 @@ class NodeTemplate(object):
return c.property_value
def validate(self):
self._validate_capabilities()
self._validate_requirments()
self._validate_properties()
self._validate_interfaces()
for prop in self.properties:
prop.validate()
def _validate_capabilities(self):
type_capabilities = self.node_type.capabilities
allowed_caps = []
if type_capabilities:
for tcap in type_capabilities:
allowed_caps.append(tcap.name)
capabilities = self.node_type.get_value(CAPABILITIES,
self.node_template)
if capabilities:
self._common_validate_field(capabilities, allowed_caps,
'Capabilities')
def _validate_requirments(self):
type_requires = self.node_type.get_all_requirements()
allowed_reqs = []
if type_requires:
for treq in type_requires:
for key in treq:
allowed_reqs.append(key)
requires = self.node_type.get_value(REQUIREMENTS, self.node_template)
if requires:
if not isinstance(requires, list):
raise TypeMismatchError(
what='Requirements of node template %s' % self.name,
type='list')
for req in requires:
self._common_validate_field(req, allowed_reqs, 'Requirements')
def _validate_interfaces(self):
ifaces = self.node_type.get_value(INTERFACES, self.node_template)
if ifaces:
for i in ifaces:
for name, value in ifaces.items():
if name == LIFECYCLE:
self._common_validate_field(
value, InterfacesDef.
interfaces_node_lifecycle_operations,
'Interfaces')
elif name == CONFIGURE:
self._common_validate_field(
value, InterfacesDef.
interfaces_relationship_confiure_operations,
'Interfaces')
else:
raise UnknownFieldError(
what='Interfaces of node template %s' % self.name,
field=name)
def _validate_properties(self):
properties = self.node_type.get_value(PROPERTIES, self.node_template)
allowed_props = []
required_props = []
for p in self.node_type.properties_def:
allowed_props.append(p.name)
if p.required:
required_props.append(p.name)
if properties:
self._common_validate_field(properties, allowed_props,
'Properties')
#make sure it's not missing any property required by a node type
missingprop = []
for r in required_props:
if r not in properties.keys():
missingprop.append(r)
if missingprop:
raise MissingRequiredFieldError(
what='Properties of node template %s' % self.name,
required=missingprop)
else:
if required_props:
raise MissingRequiredFieldError(
what='Properties of node template %s' % self.name,
required=missingprop)
def _validate_field(self):
if not isinstance(self.node_templates[self.name], dict):
raise MissingRequiredFieldError(
what='Node template %s' % self.name, required=TYPE)
try:
self.node_templates[self.name][TYPE]
except KeyError:
raise MissingRequiredFieldError(
what='Node template %s' % self.name, required=TYPE)
self._common_validate_field(self.node_templates[self.name], SECTIONS,
'Second level')
def _common_validate_field(self, schema, allowedlist, section):
for name in schema:
if name not in allowedlist:
raise UnknownFieldError(
what='%(section)s of node template %(nodename)s'
% {'section': section, 'nodename': self.name},
field=name)

View File

@@ -16,41 +16,63 @@
import logging
from translator.toscalib.common.exception import MissingRequiredFieldError
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.elements.constraints import Constraint
from translator.toscalib.properties import Property
log = logging.getLogger('tosca')
class Input(object):
INPUTFIELD = (TYPE, DESCRIPTION, DEFAULT, CONSTRAINTS) = \
('type', 'description', 'default', 'constraints')
def __init__(self, name, schema):
self.name = name
self.schema = schema
@property
def type(self):
return self.schema['type']
return self.schema[self.TYPE]
@property
def description(self):
if Property.DESCRIPTION in self.schema:
return self.schema['description']
return self.schema[self.DESCRIPTION]
@property
def default(self):
if self.Property.DEFAULT in self.schema:
return self.schema['default']
return self.schema[self.DEFAULT]
@property
def constraints(self):
if Property.CONSTRAINTS in self.schema:
return self.schema['constraints']
return self.schema[self.CONSTRAINTS]
def validate(self):
self._validate_field()
self.validate_type(self.type)
if self.constraints:
self.validate_constraints(self.constraints)
def _validate_field(self):
if not isinstance(self.schema, dict):
raise MissingRequiredFieldError(what='Input %s' % self.name,
required=self.TYPE)
try:
self.type
except KeyError:
raise MissingRequiredFieldError(what='Input %s' % self.name,
required=self.TYPE)
for name in self.schema:
if name not in self.INPUTFIELD:
raise UnknownFieldError(what='Input %s' % self.name,
field=name)
def validate_type(self, input_type):
if input_type not in Property.PROPERTIY_TYPES:
raise ValueError(_('Invalid type %s') % type)
@@ -63,14 +85,34 @@ class Input(object):
class Output(object):
OUTPUTFIELD = (DESCRIPTION, VALUE) = ('description', 'value')
def __init__(self, name, attrs):
self.name = name
self.attrs = attrs
@property
def description(self):
return self.attrs['description']
return self.attrs[self.DESCRIPTION]
@property
def value(self):
return self.attrs['value']
return self.attrs[self.VALUE]
def validate(self):
self._validate_field()
def _validate_field(self):
if not isinstance(self.attrs, dict):
raise MissingRequiredFieldError(what='Output %s' % self.name,
required=self.VALUE)
try:
self.value
except KeyError:
raise MissingRequiredFieldError(what='Output %s' % self.name,
required=self.VALUE)
for name in self.attrs:
if name not in self.OUTPUTFIELD:
raise UnknownFieldError(what='Output %s' % self.name,
field=name)

View File

@@ -0,0 +1,28 @@
description: >
TOSCA simple profile missing version section.
inputs:
cpus:
type: integer
description: Number of CPUs for the server.
constraints:
- valid_values: [ 1, 2, 4, 8 ]
node_templates:
server:
type: tosca.nodes.Compute
properties:
# compute properties (flavor)
disk_size: 10
num_cpus: { get_input: cpus }
mem_size: 4096
# host image properties
os_arch: x86_64
os_type: Linux
os_distribution: Fedora
os_version: 18
outputs:
server_address:
description: IP address of server instance.
value: { get_property: [server, ip_address] }

View File

@@ -0,0 +1,30 @@
tosca_definitions_version: tosca_simple_1.0
description: >
TOSCA simple profile with invalid top-level key: 'node_template'.
inputs:
cpus:
type: integer
description: Number of CPUs for the server.
constraints:
- valid_values: [ 1, 2, 4, 8 ]
node_template:
server:
type: tosca.nodes.Compute
properties:
# compute properties (flavor)
disk_size: 10
num_cpus: { get_input: cpus }
mem_size: 4096
# host image properties
os_arch: x86_64
os_type: Linux
os_distribution: Fedora
os_version: 18
outputs:
server_address:
description: IP address of server instance.
value: { get_property: [server, ip_address] }

View File

@@ -0,0 +1,44 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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.
from translator.toscalib.common.exception import MissingRequiredFieldError
from translator.toscalib.common.exception import TOSCAException
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.tests.base import TestCase
class ExceptionTest(TestCase):
def setUp(self):
super(TestCase, self).setUp()
TOSCAException.set_fatal_format_exception(False)
def test_message(self):
ex = MissingRequiredFieldError(what='Template', required='type')
self.assertEqual('Template is missing required field: "type".',
ex.__str__())
def test_set_flag(self):
TOSCAException.set_fatal_format_exception('True')
self.assertFalse(TOSCAException._FATAL_EXCEPTION_FORMAT_ERRORS)
def test_format_error(self):
ex = UnknownFieldError(what='Template')
self.assertEqual('An unknown exception occurred.', ex.__str__(),)
self.assertRaises(KeyError, self._formate_exception)
def _formate_exception(self):
UnknownFieldError.set_fatal_format_exception(True)
raise UnknownFieldError(what='Template')

View File

@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from translator.toscalib.common.exception import InvalidNodeTypeError
from translator.toscalib.elements.nodetype import NodeType
from translator.toscalib.tests.base import TestCase
compute_type = NodeType('tosca.nodes.Compute')
@@ -22,7 +23,8 @@ component_type = NodeType('tosca.nodes.SoftwareComponent')
class ToscaDefTest(TestCase):
def test_type(self):
self.assertEqual(compute_type.type, "tosca.nodes.Compute")
self.assertRaises(ValueError, NodeType, 'tosca.nodes.Invalid')
self.assertRaises(InvalidNodeTypeError, NodeType,
'tosca.nodes.Invalid')
def test_parent_type(self):
self.assertEqual(compute_type.parent_type.type, "tosca.nodes.Root")

View File

@@ -0,0 +1,430 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 translator.toscalib.common.exception import InvalidNodeTypeError
from translator.toscalib.common.exception import MissingRequiredFieldError
from translator.toscalib.common.exception import TypeMismatchError
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.nodetemplate import NodeTemplate
from translator.toscalib.parameters import Input, Output
from translator.toscalib.tests.base import TestCase
from translator.toscalib.tosca_template import ToscaTemplate
import translator.toscalib.utils.yamlparser
class ToscaTemplateValidationTest(TestCase):
def test_well_defined_template(self):
tpl_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/tosca_single_instance_wordpress.yaml")
self.assertIsNotNone(ToscaTemplate(tpl_path))
def test_first_level_sections(self):
tpl_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/test_tosca_top_level_error1.yaml")
try:
ToscaTemplate(tpl_path)
except Exception as err:
self.assertTrue(isinstance(err, MissingRequiredFieldError))
self.assertEqual('Template is missing required field: '
'"tosca_definitions_version".', err.__str__())
tpl_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/test_tosca_top_level_error2.yaml")
try:
ToscaTemplate(tpl_path)
except Exception as err:
self.assertTrue(isinstance(err, UnknownFieldError))
self.assertEqual('Template contain(s) unknown field: '
'"node_template", refer to the TOSCA specs '
'to verify valid values.', err.__str__())
def test_inputs(self):
tpl_snippet = '''
inputs:
cpus:
description: Number of CPUs for the server.
constraints:
- valid_values: [ 1, 2, 4, 8 ]
'''
inputs = (translator.toscalib.utils.yamlparser.
simple_parse(tpl_snippet)['inputs'])
name, attrs = list(inputs.items())[0]
input = Input(name, attrs)
try:
input.validate()
except Exception as err:
self.assertTrue(isinstance(err, MissingRequiredFieldError))
self.assertEqual('Input cpus is missing required field: '
'"type".', err.__str__())
tpl_snippet = '''
inputs:
cpus:
type: integer
description: Number of CPUs for the server.
constraint:
- valid_values: [ 1, 2, 4, 8 ]
'''
inputs = (translator.toscalib.utils.yamlparser.
simple_parse(tpl_snippet)['inputs'])
name, attrs = list(inputs.items())[0]
input = Input(name, attrs)
try:
input.validate()
except Exception as err:
self.assertTrue(isinstance(err, UnknownFieldError))
self.assertEqual('Input cpus contain(s) unknown field: '
'"constraint", refer to the TOSCA specs to '
'verify valid values.', err.__str__())
def test_outputs(self):
tpl_snippet = '''
outputs:
server_address:
description: IP address of server instance.
values: { get_property: [server, ip_address] }
'''
outputs = (translator.toscalib.utils.yamlparser.
simple_parse(tpl_snippet)['outputs'])
name, attrs = list(outputs.items())[0]
output = Output(name, attrs)
try:
output.validate()
except Exception as err:
self.assertTrue(isinstance(err, MissingRequiredFieldError))
self.assertEqual('Output server_address is missing required '
'field: "value".', err.__str__())
tpl_snippet = '''
outputs:
server_address:
descriptions: IP address of server instance.
value: { get_property: [server, ip_address] }
'''
outputs = (translator.toscalib.utils.yamlparser.
simple_parse(tpl_snippet)['outputs'])
name, attrs = list(outputs.items())[0]
output = Output(name, attrs)
try:
output.validate()
except Exception as err:
self.assertTrue(isinstance(err, UnknownFieldError))
self.assertEqual('Output server_address contain(s) unknown '
'field: "descriptions", refer to the TOSCA '
'specs to verify valid values.',
err.__str__())
def _custom_types(self):
custom_types = {}
def_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/custom_types/wordpress.yaml")
custom_type = translator.toscalib.utils.yamlparser.load_yaml(def_file)
node_types = custom_type['node_types']
for name in node_types:
defintion = node_types[name]
custom_types[name] = defintion
return custom_types
def _single_node_template_content_test(self, tpl_snippet, expectederror,
expectedmessage):
nodetemplates = (translator.toscalib.utils.yamlparser.
simple_parse(tpl_snippet))['node_templates']
name = list(nodetemplates.keys())[0]
try:
nodetemplate = NodeTemplate(name, nodetemplates,
self._custom_types())
nodetemplate.validate()
nodetemplate.requirements
nodetemplate.capabilities
nodetemplate.properties
nodetemplate.interfaces
except Exception as err:
self.assertTrue(isinstance(err, expectederror))
self.assertEqual(expectedmessage, err.__str__())
def test_node_templates(self):
tpl_snippet = '''
node_templates:
server:
properties:
# compute properties (flavor)
disk_size: 10
num_cpus: { get_input: cpus }
mem_size: 4096
# host image properties
os_arch: x86_64
os_type: Linux
os_distribution: Fedora
os_version: 18
'''
expectedmessage = ('Node template server is missing '
'required field: "type".')
self._single_node_template_content_test(tpl_snippet,
MissingRequiredFieldError,
expectedmessage)
tpl_snippet = '''
node_templates:
mysql_dbms:
type: tosca.nodes.DBMS
properties:
dbms_root_password: { get_input: db_root_pwd }
dbms_port: { get_input: db_port }
requirement:
- host: server
interfaces:
tosca.interfaces.node.Lifecycle:
create: mysql_dbms_install.sh
start: mysql_dbms_start.sh
configure:
implementation: mysql_dbms_configure.sh
input:
db_root_password: { get_property: [ mysql_dbms, \
dbms_root_password ] }
'''
expectedmessage = ('Second level of node template mysql_dbms '
'contain(s) unknown field: "requirement", '
'refer to the TOSCA specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
UnknownFieldError,
expectedmessage)
def test_node_template_type(self):
tpl_snippet = '''
node_templates:
mysql_database:
type: tosca.nodes.Databases
properties:
db_name: { get_input: db_name }
db_user: { get_input: db_user }
db_password: { get_input: db_pwd }
capabilities:
database_endpoint:
properties:
port: { get_input: db_port }
requirements:
- host: mysql_dbms
interfaces:
tosca.interfaces.node.Lifecycle:
configure: mysql_database_configure.sh
'''
expectedmessage = ('Node type "tosca.nodes.Databases" is not '
'a valid type.')
self._single_node_template_content_test(tpl_snippet,
InvalidNodeTypeError,
expectedmessage)
def test_node_template_requirements(self):
tpl_snippet = '''
node_templates:
webserver:
type: tosca.nodes.WebServer
requirements:
host: server
interfaces:
tosca.interfaces.node.Lifecycle:
create: webserver_install.sh
start: webserver_start.sh
'''
expectedmessage = ('Requirements of node template webserver '
'must be of type: "list".')
self._single_node_template_content_test(tpl_snippet,
TypeMismatchError,
expectedmessage)
tpl_snippet = '''
node_templates:
mysql_database:
type: tosca.nodes.Database
properties:
db_name: { get_input: db_name }
db_user: { get_input: db_user }
db_password: { get_input: db_pwd }
capabilities:
database_endpoint:
properties:
port: { get_input: db_port }
requirements:
- host: mysql_dbms
- database_endpoint: mysql_database
interfaces:
tosca.interfaces.node.Lifecycle:
configure: mysql_database_configure.sh
'''
expectedmessage = ('Requirements of node template mysql_database '
'contain(s) unknown field: "database_endpoint", '
'refer to the TOSCA specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
UnknownFieldError,
expectedmessage)
def test_node_template_capabilities(self):
tpl_snippet = '''
node_templates:
mysql_database:
type: tosca.nodes.Database
properties:
db_name: { get_input: db_name }
db_user: { get_input: db_user }
db_password: { get_input: db_pwd }
capabilities:
http_endpoint:
properties:
port: { get_input: db_port }
requirements:
- host: mysql_dbms
interfaces:
tosca.interfaces.node.Lifecycle:
configure: mysql_database_configure.sh
'''
expectedmessage = ('Capabilities of node template mysql_database '
'contain(s) unknown field: "http_endpoint", '
'refer to the TOSCA specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
UnknownFieldError,
expectedmessage)
def test_node_template_properties(self):
tpl_snippet = '''
node_templates:
server:
type: tosca.nodes.Compute
properties:
# compute properties (flavor)
disk_size: 10
num_cpus: { get_input: cpus }
mem_size: 4096
# host image properties
os_arch: x86_64
os_distribution: Fedora
os_version: 18
'''
expectedmessage = ('Properties of node template server is missing '
'required field: "[\'os_type\']".')
self._single_node_template_content_test(tpl_snippet,
MissingRequiredFieldError,
expectedmessage)
tpl_snippet = '''
node_templates:
server:
type: tosca.nodes.Compute
properties:
# compute properties (flavor)
disk_size: 10
num_cpus: { get_input: cpus }
mem_size: 4096
# host image properties
os_arch: x86_64
os_type: Linux
os_distribution: Fedora
os_version: 18
os_image: F18_x86_64
'''
expectedmessage = ('Properties of node template server contain(s) '
'unknown field: "os_image", refer to the TOSCA '
'specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
UnknownFieldError,
expectedmessage)
def test_node_template_interfaces(self):
tpl_snippet = '''
node_templates:
wordpress:
type: tosca.nodes.WebApplication.WordPress
requirements:
- host: webserver
- database_endpoint: mysql_database
interfaces:
tosca.interfaces.node.Lifecycles:
create: wordpress_install.sh
configure:
implementation: wordpress_configure.sh
input:
wp_db_name: { get_property: [ mysql_database, db_name ] }
wp_db_user: { get_property: [ mysql_database, db_user ] }
wp_db_password: { get_property: [ mysql_database, \
db_password ] }
wp_db_port: { get_ref_property: [ database_endpoint, \
database_endpoint, port ] }
'''
expectedmessage = ('Interfaces of node template wordpress '
'contain(s) unknown field: '
'"tosca.interfaces.node.Lifecycles", '
'refer to the TOSCA specs to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
UnknownFieldError,
expectedmessage)
tpl_snippet = '''
node_templates:
wordpress:
type: tosca.nodes.WebApplication.WordPress
requirements:
- host: webserver
- database_endpoint: mysql_database
interfaces:
tosca.interfaces.node.Lifecycle:
create: wordpress_install.sh
config:
implementation: wordpress_configure.sh
input:
wp_db_name: { get_property: [ mysql_database, db_name ] }
wp_db_user: { get_property: [ mysql_database, db_user ] }
wp_db_password: { get_property: [ mysql_database, \
db_password ] }
wp_db_port: { get_ref_property: [ database_endpoint, \
database_endpoint, port ] }
'''
expectedmessage = ('Interfaces of node template wordpress contain(s) '
'unknown field: "config", refer to the TOSCA specs'
' to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
UnknownFieldError,
expectedmessage)
tpl_snippet = '''
node_templates:
wordpress:
type: tosca.nodes.WebApplication.WordPress
requirements:
- host: webserver
- database_endpoint: mysql_database
interfaces:
tosca.interfaces.node.Lifecycle:
create: wordpress_install.sh
configure:
implementation: wordpress_configure.sh
inputs:
wp_db_name: { get_property: [ mysql_database, db_name ] }
wp_db_user: { get_property: [ mysql_database, db_user ] }
wp_db_password: { get_property: [ mysql_database, \
db_password ] }
wp_db_port: { get_ref_property: [ database_endpoint, \
database_endpoint, port ] }
'''
expectedmessage = ('Interfaces of node template wordpress contain(s) '
'unknown field: "inputs", refer to the TOSCA specs'
' to verify valid values.')
self._single_node_template_content_test(tpl_snippet,
UnknownFieldError,
expectedmessage)

View File

@@ -16,13 +16,17 @@
import logging
import os
from translator.toscalib.common.exception import MissingRequiredFieldError
from translator.toscalib.common.exception import UnknownFieldError
from translator.toscalib.nodetemplate import NodeTemplate
from translator.toscalib.parameters import Input, Output
from translator.toscalib.tpl_relationship_graph import ToscaGraph
from translator.toscalib.utils.gettextutils import _
import translator.toscalib.utils.yamlparser
#Simple YAML definition A.4.1 Keynames
SECTIONS = (VERSION, DESCRIPTION, IMPORTS, INPUTS,
NODE_TEMPLATES, OUTPUTS) = \
('tosca_definitions_version', 'description', 'imports', 'inputs',
@@ -38,6 +42,7 @@ class ToscaTemplate(object):
def __init__(self, path):
self.tpl = YAML_LOADER(path)
self.path = path
self._validate_field()
self.version = self._tpl_version()
self.description = self._tpl_description()
self.inputs = self._inputs()
@@ -49,9 +54,6 @@ class ToscaTemplate(object):
inputs = []
for name, attrs in self._tpl_inputs().items():
input = Input(name, attrs)
if not isinstance(input.schema, dict):
raise ValueError(_("The input %(input)s has no attributes.")
% {'input': input})
input.validate()
inputs.append(input)
return inputs
@@ -73,7 +75,7 @@ class ToscaTemplate(object):
custom_types[name] = defintion
nodetemplates = []
tpls = self._tpl_nodetemplates()
for name, value in tpls.items():
for name in tpls:
tpl = NodeTemplate(name, tpls, custom_types)
tpl.validate()
nodetemplates.append(tpl)
@@ -82,7 +84,9 @@ class ToscaTemplate(object):
def _outputs(self):
outputs = []
for name, attrs in self._tpl_outputs().items():
outputs.append(Output(name, attrs))
output = Output(name, attrs)
output.validate()
outputs.append(output)
return outputs
def _tpl_version(self):
@@ -103,3 +107,12 @@ class ToscaTemplate(object):
def _tpl_outputs(self):
return self.tpl[OUTPUTS]
def _validate_field(self):
try:
self._tpl_version()
except KeyError:
raise MissingRequiredFieldError(what='Template', required=VERSION)
for name in self.tpl:
if name not in SECTIONS:
raise UnknownFieldError(what='Template', field=name)

View File

@@ -24,3 +24,14 @@ else:
def load_yaml(path):
with open(path) as f:
return yaml.load(f.read(), Loader=yaml_loader)
def simple_parse(tmpl_str):
try:
tpl = yaml.load(tmpl_str, Loader=yaml_loader)
except yaml.YAMLError as yea:
raise ValueError(yea)
else:
if tpl is None:
tpl = {}
return tpl