From b6832923b29353ceea23b8f246cf479952563e4a Mon Sep 17 00:00:00 2001 From: huruifeng Date: Tue, 21 Apr 2015 17:02:54 +0800 Subject: [PATCH] Inital Implementation of topology template Add a class which can read the 'topology_template' section in a service template. And related tests. Substitution mapping is not implemented yet. partially implements: bp tosca-topology-template Change-Id: I5f63c706c97a1078d3c6a99bf349f0043ca20cf5 --- translator/toscalib/groups.py | 27 +++ .../toscalib/tests/test_topology_template.py | 153 +++++++++++++++ translator/toscalib/topology_template.py | 177 ++++++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 translator/toscalib/groups.py create mode 100644 translator/toscalib/tests/test_topology_template.py create mode 100644 translator/toscalib/topology_template.py diff --git a/translator/toscalib/groups.py b/translator/toscalib/groups.py new file mode 100644 index 00000000..40ebcf54 --- /dev/null +++ b/translator/toscalib/groups.py @@ -0,0 +1,27 @@ +# 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. + + +class NodeGroup(object): + + def __init__(self, name, group_templates, member_nodes): + self.name = name + self.tpl = group_templates + self.members = member_nodes + + @property + def member_names(self): + return self.tpl.get('members') + + @property + def policies(self): + return self.tpl.get('policies') diff --git a/translator/toscalib/tests/test_topology_template.py b/translator/toscalib/tests/test_topology_template.py new file mode 100644 index 00000000..98027dc4 --- /dev/null +++ b/translator/toscalib/tests/test_topology_template.py @@ -0,0 +1,153 @@ +# 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.tests.base import TestCase +from translator.toscalib.topology_template import TopologyTemplate +import translator.toscalib.utils.yamlparser + +YAML_LOADER = translator.toscalib.utils.yamlparser.load_yaml + + +class TopologyTemplateTest(TestCase): + + def setUp(self): + TestCase.setUp(self) + '''TOSCA template.''' + self.tosca_tpl_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/topology_template/subsystem.yaml") + self.tpl = YAML_LOADER(self.tosca_tpl_path) + self.topo_tpl = self.tpl.get('topology_template') + self.imports = self.tpl.get('imports') + self.topo = TopologyTemplate(self.topo_tpl, + self._get_all_custom_def()) + + def _get_custom_def(self, type_definition): + custom_defs = {} + for definition in self.imports: + if os.path.isabs(definition): + def_file = definition + else: + tpl_dir = os.path.dirname(os.path.abspath(self.tosca_tpl_path)) + def_file = os.path.join(tpl_dir, definition) + custom_type = YAML_LOADER(def_file) + custom_defs.update(custom_type.get(type_definition)) + return custom_defs + + def _get_all_custom_def(self): + custom_defs = {} + custom_defs.update(self._get_custom_def('node_types')) + custom_defs.update(self._get_custom_def('capability_types')) + return custom_defs + + def test_description(self): + expected_desc = 'Template of a database including its hosting stack.' + self.assertEqual(expected_desc, self.topo.description) + + def test_inputs(self): + self.assertEqual( + ['mq_server_ip', 'my_cpus', 'receiver_port'], + sorted([input.name for input in self.topo.inputs])) + + input_name = "receiver_port" + expected_description = "Port to be used for receiving messages." + for input in self.topo.inputs: + if input.name == input_name: + self.assertEqual(expected_description, input.description) + + def test_node_tpls(self): + '''Test nodetemplate names.''' + self.assertEqual( + ['app', 'server', 'websrv'], + sorted([tpl.name for tpl in self.topo.nodetemplates])) + + tpl_name = "app" + expected_type = "example.SomeApp" + expected_properties = ['admin_user', 'pool_size'] + expected_capabilities = ['message_receiver'] + expected_requirements = [{'host': 'websrv'}] + expected_relationshp = ['tosca.relationships.HostedOn'] + expected_host = ['websrv'] + + for tpl in self.topo.nodetemplates: + if tpl_name == tpl.name: + '''Test node type.''' + self.assertEqual(tpl.type, expected_type) + + '''Test properties.''' + self.assertEqual( + expected_properties, + sorted(tpl.get_properties().keys())) + + '''Test capabilities.''' + self.assertEqual( + expected_capabilities, + sorted(tpl.get_capabilities().keys())) + + '''Test requirements.''' + self.assertEqual( + expected_requirements, tpl.requirements) + + '''Test relationship.''' + self.assertEqual( + expected_relationshp, + [x.type for x in tpl.relationships.keys()]) + self.assertEqual( + expected_host, + [y.name for y in tpl.relationships.values()]) + + '''Test interfaces.''' + # TODO(hurf) add interface test when new template is available + + if tpl.name == 'server': + '''Test property value''' + props = tpl.get_properties() + if props and 'mem_size' in props.keys(): + self.assertEqual(props['mem_size'].value, '4096 MB') + '''Test capability''' + caps = tpl.get_capabilities() + self.assertIn('os', caps.keys()) + os_props_objs = None + os_props = None + os_type_prop = None + if caps and 'os' in caps.keys(): + capability = caps['os'] + os_props_objs = capability.get_properties_objects() + os_props = capability.get_properties() + os_type_prop = capability.get_property_value('type') + break + self.assertEqual( + ['Linux'], + [p.value for p in os_props_objs if p.name == 'type']) + self.assertEqual( + 'Linux', + os_props['type'].value if 'type' in os_props else '') + self.assertEqual('Linux', os_props['type'].value) + self.assertEqual('Linux', os_type_prop) + + def test_outputs(self): + self.assertEqual( + ['receiver_ip'], + sorted([output.name for output in self.topo.outputs])) + + def test_groups(self): + group = self.topo.groups[0] + self.assertEqual('webserver_group', group.name) + self.assertEqual(['websrv', 'server'], group.member_names) + for node in group.members: + if node.name == 'server': + '''Test property value''' + props = node.get_properties() + if props and 'mem_size' in props.keys(): + self.assertEqual(props['mem_size'].value, '4096 MB') diff --git a/translator/toscalib/topology_template.py b/translator/toscalib/topology_template.py new file mode 100644 index 00000000..ac36e4cd --- /dev/null +++ b/translator/toscalib/topology_template.py @@ -0,0 +1,177 @@ +# 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 logging + +from translator.toscalib.common import exception +from translator.toscalib import functions +from translator.toscalib.groups import NodeGroup +from translator.toscalib.nodetemplate import NodeTemplate +from translator.toscalib.parameters import Input +from translator.toscalib.parameters import Output +from translator.toscalib.relationship_template import RelationshipTemplate +from translator.toscalib.tpl_relationship_graph import ToscaGraph + + +# Topology template key names +SECTIONS = (DESCRIPTION, INPUTS, NODE_TEMPLATES, + RELATIONSHIP_TEMPLATES, OUTPUTS, GROUPS, + SUBSTITUION_MAPPINGS) = \ + ('description', 'inputs', 'node_templates', + 'relationship_templates', 'outputs', 'groups', + 'substitution_mappings') + +log = logging.getLogger("tosca.model") + + +class TopologyTemplate(object): + + '''Load the template data.''' + def __init__(self, template, custom_defs): + self.tpl = template + self.custom_defs = custom_defs + self._validate_field() + self.description = self._tpl_description() + self.inputs = self._inputs() + self.relationship_templates = self._relationship_templates() + self.nodetemplates = self._nodetemplates() + self.outputs = self._outputs() + self.graph = ToscaGraph(self.nodetemplates) + self.groups = self._groups() + self._process_intrinsic_functions() + + def _inputs(self): + inputs = [] + for name, attrs in self._tpl_inputs().items(): + input = Input(name, attrs) + input.validate() + inputs.append(input) + return inputs + + def _nodetemplates(self): + nodetemplates = [] + tpls = self._tpl_nodetemplates() + for name in tpls: + tpl = NodeTemplate(name, tpls, self.custom_defs, + self.relationship_templates) + tpl.validate(self) + nodetemplates.append(tpl) + return nodetemplates + + def _relationship_templates(self): + rel_templates = [] + tpls = self._tpl_relationship_templates() + for name in tpls: + tpl = RelationshipTemplate(tpls[name], name, self.custom_defs) + rel_templates.append(tpl) + return rel_templates + + def _outputs(self): + outputs = [] + for name, attrs in self._tpl_outputs().items(): + output = Output(name, attrs) + output.validate() + outputs.append(output) + return outputs + + def _substitution_mappings(self): + pass + + def _groups(self): + groups = [] + for group_name, group_tpl in self._tpl_groups().items(): + member_names = group_tpl.get('members') + if member_names and len(member_names) > 1: + group = NodeGroup(group_name, group_tpl, + self._get_group_memerbs(member_names)) + groups.append(group) + else: + raise ValueError + return groups + + def _get_group_memerbs(self, member_names): + member_nodes = [] + for member in member_names: + for node in self.nodetemplates: + if node.name == member: + member_nodes.append(node) + return member_nodes + + # topology template can act like node template + # it is exposed by substitution_mappings. + def nodetype(self): + pass + + def capabilities(self): + pass + + def requirements(self): + pass + + def _tpl_description(self): + return self.tpl[DESCRIPTION].rstrip() + + def _tpl_inputs(self): + return self.tpl.get(INPUTS) or {} + + def _tpl_nodetemplates(self): + return self.tpl[NODE_TEMPLATES] + + def _tpl_relationship_templates(self): + return self.tpl.get(RELATIONSHIP_TEMPLATES) or {} + + def _tpl_outputs(self): + return self.tpl.get(OUTPUTS) or {} + + def _tpl_substitution_mappings(self): + return self.tpl.get(SUBSTITUION_MAPPINGS) or {} + + def _tpl_groups(self): + return self.tpl.get(GROUPS) or {} + + def _validate_field(self): + for name in self.tpl: + if name not in SECTIONS: + raise exception.UnknownFieldError(what='Template', field=name) + + def _process_intrinsic_functions(self): + """Process intrinsic functions + + Current implementation processes functions within node template + properties, requirements, interfaces inputs and template outputs. + """ + for node_template in self.nodetemplates: + for prop in node_template.get_properties_objects(): + prop.value = functions.get_function(self, + node_template, + prop.value) + for interface in node_template.interfaces: + if interface.inputs: + for name, value in interface.inputs.items(): + interface.inputs[name] = functions.get_function( + self, + node_template, + value) + if node_template.requirements: + for req in node_template.requirements: + if 'properties' in req: + for key, value in req['properties'].items(): + req['properties'][key] = functions.get_function( + self, + req, + value) + + for output in self.outputs: + func = functions.get_function(self, self.outputs, output.value) + if isinstance(func, functions.GetAttribute): + output.attrs[output.VALUE] = func