From 88b414f324043f6e641474c8846bb6b5e2aadcf8 Mon Sep 17 00:00:00 2001 From: Idan Moyal Date: Thu, 4 Dec 2014 01:47:59 +0200 Subject: [PATCH] get_attribute HOST keyword Added HOST keyword support for get_attribute function. Implements: blueprint host-function-keyword Change-Id: Ie9cee9b95782efb056e4003dc6e7f1888c7489f2 --- translator/hot/translate_outputs.py | 2 +- translator/toscalib/entity_template.py | 8 +- translator/toscalib/functions.py | 76 ++++++++++++++++--- .../test_get_attribute_host_keyword.yaml | 32 ++++++++ .../test_get_attribute_host_not_found.yaml | 16 ++++ ...get_attribute_illegal_host_in_outputs.yaml | 14 ++++ translator/toscalib/tests/test_functions.py | 37 +++++++++ 7 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 translator/toscalib/tests/data/functions/test_get_attribute_host_keyword.yaml create mode 100644 translator/toscalib/tests/data/functions/test_get_attribute_host_not_found.yaml create mode 100644 translator/toscalib/tests/data/functions/test_get_attribute_illegal_host_in_outputs.yaml diff --git a/translator/hot/translate_outputs.py b/translator/hot/translate_outputs.py index a082d283..68464123 100644 --- a/translator/hot/translate_outputs.py +++ b/translator/hot/translate_outputs.py @@ -34,7 +34,7 @@ class TranslateOutputs(): if isinstance(output.value, functions.GetAttribute): func = output.value get_parameters = [ - func.node_template_name, + func.get_referenced_node_template().name, self._translate_attribute_name(func.attribute_name)] hot_value['get_attr'] = get_parameters elif isinstance(output.value, functions.GetProperty): diff --git a/translator/toscalib/entity_template.py b/translator/toscalib/entity_template.py index 92e41f50..7fd003fc 100644 --- a/translator/toscalib/entity_template.py +++ b/translator/toscalib/entity_template.py @@ -41,6 +41,7 @@ class EntityTemplate(object): custom_def) self._properties = None self._interfaces = None + self._requirements = None self._capabilities = None @property @@ -49,8 +50,11 @@ class EntityTemplate(object): @property def requirements(self): - return self.type_definition.get_value(self.REQUIREMENTS, - self.entity_tpl) + if self._requirements is None: + self._requirements = self.type_definition.get_value( + self.REQUIREMENTS, + self.entity_tpl) or [] + return self._requirements @property def properties(self): diff --git a/translator/toscalib/functions.py b/translator/toscalib/functions.py index 4e239bee..78118667 100644 --- a/translator/toscalib/functions.py +++ b/translator/toscalib/functions.py @@ -23,6 +23,9 @@ GET_ATTRIBUTE = 'get_attribute' GET_INPUT = 'get_input' SELF = 'SELF' +HOST = 'HOST' + +HOSTED_ON = 'tosca.relationships.HostedOn' class Function(object): @@ -94,12 +97,17 @@ class GetAttribute(Function): Arguments: - * Node template name. + * Node template name | HOST. * Attribute name. + If the HOST keyword is passed as the node template name argument the + function will search each node template along the HostedOn relationship + chain until a node which contains the attribute is found. + Examples: * { get_attribute: [ server, ip_address ] } + * { get_attribute: [ HOST, ip_address ] } """ def validate(self): @@ -107,25 +115,69 @@ class GetAttribute(Function): raise ValueError(_( 'Illegal arguments for {0} function. Expected arguments: ' 'node-template-name, attribute-name').format(GET_ATTRIBUTE)) - self._find_attribute(self.args[1]) + self._find_node_template_containing_attribute() def result(self): pass - def _find_attribute(self, attribute_name): - node_tpl = self._find_node_template(self.args[0]) - found = [ - attr for attr in node_tpl.type_definition.attributes_def - if attr.name == attribute_name] - if len(found) == 0: + def get_referenced_node_template(self): + """Gets the NodeTemplate instance the get_attribute function refers to. + + If HOST keyword was used as the node template argument, the node + template which contains the attribute along the HostedOn relationship + chain will be returned. + """ + return self._find_node_template_containing_attribute() + + def _find_node_template_containing_attribute(self): + if self.node_template_name == HOST: + # Currently this is the only way to tell whether the function + # is used within the outputs section of the TOSCA template. + if isinstance(self.context, list): + raise ValueError(_( + "get_attribute HOST keyword is not allowed within the " + "outputs section of the TOSCA template")) + node_tpl = self._find_host_containing_attribute() + if not node_tpl: + raise ValueError(_( + "get_attribute HOST keyword is used in '{0}' node " + "template but {1} was not found " + "in relationship chain").format(self.context.name, + HOSTED_ON)) + else: + node_tpl = self._find_node_template(self.args[0]) + if not self._attribute_exists_in_type(node_tpl.type_definition): raise KeyError(_( - "Attribute: '{0}' not found in node template: {1}.").format( - attribute_name, node_tpl.name)) - return found[0] + "Attribute '{0}' not found in node template: {1}.").format( + self.attribute_name, node_tpl.name)) + return node_tpl + + def _attribute_exists_in_type(self, type_definition): + found = [attr for attr in type_definition.attributes_def + if attr.name == self.attribute_name] + return len(found) == 1 + + def _find_host_containing_attribute(self, node_template_name=SELF): + node_template = self._find_node_template(node_template_name) + from translator.toscalib.elements.entitytype import EntityType + hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON] + for r in node_template.requirements: + for requirement, target_name in r.items(): + target_node = self._find_node_template(target_name) + target_type = target_node.type_definition + for capability in target_type.capabilities: + if capability.type in hosted_on_rel['valid_targets']: + if self._attribute_exists_in_type(target_type): + return target_node + return self._find_host_containing_attribute( + target_name) + return None def _find_node_template(self, node_template_name): + name = self.context.name if node_template_name == SELF else \ + node_template_name for node_template in self.tosca_tpl.nodetemplates: - if node_template.name == node_template_name: + if node_template.name == name: return node_template raise KeyError(_( 'No such node template: {0}.').format(node_template_name)) diff --git a/translator/toscalib/tests/data/functions/test_get_attribute_host_keyword.yaml b/translator/toscalib/tests/data/functions/test_get_attribute_host_keyword.yaml new file mode 100644 index 00000000..369a9ba8 --- /dev/null +++ b/translator/toscalib/tests/data/functions/test_get_attribute_host_keyword.yaml @@ -0,0 +1,32 @@ +tosca_definitions_version: tosca_simple_1.0 + +description: > + TOSCA template for testing get_attribute with HOST keyword. + +node_templates: + server: + type: tosca.nodes.Compute + properties: + os_type: Linux + + dbms: + type: tosca.nodes.DBMS + requirements: + - host: server + interfaces: + tosca.interfaces.node.Lifecycle: + configure: + implementation: configure.sh + input: + ip_address: { get_attribute: [ HOST, ip_address ] } + + database: + type: tosca.nodes.Database + requirements: + - host: dbms + interfaces: + tosca.interfaces.node.Lifecycle: + configure: + implementation: configure.sh + input: + ip_address: { get_attribute: [ HOST, ip_address ] } diff --git a/translator/toscalib/tests/data/functions/test_get_attribute_host_not_found.yaml b/translator/toscalib/tests/data/functions/test_get_attribute_host_not_found.yaml new file mode 100644 index 00000000..d3c123a7 --- /dev/null +++ b/translator/toscalib/tests/data/functions/test_get_attribute_host_not_found.yaml @@ -0,0 +1,16 @@ +tosca_definitions_version: tosca_simple_1.0 + +description: > + TOSCA template for testing get_attribute with HOST keyword. + +node_templates: + server: + type: tosca.nodes.Compute + properties: + os_type: Linux + interfaces: + tosca.interfaces.node.Lifecycle: + configure: + implementation: configure.sh + input: + ip_address: { get_attribute: [ HOST, ip_address ] } diff --git a/translator/toscalib/tests/data/functions/test_get_attribute_illegal_host_in_outputs.yaml b/translator/toscalib/tests/data/functions/test_get_attribute_illegal_host_in_outputs.yaml new file mode 100644 index 00000000..65b398c7 --- /dev/null +++ b/translator/toscalib/tests/data/functions/test_get_attribute_illegal_host_in_outputs.yaml @@ -0,0 +1,14 @@ +tosca_definitions_version: tosca_simple_1.0 + +description: > + TOSCA template for testing get_attribute with HOST keyword. + +node_templates: + server: + type: tosca.nodes.Compute + properties: + os_type: Linux + +outputs: + ip_address: + value: { get_attribute: [ HOST, ip_address ] } diff --git a/translator/toscalib/tests/test_functions.py b/translator/toscalib/tests/test_functions.py index 546339a6..772113bb 100644 --- a/translator/toscalib/tests/test_functions.py +++ b/translator/toscalib/tests/test_functions.py @@ -143,3 +143,40 @@ class GetAttributeTest(TestCase): 'functions/test_get_attribute_unknown_attribute_name.yaml') self.assertIn('unknown_attribute', six.text_type(err)) self.assertIn('server', six.text_type(err)) + + def test_get_attribute_host_keyword(self): + tpl = self._load_template( + 'functions/test_get_attribute_host_keyword.yaml') + + def assert_get_attribute_host_functionality(node_template_name): + node = [x for x in tpl.nodetemplates + if x.name == node_template_name][0] + configure_op = [ + x for x in node.interfaces if x.name == 'configure'][0] + ip_addr_input = configure_op.input['ip_address'] + self.assertIsInstance(ip_addr_input, functions.GetAttribute) + self.assertEqual('server', + ip_addr_input.get_referenced_node_template().name) + + assert_get_attribute_host_functionality('dbms') + assert_get_attribute_host_functionality('database') + + def test_get_attribute_host_not_found(self): + err = self.assertRaises( + ValueError, + self._load_template, + 'functions/test_get_attribute_host_not_found.yaml') + self.assertIn( + "get_attribute HOST keyword is used in 'server' node template but " + "tosca.relationships.HostedOn was not found in relationship chain", + six.text_type(err)) + + def test_get_attribute_illegal_host_in_outputs(self): + err = self.assertRaises( + ValueError, + self._load_template, + 'functions/test_get_attribute_illegal_host_in_outputs.yaml') + self.assertIn( + "get_attribute HOST keyword is not allowed within the outputs " + "section of the TOSCA template", + six.text_type(err))