Add getting custom node by capability
[parser.git] / tosca2heat / tosca-parser / toscaparser / nodetemplate.py
1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
2 #    not use this file except in compliance with the License. You may obtain
3 #    a copy of the License at
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
7 #    Unless required by applicable law or agreed to in writing, software
8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 #    License for the specific language governing permissions and limitations
11 #    under the License.
12
13
14 import logging
15
16 from toscaparser.common.exception import ExceptionCollector
17 from toscaparser.common.exception import InvalidPropertyValueError
18 from toscaparser.common.exception import MissingRequiredFieldError
19 from toscaparser.common.exception import TypeMismatchError
20 from toscaparser.common.exception import UnknownFieldError
21 from toscaparser.common.exception import ValidationError
22 from toscaparser.dataentity import DataEntity
23 from toscaparser.elements.interfaces import CONFIGURE
24 from toscaparser.elements.interfaces import CONFIGURE_SHORTNAME
25 from toscaparser.elements.interfaces import INTERFACE_DEF_RESERVED_WORDS
26 from toscaparser.elements.interfaces import InterfacesDef
27 from toscaparser.elements.interfaces import LIFECYCLE
28 from toscaparser.elements.interfaces import LIFECYCLE_SHORTNAME
29 from toscaparser.elements.relationshiptype import RelationshipType
30 from toscaparser.entity_template import EntityTemplate
31 from toscaparser.relationship_template import RelationshipTemplate
32 from toscaparser.utils.gettextutils import _
33
34 log = logging.getLogger('tosca')
35
36
37 class NodeTemplate(EntityTemplate):
38     '''Node template from a Tosca profile.'''
39     def __init__(self, name, node_templates, custom_def=None,
40                  available_rel_tpls=None, available_rel_types=None):
41         super(NodeTemplate, self).__init__(name, node_templates[name],
42                                            'node_type',
43                                            custom_def)
44         self.templates = node_templates
45         self._validate_fields(node_templates[name])
46         self.custom_def = custom_def
47         self.related = {}
48         self.relationship_tpl = []
49         self.available_rel_tpls = available_rel_tpls
50         self.available_rel_types = available_rel_types
51         self._relationships = {}
52         self.substitution_mapped = None
53
54     @property
55     def relationships(self):
56         if not self._relationships:
57             requires = self.requirements
58             if requires and isinstance(requires, list):
59                 for r in requires:
60                     for r1, value in r.items():
61                         explicit = self._get_explicit_relationship(r, value)
62                         if explicit:
63                             for key, value in explicit.items():
64                                 self._relationships[key] = value
65         return self._relationships
66
67     def _get_explicit_relationship(self, req, value):
68         """Handle explicit relationship
69
70         For example,
71         - req:
72             node: DBMS
73             relationship: tosca.relationships.HostedOn
74         """
75         explicit_relation = {}
76         node = value.get('node') if isinstance(value, dict) else value
77
78         if node:
79             # TODO(spzala) implement look up once Glance meta data is available
80             # to find a matching TOSCA node using the TOSCA types
81             msg = _('Lookup by TOSCA types is not supported. '
82                     'Requirement node "%(node)s" for "%(name)s"'
83                     ' can not be full-filled.') \
84                 % {'node': node, 'name': self.name}
85             if (node in list(self.type_definition.TOSCA_DEF.keys())
86                or node in self.custom_def):
87                 ExceptionCollector.appendException(NotImplementedError(msg))
88                 return
89
90             if node not in self.templates:
91                 ExceptionCollector.appendException(
92                     KeyError(_('Node template "%(node)s" was not found'
93                                ' in "%(name)s".')
94                              % {'node': node, 'name': self.name}))
95                 return
96
97             related_tpl = NodeTemplate(node, self.templates, self.custom_def)
98             relationship = value.get('relationship') \
99                 if isinstance(value, dict) else None
100             # check if it's type has relationship defined
101             if not relationship:
102                 parent_reqs = self.type_definition.get_all_requirements()
103                 if parent_reqs is None:
104                     ExceptionCollector.appendException(
105                         ValidationError(message='parent_req is ' +
106                                         str(parent_reqs)))
107                 else:
108                     for key in req.keys():
109                         for req_dict in parent_reqs:
110                             if key in req_dict.keys():
111                                 relationship = (req_dict.get(key).
112                                                 get('relationship'))
113                                 break
114             if relationship:
115                 found_relationship_tpl = False
116
117                 # apply available relationship templates if found
118                 if self.available_rel_tpls:
119                     for tpl in self.available_rel_tpls:
120                         if tpl.name == relationship:
121                             rtype = RelationshipType(tpl.type, None,
122                                                      self.custom_def)
123                             explicit_relation[rtype] = related_tpl
124                             tpl.target = related_tpl
125                             tpl.source = self
126                             self.relationship_tpl.append(tpl)
127                             found_relationship_tpl = True
128                 # create relationship template object.
129                 rel_prfx = self.type_definition.RELATIONSHIP_PREFIX
130                 if not found_relationship_tpl:
131                     if isinstance(relationship, dict):
132                         relationship = relationship.get('type')
133                         if relationship:
134                             if self.available_rel_types and \
135                                relationship in self.available_rel_types.keys():
136                                 pass
137                             elif not relationship.startswith(rel_prfx):
138                                 relationship = rel_prfx + relationship
139                         else:
140                             ExceptionCollector.appendException(
141                                 MissingRequiredFieldError(
142                                     what=_('"relationship" used in template '
143                                            '"%s"') % related_tpl.name,
144                                     required=self.TYPE))
145                     for rtype in self.type_definition.relationship.keys():
146                         if rtype.type == relationship:
147                             explicit_relation[rtype] = related_tpl
148                             related_tpl._add_relationship_template(req,
149                                                                    rtype.type,
150                                                                    self)
151                         elif self.available_rel_types:
152                             if relationship in self.available_rel_types.keys():
153                                 rel_type_def = self.available_rel_types.\
154                                     get(relationship)
155                                 if 'derived_from' in rel_type_def:
156                                     super_type = \
157                                         rel_type_def.get('derived_from')
158                                     if not super_type.startswith(rel_prfx):
159                                         super_type = rel_prfx + super_type
160                                     if rtype.type == super_type:
161                                         explicit_relation[rtype] = related_tpl
162                                         related_tpl.\
163                                             _add_relationship_template(
164                                                 req, rtype.type, self)
165         return explicit_relation
166
167     def _add_relationship_template(self, requirement, rtype, source):
168         req = requirement.copy()
169         req['type'] = rtype
170         tpl = RelationshipTemplate(req, rtype, self.custom_def, self, source)
171         self.relationship_tpl.append(tpl)
172
173     def get_relationship_template(self):
174         return self.relationship_tpl
175
176     def _add_next(self, nodetpl, relationship):
177         self.related[nodetpl] = relationship
178
179     @property
180     def related_nodes(self):
181         if not self.related:
182             for relation, node in self.type_definition.relationship.items():
183                 for tpl in self.templates:
184                     if tpl == node.type:
185                         self.related[NodeTemplate(tpl)] = relation
186         return self.related.keys()
187
188     def validate(self, tosca_tpl=None):
189         self._validate_capabilities()
190         self._validate_requirements()
191         self._validate_properties(self.entity_tpl, self.type_definition)
192         self._validate_interfaces()
193         for prop in self.get_properties_objects():
194             prop.validate()
195
196     def _validate_requirements(self):
197         type_requires = self.type_definition.get_all_requirements()
198         allowed_reqs = ["template"]
199         if type_requires:
200             for treq in type_requires:
201                 for key, value in treq.items():
202                     allowed_reqs.append(key)
203                     if isinstance(value, dict):
204                         for key in value:
205                             allowed_reqs.append(key)
206
207         requires = self.type_definition.get_value(self.REQUIREMENTS,
208                                                   self.entity_tpl)
209         if requires:
210             if not isinstance(requires, list):
211                 ExceptionCollector.appendException(
212                     TypeMismatchError(
213                         what='"requirements" of template "%s"' % self.name,
214                         type='list'))
215             else:
216                 for req in requires:
217                     for r1, value in req.items():
218                         if isinstance(value, dict):
219                             self._validate_requirements_keys(value)
220                             self._validate_requirements_properties(value)
221                             allowed_reqs.append(r1)
222                     self._common_validate_field(req, allowed_reqs,
223                                                 'requirements')
224
225     def _validate_requirements_properties(self, requirements):
226         # TODO(anyone): Only occurrences property of the requirements is
227         # validated here. Validation of other requirement properties are being
228         # validated in different files. Better to keep all the requirements
229         # properties validation here.
230         for key, value in requirements.items():
231             if key == 'occurrences':
232                 self._validate_occurrences(value)
233                 break
234
235     def _validate_occurrences(self, occurrences):
236         DataEntity.validate_datatype('list', occurrences)
237         for value in occurrences:
238             DataEntity.validate_datatype('integer', value)
239         if len(occurrences) != 2 or not (0 <= occurrences[0] <= occurrences[1]) \
240                 or occurrences[1] == 0:
241             ExceptionCollector.appendException(
242                 InvalidPropertyValueError(what=(occurrences)))
243
244     def _validate_requirements_keys(self, requirement):
245         for key in requirement.keys():
246             if key not in self.REQUIREMENTS_SECTION:
247                 ExceptionCollector.appendException(
248                     UnknownFieldError(
249                         what='"requirements" of template "%s"' % self.name,
250                         field=key))
251
252     def _validate_interfaces(self):
253         ifaces = self.type_definition.get_value(self.INTERFACES,
254                                                 self.entity_tpl)
255         if ifaces:
256             for name, value in ifaces.items():
257                 if name in (LIFECYCLE, LIFECYCLE_SHORTNAME):
258                     self._common_validate_field(
259                         value, InterfacesDef.
260                         interfaces_node_lifecycle_operations,
261                         'interfaces')
262                 elif name in (CONFIGURE, CONFIGURE_SHORTNAME):
263                     self._common_validate_field(
264                         value, InterfacesDef.
265                         interfaces_relationship_configure_operations,
266                         'interfaces')
267                 elif name in self.type_definition.interfaces.keys():
268                     self._common_validate_field(
269                         value,
270                         self._collect_custom_iface_operations(name),
271                         'interfaces')
272                 else:
273                     ExceptionCollector.appendException(
274                         UnknownFieldError(
275                             what='"interfaces" of template "%s"' %
276                             self.name, field=name))
277
278     def _collect_custom_iface_operations(self, name):
279         allowed_operations = []
280         nodetype_iface_def = self.type_definition.interfaces[name]
281         allowed_operations.extend(nodetype_iface_def.keys())
282         if 'type' in nodetype_iface_def:
283             iface_type = nodetype_iface_def['type']
284             if iface_type in self.type_definition.custom_def:
285                 iface_type_def = self.type_definition.custom_def[iface_type]
286             else:
287                 iface_type_def = self.type_definition.TOSCA_DEF[iface_type]
288             allowed_operations.extend(iface_type_def.keys())
289         allowed_operations = [op for op in allowed_operations if
290                               op not in INTERFACE_DEF_RESERVED_WORDS]
291         return allowed_operations
292
293     def _validate_fields(self, nodetemplate):
294         for name in nodetemplate.keys():
295             if name not in self.SECTIONS and name not in self.SPECIAL_SECTIONS:
296                 ExceptionCollector.appendException(
297                     UnknownFieldError(what='Node template "%s"' % self.name,
298                                       field=name))