Add output validation for substitution mappings

1. Validate that the attributes of the substitution mappings node type can be
accessed in the service template by substituted node.
2.Validate that an attempt to access any attributes not belong to a specific
node type or substitution mappings results into an expected error.

This patch is related to bp: https://review.openstack.org/#/c/345492/

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

Change-Id: I5f6cdcf7eb66f20ba9e524b73edd249d2c7b1e31
This commit is contained in:
shangxdy 2016-12-06 21:15:55 +08:00 committed by sahdev zala
parent 9baadf93b5
commit d061ef025d
10 changed files with 195 additions and 38 deletions

@ -114,6 +114,10 @@ class UnknownInputError(TOSCAException):
msg_fmt = _('Unknown input "%(input_name)s".')
class UnknownOutputError(TOSCAException):
msg_fmt = _('Unknown output "%(output_name)s" in %(where)s.')
class MissingRequiredInputError(TOSCAException):
msg_fmt = _('%(what)s is missing required input definition '
'of input "%(input_name)s".')
@ -129,6 +133,11 @@ class MissingDefaultValueError(TOSCAException):
'of input "%(input_name)s".')
class MissingRequiredOutputError(TOSCAException):
msg_fmt = _('%(what)s is missing required output definition '
'of output "%(output_name)s".')
class InvalidPropertyValueError(TOSCAException):
msg_fmt = _('Value of property "%(what)s" is invalid.')

@ -18,6 +18,7 @@ from toscaparser.common.exception import MissingDefaultValueError
from toscaparser.common.exception import MissingRequiredFieldError
from toscaparser.common.exception import MissingRequiredInputError
from toscaparser.common.exception import UnknownFieldError
from toscaparser.common.exception import UnknownOutputError
from toscaparser.elements.nodetype import NodeType
from toscaparser.utils.gettextutils import _
@ -34,6 +35,8 @@ class SubstitutionMappings(object):
SECTIONS = (NODE_TYPE, REQUIREMENTS, CAPABILITIES) = \
('node_type', 'requirements', 'capabilities')
OPTIONAL_OUTPUTS = ['tosca_id', 'tosca_name', 'state']
def __init__(self, sub_mapping_def, nodetemplates, inputs, outputs,
sub_mapped_node_template, custom_defs):
self.nodetemplates = nodetemplates
@ -189,14 +192,23 @@ class SubstitutionMappings(object):
# field=req))
def _validate_outputs(self):
"""validate the outputs of substitution mappings."""
pass
# The outputs in service template which defines substutition mappings
# must be in atrributes of node template wchich be mapped.
# outputs_names = self.sub_mapped_node_template.get_properties().
# keys() if self.sub_mapped_node_template else None
# for name in outputs_names:
# if name not in [output.name for input in self.outputs]:
# ExceptionCollector.appendException(
# UnknownFieldError(what='SubstitutionMappings',
# field=name))
"""validate the outputs of substitution mappings.
The outputs defined by the topology template have to match the
attributes of the node type or the substituted node template,
and the observable attributes of the substituted node template
have to be defined as attributes of the node type or outputs in
the topology template.
"""
# The outputs defined by the topology template have to match the
# attributes of the node type according to the specification, but
# it's reasonable that there are more inputs than the node type
# has properties, the specification will be amended?
for output in self.outputs:
if output.name not in self.node_definition.get_attributes_def():
ExceptionCollector.appendException(
UnknownOutputError(
where=_('SubstitutionMappings with node_type ')
+ self.node_type,
output_name=output.name))

@ -75,15 +75,6 @@ topology_template:
distribution: Ubuntu
version: 14.04
outputs:
receiver_ip:
description: private IP address of the database application
value: { get_attribute: [ server, private_address ] }
# It seems current _process_intrisic_function can not handle more than 2 arguments, save it for later
# receiver_port:
# description: Port of the message receiver endpoint
# value: { get_attribute: [ app, data_endpoint, port_name ] }
groups:
dbserver_group:
members: [ dbms, server ]

@ -61,13 +61,13 @@ topology_template:
version: 14.04
outputs:
receiver_ip:
description: private IP address of the message receiver application
value: { get_attribute: [ server, private_address ] }
# It seems current _process_intrisic_function can not handle more than 2 arguments, save it for later
# receiver_port:
# description: Port of the message receiver endpoint
# value: { get_attribute: [ app, data_endpoint, port_name ] }
server_ip:
description: server_ip of the message receiver application
value: { get_input: server_ip }
server_port:
description: server_port of the message receiver application
value: { get_input: server_port }
groups:
tran_server_group:

@ -21,8 +21,6 @@ topology_template:
node_templates:
mq:
type: example.QueuingSubsystem
# properties:
# to be updated when substitution_mapping is validated later
properties:
server_ip: { get_input: mq_server_ip }
server_port: { get_input: mq_server_port }
@ -36,9 +34,7 @@ topology_template:
trans1:
type: example.TransactionSubsystem
properties:
# mq_server_ip: 127.0.0.1
mq_server_ip: { get_attribute: [ mq, server_ip ] }
# receiver_port: 8080
receiver_port: { get_attribute: [ mq, server_port ] }
# capabilities:
# message_receiver:
@ -49,9 +45,7 @@ topology_template:
trans2:
type: example.TransactionSubsystem
properties:
# mq_server_ip: 127.0.0.1
mq_server_ip: { get_attribute: [ mq, server_ip ] }
# receiver_port: 8080
receiver_port: { get_attribute: [ mq, server_port ] }
# capabilities:
# message_receiver:

@ -77,10 +77,10 @@ topology_template:
receiver_ip:
description: private IP address of the message receiver application
value: { get_attribute: [ server, private_address ] }
# It seems current _process_intrisic_function can not handle more than 2 arguments, save it for later
# receiver_port:
# description: Port of the message receiver endpoint
# value: { get_attribute: [ app, data_endpoint, port_name ] }
receiver_port:
description: receiver_port of the message receiver application
value: { get_input: receiver_port }
groups:
webserver_group:

@ -0,0 +1,70 @@
tosca_definitions_version: tosca_simple_yaml_1_0
description: >
Template showing an example TOSCA type to demonstrate usage
of output in the substitution mappings.
node_types:
example.app:
derived_from: tosca.nodes.WebApplication
properties:
mq_server_ip:
type: string
required: False
receiver_port:
type: integer
required: False
attributes:
receiver_ip:
type: string
receiver_port:
type: integer
topology_template:
inputs:
mq_server_ip:
type: string
description: IP address of the message queuing server to receive messages from.
default: 127.0.0.1
receiver_port:
type: integer
description: Port to be used for receiving messages.
default: 8080
my_cpus:
type: integer
description: Number of CPUs for the server.
default: 2
constraints:
- valid_values: [ 1, 2, 4, 8 ]
substitution_mappings:
node_type: example.app
node_templates:
app:
type: example.app
properties:
mq_server_ip: { get_input: mq_server_ip }
receiver_port: { get_input: receiver_port }
requirements:
- host:
node: websrv
websrv:
type: tosca.nodes.WebServer
requirements:
- host:
node: server
server:
type: tosca.nodes.Compute
capabilities:
host:
properties:
disk_size: 10 GB
num_cpus: { get_input: my_cpus }
mem_size: 4096 MB
os:
properties:
architecture: x86_64
type: Linux
distribution: Ubuntu
version: 14.04

@ -0,0 +1,31 @@
tosca_definitions_version: tosca_simple_yaml_1_0
imports:
- test_example_app_substitution_mappings.yaml
topology_template:
description: >
Test template showing valid output section containing attribute defined
in the substitution mappings in the imported yaml file.
inputs:
mq_server_ip:
type: string
default: 127.0.0.1
description: IP address of the message queuing server to receive messages from.
mq_server_port:
type: integer
default: 8080
description: Port to be used for receiving messages.
node_templates:
substitute_app:
type: example.app
properties:
mq_server_ip: { get_input: mq_server_ip }
receiver_port: { get_input: mq_server_port }
outputs:
receiver_ip:
description: private IP address of the message receiver application
value: { get_attribute: [ substitute_app, my_cpu_output ] }

@ -0,0 +1,31 @@
tosca_definitions_version: tosca_simple_yaml_1_0
imports:
- test_example_app_substitution_mappings.yaml
topology_template:
description: >
Test template showing valid output section containing attribute defined
in the substitution mappings in the imported yaml file.
inputs:
mq_server_ip:
type: string
default: 127.0.0.1
description: IP address of the message queuing server to receive messages from.
mq_server_port:
type: integer
default: 8080
description: Port to be used for receiving messages.
node_templates:
sustitute_app:
type: example.app
properties:
mq_server_ip: { get_input: mq_server_ip }
receiver_port: { get_input: mq_server_port }
outputs:
receiver_ip:
description: private IP address of the message receiver application
value: { get_attribute: [ sustitute_app, receiver_ip ] }

@ -154,7 +154,7 @@ class TopologyTemplateTest(TestCase):
def test_outputs(self):
self.assertEqual(
['receiver_ip'],
sorted(['receiver_ip', 'receiver_port']),
sorted([output.name for output in self.topo.outputs]))
def test_groups(self):
@ -262,3 +262,22 @@ class TopologyTemplateTest(TestCase):
lambda: ToscaTemplate(tpl_path1))
exception.ExceptionCollector.assertExceptionMessage(
exception.MissingRequiredInputError, errormsg)
def test_substitution_mappings_valid_output(self):
tpl_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/topology_template/validate/"
"test_substitution_mappings_valid_output.yaml")
self.assertIsNotNone(ToscaTemplate(tpl_path))
def test_system_with_unknown_output_validation(self):
tpl_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"data/topology_template/validate/"
"test_substitution_mappings_invalid_output.yaml")
errormsg = _('\'Attribute "my_cpu_output" was not found in node '
'template "substitute_app".\'')
self.assertRaises(exception.ValidationError,
lambda: ToscaTemplate(tpl_path))
exception.ExceptionCollector.assertExceptionMessage(
KeyError, errormsg)