From a80b6893c2dadb30b5e30e244a92cc2ff970326d Mon Sep 17 00:00:00 2001 From: Vahid Hashemian Date: Thu, 24 Sep 2015 10:23:24 -0700 Subject: [PATCH] Fix translation issue with interface inputs and functions Fix the issue with interface inputs described using intrinsic functions, that produces an invalid HOT output. Also, include necessary unit tests, and revert templates that were simplified due to this issue.. This patch includes changes required in tosca-parser for resolving the issue. Change-Id: Iefde09d4633a1cadfe2c6b9ac28c58d2da79a37d Partial-Bug: #1440247 --- toscaparser/elements/constraints.py | 9 ++-- toscaparser/functions.py | 15 ++++-- .../CSAR/tosca_elk/Definitions/tosca_elk.yaml | 33 ++++-------- .../tosca_single_instance_wordpress.yaml | 15 ++---- toscaparser/tests/data/tosca_elk.yaml | 6 +-- .../data/tosca_single_instance_wordpress.yaml | 23 ++++---- toscaparser/tests/test_constraints.py | 4 +- toscaparser/tests/test_datatypes.py | 8 +-- toscaparser/tests/test_functions.py | 54 ++++++++----------- toscaparser/tests/test_properties.py | 13 +++-- toscaparser/tests/test_toscatpl.py | 2 +- toscaparser/tests/test_toscatplvalidation.py | 10 ++-- toscaparser/utils/validateutils.py | 12 ++--- 13 files changed, 92 insertions(+), 112 deletions(-) diff --git a/toscaparser/elements/constraints.py b/toscaparser/elements/constraints.py index 3fe1976a..c4d862fa 100644 --- a/toscaparser/elements/constraints.py +++ b/toscaparser/elements/constraints.py @@ -14,10 +14,10 @@ import collections import datetime import re +import toscaparser from toscaparser.common.exception import InvalidSchemaError from toscaparser.common.exception import ValidationError from toscaparser.elements import scalarunit -from toscaparser.functions import is_function from toscaparser.utils.gettextutils import _ @@ -262,7 +262,8 @@ class GreaterOrEqual(Constraint): 'be comparable.')) def _is_valid(self, value): - if is_function(value) or value >= self.constraint_value: + if toscaparser.functions.is_function(value) or \ + value >= self.constraint_value: return True return False @@ -417,8 +418,8 @@ class ValidValues(Constraint): def _err_msg(self, value): allowed = '[%s]' % ', '.join(str(a) for a in self.constraint_value) - return (_('%(pname)s: %(pvalue)s is not an valid ' - 'value "%(cvalue)s".') % + return (_('%(pname)s: %(pvalue)s is not a valid value. Expected a ' + 'value from "%(cvalue)s".') % dict(pname=self.property_name, pvalue=value, cvalue=allowed)) diff --git a/toscaparser/functions.py b/toscaparser/functions.py index 9ac2ce89..4028aab3 100644 --- a/toscaparser/functions.py +++ b/toscaparser/functions.py @@ -16,6 +16,7 @@ import abc import six from toscaparser.common.exception import UnknownInputError +from toscaparser.dataentity import DataEntity from toscaparser.utils.gettextutils import _ @@ -80,9 +81,15 @@ class GetInput(Function): raise UnknownInputError(input_name=self.args[0]) def result(self): - found_input = [input_def for input_def in self.tosca_tpl.inputs - if self.input_name == input_def.name][0] - return found_input.default + if self.tosca_tpl.parsed_params and \ + self.input_name in self.tosca_tpl.parsed_params: + return DataEntity.validate_datatype( + self.tosca_tpl.tpl['inputs'][self.input_name]['type'], + self.tosca_tpl.parsed_params[self.input_name]) + + input = [input_def for input_def in self.tosca_tpl.inputs + if self.input_name == input_def.name][0] + return input.default @property def input_name(self): @@ -307,7 +314,7 @@ class GetProperty(Function): else: property_value = self._find_property(self.args[1]).value if isinstance(property_value, Function): - return property_value + return property_value.result() return get_function(self.tosca_tpl, self.context, property_value) diff --git a/toscaparser/tests/data/CSAR/tosca_elk/Definitions/tosca_elk.yaml b/toscaparser/tests/data/CSAR/tosca_elk/Definitions/tosca_elk.yaml index 66cc04be..932f1315 100644 --- a/toscaparser/tests/data/CSAR/tosca_elk/Definitions/tosca_elk.yaml +++ b/toscaparser/tests/data/CSAR/tosca_elk/Definitions/tosca_elk.yaml @@ -43,16 +43,14 @@ topology_template: properties: github_url: { get_input: github_url } requirements: - - host: - node: nodejs - - database_connection: - node: mongo_db + - host: nodejs + - database_connection: mongo_db interfaces: Standard: configure: implementation: ../Scripts/nodejs/config.sh inputs: - github_url: http://github.com/paypal/rest-api-sample-app-nodejs.git + github_url: { get_property: [ SELF, github_url ] } mongodb_ip: { get_attribute: [mongo_server, private_address] } start: ../Scripts/nodejs/start.sh nodejs: @@ -66,16 +64,14 @@ topology_template: mongo_db: type: tosca.nodes.Database requirements: - - host: - node: mongo_dbms + - host: mongo_dbms interfaces: Standard: create: ../Scripts/mongodb/create_database.sh mongo_dbms: type: tosca.nodes.DBMS requirements: - - host: - node: mongo_server + - host: mongo_server interfaces: Standard: create: ../Scripts/mongodb/create.sh @@ -87,8 +83,7 @@ topology_template: elasticsearch: type: tosca.nodes.SoftwareComponent.Elasticsearch requirements: - - host: - node: elasticsearch_server + - host: elasticsearch_server interfaces: Standard: create: ../Scripts/elasticsearch/create.sh @@ -96,8 +91,7 @@ topology_template: logstash: type: tosca.nodes.SoftwareComponent.Logstash requirements: - - host: - node: logstash_server + - host: logstash_server - search_endpoint: node: elasticsearch capability: search_endpoint @@ -116,11 +110,8 @@ topology_template: kibana: type: tosca.nodes.SoftwareComponent.Kibana requirements: - - host: - node: kibana_server - - search_endpoint: - node: elasticsearch - capability: search_endpoint + - host: kibana_server + - search_endpoint: elasticsearch interfaces: Standard: create: ../Scripts/kibana/create.sh @@ -133,8 +124,7 @@ topology_template: app_collectd: type: tosca.nodes.SoftwareComponent.Collectd requirements: - - host: - node: app_server + - host: app_server - log_endpoint: node: logstash capability: log_endpoint @@ -155,8 +145,7 @@ topology_template: app_rsyslog: type: tosca.nodes.SoftwareComponent.Rsyslog requirements: - - host: - node: app_server + - host: app_server - log_endpoint: node: logstash capability: log_endpoint diff --git a/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/tosca_single_instance_wordpress.yaml b/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/tosca_single_instance_wordpress.yaml index 1edee26b..96b7db6d 100644 --- a/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/tosca_single_instance_wordpress.yaml +++ b/toscaparser/tests/data/CSAR/tosca_single_instance_wordpress/Definitions/tosca_single_instance_wordpress.yaml @@ -33,10 +33,8 @@ topology_template: wordpress: type: tosca.nodes.WebApplication.WordPress requirements: - - host: - node: webserver - - database_endpoint: - node: mysql_database + - host: webserver + - database_endpoint: mysql_database interfaces: Standard: create: Scripts/WordPress/install.sh @@ -54,8 +52,7 @@ topology_template: user: { get_input: db_user } password: { get_input: db_pwd } requirements: - - host: - node: mysql_dbms + - host: mysql_dbms interfaces: Standard: configure: @@ -72,8 +69,7 @@ topology_template: root_password: { get_input: db_root_pwd } port: { get_input: db_port } requirements: - - host: - node: server + - host: server interfaces: Standard: create: Scripts/MYSQLDBMS/install.sh @@ -86,8 +82,7 @@ topology_template: webserver: type: tosca.nodes.WebServer requirements: - - host: - node: server + - host: server interfaces: Standard: create: Scripts/WebServer/install.sh diff --git a/toscaparser/tests/data/tosca_elk.yaml b/toscaparser/tests/data/tosca_elk.yaml index 2576d6ee..6fc17561 100644 --- a/toscaparser/tests/data/tosca_elk.yaml +++ b/toscaparser/tests/data/tosca_elk.yaml @@ -51,7 +51,7 @@ topology_template: configure: implementation: nodejs/config.sh inputs: - github_url: http://github.com/paypal/rest-api-sample-app-nodejs.git + github_url: { get_property: [ SELF, github_url ] } mongodb_ip: { get_attribute: [mongo_server, private_address] } start: nodejs/start.sh nodejs: @@ -111,9 +111,7 @@ topology_template: type: tosca.nodes.SoftwareComponent.Kibana requirements: - host: kibana_server - - search_endpoint: - node: elasticsearch - capability: search_endpoint + - search_endpoint: elasticsearch interfaces: Standard: create: kibana/create.sh diff --git a/toscaparser/tests/data/tosca_single_instance_wordpress.yaml b/toscaparser/tests/data/tosca_single_instance_wordpress.yaml index f6570340..9e686abd 100644 --- a/toscaparser/tests/data/tosca_single_instance_wordpress.yaml +++ b/toscaparser/tests/data/tosca_single_instance_wordpress.yaml @@ -46,9 +46,9 @@ topology_template: configure: implementation: wordpress/wordpress_configure.sh inputs: - wp_db_name: wordpress - wp_db_user: wp_user - wp_db_password: wp_pass + wp_db_name: { get_property: [ mysql_database, name ] } + wp_db_user: { get_property: [ mysql_database, user ] } + wp_db_password: { get_property: [ mysql_database, password ] } mysql_database: type: tosca.nodes.Database @@ -61,17 +61,17 @@ topology_template: properties: port: { get_input: db_port } requirements: - - host: - node: mysql_dbms + - host: mysql_dbms interfaces: Standard: configure: implementation: mysql/mysql_database_configure.sh inputs: - db_name: wordpress - db_user: wp_user - db_password: wp_pass - db_root_password: passw0rd + db_name: { get_property: [ SELF, name ] } + db_user: { get_property: [ SELF, user ] } + db_password: { get_property: [ SELF, password ] } + db_root_password: { get_property: [ mysql_dbms, root_password ] } + mysql_dbms: type: tosca.nodes.DBMS properties: @@ -84,12 +84,12 @@ topology_template: create: implementation: mysql/mysql_dbms_install.sh inputs: - db_root_password: passw0rd + db_root_password: { get_property: [ mysql_dbms, root_password ] } start: mysql/mysql_dbms_start.sh configure: implementation: mysql/mysql_dbms_configure.sh inputs: - db_port: 3366 + db_port: { get_property: [ mysql_dbms, port ] } webserver: type: tosca.nodes.WebServer @@ -99,6 +99,7 @@ topology_template: Standard: create: webserver/webserver_install.sh start: webserver/webserver_start.sh + server: type: tosca.nodes.Compute capabilities: diff --git a/toscaparser/tests/test_constraints.py b/toscaparser/tests/test_constraints.py index 784ff5b6..10cec099 100644 --- a/toscaparser/tests/test_constraints.py +++ b/toscaparser/tests/test_constraints.py @@ -100,8 +100,8 @@ class ConstraintTest(TestCase): constraint = Constraint('prop', Schema.INTEGER, schema) error = self.assertRaises(exception.ValidationError, constraint.validate, 5) - self.assertEqual('prop: 5 is not an valid value "[2, 4, 6, 8]".', - str(error)) + self.assertEqual('prop: 5 is not a valid value. Expected a value from ' + '"[2, 4, 6, 8]".', str(error)) def test_invalid_in_range(self): snippet = 'in_range: {2, 6}' diff --git a/toscaparser/tests/test_datatypes.py b/toscaparser/tests/test_datatypes.py index f5dcd9b1..bfc17657 100644 --- a/toscaparser/tests/test_datatypes.py +++ b/toscaparser/tests/test_datatypes.py @@ -221,7 +221,7 @@ class DataTypeTest(TestCase): data = DataEntity('tosca.my.datatypes.PeopleBase', value, DataTypeTest.custom_type_def) error = self.assertRaises(ValueError, data.validate) - self.assertEqual('"123" is not a string', error.__str__()) + self.assertEqual('"123" is not a string.', error.__str__()) # the value of name doesn't meet the defined constraint def test_value_error_in_dataentity(self): @@ -247,7 +247,7 @@ class DataTypeTest(TestCase): data = DataEntity('tosca.my.datatypes.People', value, DataTypeTest.custom_type_def) error = self.assertRaises(ValueError, data.validate) - self.assertEqual('"1" is not a string', error.__str__()) + self.assertEqual('"1" is not a string.', error.__str__()) # contact_pone is an invalid field name in nested datatype def test_validation_in_nested_datatype(self): @@ -289,11 +289,11 @@ class DataTypeTest(TestCase): "data/datatypes/test_custom_datatypes_value_error.yaml") error = self.assertRaises(ValueError, ToscaTemplate, tpl_path) self.assertEqual('"[\'1 foo street\', \'9 bar avenue\']" ' - 'is not a map', error.__str__()) + 'is not a map.', error.__str__()) def test_datatype_in_template_nested_datatype_error(self): tpl_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/datatypes/test_custom_datatypes_nested_datatype_error.yaml") error = self.assertRaises(ValueError, ToscaTemplate, tpl_path) - self.assertEqual('"123456789" is not a string', error.__str__()) + self.assertEqual('"123456789" is not a string.', error.__str__()) diff --git a/toscaparser/tests/test_functions.py b/toscaparser/tests/test_functions.py index 268d54bd..d66a5b84 100644 --- a/toscaparser/tests/test_functions.py +++ b/toscaparser/tests/test_functions.py @@ -23,7 +23,8 @@ class IntrinsicFunctionsTest(TestCase): tosca_tpl = os.path.join( os.path.dirname(os.path.abspath(__file__)), "data/tosca_single_instance_wordpress.yaml") - tosca = ToscaTemplate(tosca_tpl) + params = {'db_name': 'my_wordpress', 'db_user': 'my_db_user'} + tosca = ToscaTemplate(tosca_tpl, parsed_params=params) def _get_node(self, node_name): return [ @@ -39,34 +40,30 @@ class IntrinsicFunctionsTest(TestCase): return [prop.value for prop in node_template.get_properties_objects() if prop.name == property_name][0] - def test_get_property(self): - TestCase.skip(self, 'bug #1440247') - mysql_dbms = self._get_node('mysql_dbms') - operation = self._get_operation(mysql_dbms.interfaces, 'configure') - db_root_password = operation.inputs['db_root_password'] - self.assertTrue(isinstance(db_root_password, functions.GetProperty)) - result = db_root_password.result() - self.assertTrue(isinstance(result, functions.GetInput)) + def _get_inputs_dict(self): + inputs = {} + for input in self.tosca.inputs: + inputs[input.name] = input.default + return inputs - def test_get_requirement_property(self): - TestCase.skip(self, 'bug #1440247') + def _get_input(self, name): + self._get_inputs_dict()[name] + + def test_get_property(self): wordpress = self._get_node('wordpress') operation = self._get_operation(wordpress.interfaces, 'configure') - wp_db_port = operation.inputs['wp_db_port'] - self.assertTrue(isinstance(wp_db_port, functions.GetProperty)) - result = wp_db_port.result() - self.assertTrue(isinstance(result, functions.GetInput)) - self.assertEqual('db_port', result.input_name) + wp_db_password = operation.inputs['wp_db_password'] + self.assertTrue(isinstance(wp_db_password, functions.GetProperty)) + result = wp_db_password.result() + self.assertEqual('wp_pass', result) - def test_get_capability_property(self): - TestCase.skip(self, 'bug #1440247') - mysql_database = self._get_node('mysql_database') - operation = self._get_operation(mysql_database.interfaces, 'configure') - db_port = operation.inputs['db_port'] - self.assertTrue(isinstance(db_port, functions.GetProperty)) - result = db_port.result() - self.assertTrue(isinstance(result, functions.GetInput)) - self.assertEqual('db_port', result.input_name) + def test_get_property_with_input_param(self): + wordpress = self._get_node('wordpress') + operation = self._get_operation(wordpress.interfaces, 'configure') + wp_db_user = operation.inputs['wp_db_user'] + self.assertTrue(isinstance(wp_db_user, functions.GetProperty)) + result = wp_db_user.result() + self.assertEqual('my_db_user', result) def test_unknown_capability_property(self): err = self.assertRaises( @@ -87,13 +84,6 @@ class IntrinsicFunctionsTest(TestCase): expected_inputs.remove(prop.value.input_name) self.assertListEqual(expected_inputs, []) - def test_get_input_in_interface(self): - TestCase.skip(self, 'bug #1440247') - mysql_dbms = self._get_node('mysql_dbms') - operation = self._get_operation(mysql_dbms.interfaces, 'configure') - db_user = operation.inputs['db_user'] - self.assertTrue(isinstance(db_user, functions.GetInput)) - def test_get_input_validation(self): self.assertRaises(exception.UnknownInputError, self._load_template, diff --git a/toscaparser/tests/test_properties.py b/toscaparser/tests/test_properties.py index 8e3ba9d1..8eb44ffb 100644 --- a/toscaparser/tests/test_properties.py +++ b/toscaparser/tests/test_properties.py @@ -44,7 +44,7 @@ class PropertyTest(TestCase): propertyInstance = Property('test_property', 'a', test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) - self.assertEqual('"a" is not a list', str(error)) + self.assertEqual('"a" is not a list.', str(error)) def test_list_entry_schema(self): test_property_schema = {'type': 'list', @@ -73,8 +73,7 @@ class PropertyTest(TestCase): propertyInstance = Property('test_property', [1, 'b'], test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) - self.assertEqual('"b" is not an integer', - str(error)) + self.assertEqual('"b" is not an integer.', str(error)) def test_map(self): test_property_schema = {'type': 'map'} @@ -88,7 +87,7 @@ class PropertyTest(TestCase): propertyInstance = Property('test_property', 12, test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) - self.assertEqual('"12" is not a map', str(error)) + self.assertEqual('"12" is not a map.', str(error)) def test_map_entry_schema(self): test_property_schema = {'type': 'map', @@ -107,7 +106,7 @@ class PropertyTest(TestCase): {'valid': True, 'contact_name': 123}, test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) - self.assertEqual('"123" is not a boolean', str(error)) + self.assertEqual('"123" is not a boolean.', str(error)) def test_boolean(self): test_property_schema = {'type': 'boolean'} @@ -124,7 +123,7 @@ class PropertyTest(TestCase): propertyInstance = Property('test_property', 12, test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) - self.assertEqual('"12" is not a boolean', str(error)) + self.assertEqual('"12" is not a boolean.', str(error)) def test_float(self): test_property_schema = {'type': 'float'} @@ -138,7 +137,7 @@ class PropertyTest(TestCase): propertyInstance = Property('test_property', 12, test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) - self.assertEqual('"12" is not a float', str(error)) + self.assertEqual('"12" is not a float.', str(error)) def test_timestamp(self): test_property_schema = {'type': 'timestamp'} diff --git a/toscaparser/tests/test_toscatpl.py b/toscaparser/tests/test_toscatpl.py index 5f90124c..ae1dbd24 100644 --- a/toscaparser/tests/test_toscatpl.py +++ b/toscaparser/tests/test_toscatpl.py @@ -68,7 +68,7 @@ class ToscaTemplateTest(TestCase): expected_type = "tosca.nodes.Database" expected_properties = ['name', 'password', 'user'] expected_capabilities = ['database_endpoint'] - expected_requirements = [{'host': {'node': 'mysql_dbms'}}] + expected_requirements = [{'host': 'mysql_dbms'}] ''' TODO: needs enhancement in tosca_elk.yaml.. expected_relationshp = ['tosca.relationships.HostedOn'] expected_host = ['mysql_dbms'] diff --git a/toscaparser/tests/test_toscatplvalidation.py b/toscaparser/tests/test_toscatplvalidation.py index 97e3a98c..90430529 100644 --- a/toscaparser/tests/test_toscatplvalidation.py +++ b/toscaparser/tests/test_toscatplvalidation.py @@ -730,7 +730,7 @@ custom_types/wordpress.yaml capability: log_endpoint occurrences: [a, w] ''' - expectedmessage = ('"a" is not an integer') + expectedmessage = ('"a" is not an integer.') err = self.assertRaises( ValueError, lambda: self._single_node_template_content_test(tpl_snippet)) @@ -745,7 +745,7 @@ custom_types/wordpress.yaml capability: log_endpoint occurrences: -1 ''' - expectedmessage = ('"-1" is not a list') + expectedmessage = ('"-1" is not a list.') err = self.assertRaises( ValueError, lambda: self._single_node_template_content_test(tpl_snippet)) @@ -1000,7 +1000,7 @@ custom_types/wordpress.yaml lambda: self._single_node_template_content_test(tpl_snippet)) self.assertEqual(expectedmessage, err.__str__()) - # validatating capability property values + # validating capability property values tpl_snippet = ''' node_templates: server: @@ -1010,8 +1010,8 @@ custom_types/wordpress.yaml properties: initiator: test ''' - expectedmessage = ('initiator: test is not an valid value ' - '"[source, target, peer]".') + expectedmessage = ('initiator: test is not a valid value. Expected a ' + 'value from "[source, target, peer]".') err = self.assertRaises( exception.ValidationError, diff --git a/toscaparser/utils/validateutils.py b/toscaparser/utils/validateutils.py index f24e4d48..19f93b01 100644 --- a/toscaparser/utils/validateutils.py +++ b/toscaparser/utils/validateutils.py @@ -42,31 +42,31 @@ def validate_integer(value): try: value = int(value) except Exception: - raise ValueError(_('"%s" is not an integer') % value) + raise ValueError(_('"%s" is not an integer.') % value) return value def validate_float(value): if not isinstance(value, float): - raise ValueError(_('"%s" is not a float') % value) + raise ValueError(_('"%s" is not a float.') % value) return validate_number(value) def validate_string(value): if not isinstance(value, six.string_types): - raise ValueError(_('"%s" is not a string') % value) + raise ValueError(_('"%s" is not a string.') % value) return value def validate_list(value): if not isinstance(value, list): - raise ValueError(_('"%s" is not a list') % value) + raise ValueError(_('"%s" is not a list.') % value) return value def validate_map(value): if not isinstance(value, collections.Mapping): - raise ValueError(_('"%s" is not a map') % value) + raise ValueError(_('"%s" is not a map.') % value) return value @@ -78,7 +78,7 @@ def validate_boolean(value): normalised = value.lower() if normalised in ['true', 'false']: return normalised == 'true' - raise ValueError(_('"%s" is not a boolean') % value) + raise ValueError(_('"%s" is not a boolean.') % value) def validate_timestamp(value):