tosca-parser support the semantic of substitution mapping
[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 InterfacesDef
26 from toscaparser.elements.interfaces import LIFECYCLE
27 from toscaparser.elements.interfaces import LIFECYCLE_SHORTNAME
28 from toscaparser.elements.relationshiptype import RelationshipType
29 from toscaparser.entity_template import EntityTemplate
30 from toscaparser.relationship_template import RelationshipTemplate
31 from toscaparser.utils.gettextutils import _
32
33 log = logging.getLogger('tosca')
34
35
36 class NodeTemplate(EntityTemplate):
37     '''Node template from a Tosca profile.'''
38     def __init__(self, name, node_templates, custom_def=None,
39                  available_rel_tpls=None, available_rel_types=None):
40         super(NodeTemplate, self).__init__(name, node_templates[name],
41                                            'node_type',
42                                            custom_def)
43         self.templates = node_templates
44         self._validate_fields(node_templates[name])
45         self.custom_def = custom_def
46         self.related = {}
47         self.relationship_tpl = []
48         self.available_rel_tpls = available_rel_tpls
49         self.available_rel_types = available_rel_types
50         self._relationships = {}
51         self.substitution_mapped = False
52
53     @property
54     def relationships(self):
55         if not self._relationships:
56             requires = self.requirements
57             if requires:
58                 for r in requires:
59                     for r1, value in r.items():
60                         explicit = self._get_explicit_relationship(r, value)
61                         if explicit:
62                             for key, value in explicit.items():
63                                 self._relationships[key] = value
64         return self._relationships
65
66     def _get_explicit_relationship(self, req, value):
67         """Handle explicit relationship
68
69         For example,
70         - req:
71             node: DBMS
72             relationship: tosca.relationships.HostedOn
73         """
74         explicit_relation = {}
75         node = value.get('node') if isinstance(value, dict) else value
76
77         if node:
78             # TODO(spzala) implement look up once Glance meta data is available
79             # to find a matching TOSCA node using the TOSCA types
80             msg = _('Lookup by TOSCA types is not supported. '
81                     'Requirement for "%s" can not be full-filled.') % self.name
82             if (node in list(self.type_definition.TOSCA_DEF.keys())
83                or node in self.custom_def):
84                 ExceptionCollector.appendException(NotImplementedError(msg))
85                 return
86
87             if node not in self.templates:
88                 ExceptionCollector.appendException(
89                     KeyError(_('Node template "%s" was not found.') % node))
90                 return
91
92             related_tpl = NodeTemplate(node, self.templates, self.custom_def)
93             relationship = value.get('relationship') \
94                 if isinstance(value, dict) else None
95             # check if it's type has relationship defined
96             if not relationship:
97                 parent_reqs = self.type_definition.get_all_requirements()
98                 if parent_reqs is None:
99                     ExceptionCollector.appendException(
100                         ValidationError(message='parent_req is ' +
101                                         str(parent_reqs)))
102                 else:
103                     for key in req.keys():
104                         for req_dict in parent_reqs:
105                             if key in req_dict.keys():
106                                 relationship = (req_dict.get(key).
107                                                 get('relationship'))
108                                 break
109             if relationship:
110                 found_relationship_tpl = False
111                 # apply available relationship templates if found
112                 if self.available_rel_tpls:
113                     for tpl in self.available_rel_tpls:
114                         if tpl.name == relationship:
115                             rtype = RelationshipType(tpl.type, None,
116                                                      self.custom_def)
117                             explicit_relation[rtype] = related_tpl
118                             tpl.target = related_tpl
119                             tpl.source = self
120                             self.relationship_tpl.append(tpl)
121                             found_relationship_tpl = True
122                 # create relationship template object.
123                 rel_prfx = self.type_definition.RELATIONSHIP_PREFIX
124                 if not found_relationship_tpl:
125                     if isinstance(relationship, dict):
126                         relationship = relationship.get('type')
127                         if relationship:
128                             if self.available_rel_types and \
129                                relationship in self.available_rel_types.keys():
130                                 pass
131                             elif not relationship.startswith(rel_prfx):
132                                 relationship = rel_prfx + relationship
133                         else:
134                             ExceptionCollector.appendException(
135                                 MissingRequiredFieldError(
136                                     what=_('"relationship" used in template '
137                                            '"%s"') % related_tpl.name,
138                                     required=self.TYPE))
139                     for rtype in self.type_definition.relationship.keys():
140                         if rtype.type == relationship:
141                             explicit_relation[rtype] = related_tpl
142                             related_tpl._add_relationship_template(req,
143                                                                    rtype.type,
144                                                                    self)
145                         elif self.available_rel_types:
146                             if relationship in self.available_rel_types.keys():
147                                 rel_type_def = self.available_rel_types.\
148                                     get(relationship)
149                                 if 'derived_from' in rel_type_def:
150                                     super_type = \
151                                         rel_type_def.get('derived_from')
152                                     if not super_type.startswith(rel_prfx):
153                                         super_type = rel_prfx + super_type
154                                     if rtype.type == super_type:
155                                         explicit_relation[rtype] = related_tpl
156                                         related_tpl.\
157                                             _add_relationship_template(
158                                                 req, rtype.type, self)
159         return explicit_relation
160
161     def _add_relationship_template(self, requirement, rtype, source):
162         req = requirement.copy()
163         req['type'] = rtype
164         tpl = RelationshipTemplate(req, rtype, self.custom_def, self, source)
165         self.relationship_tpl.append(tpl)
166
167     def get_relationship_template(self):
168         return self.relationship_tpl
169
170     def _add_next(self, nodetpl, relationship):
171         self.related[nodetpl] = relationship
172
173     @property
174     def related_nodes(self):
175         if not self.related:
176             for relation, node in self.type_definition.relationship.items():
177                 for tpl in self.templates:
178                     if tpl == node.type:
179                         self.related[NodeTemplate(tpl)] = relation
180         return self.related.keys()
181
182     def validate(self, tosca_tpl=None):
183         self._validate_capabilities()
184         self._validate_requirements()
185         self._validate_properties(self.entity_tpl, self.type_definition)
186         self._validate_interfaces()
187         for prop in self.get_properties_objects():
188             prop.validate()
189
190     def _validate_requirements(self):
191         type_requires = self.type_definition.get_all_requirements()
192         allowed_reqs = ["template"]
193         if type_requires:
194             for treq in type_requires:
195                 for key, value in treq.items():
196                     allowed_reqs.append(key)
197                     if isinstance(value, dict):
198                         for key in value:
199                             allowed_reqs.append(key)
200
201         requires = self.type_definition.get_value(self.REQUIREMENTS,
202                                                   self.entity_tpl)
203         if requires:
204             if not isinstance(requires, list):
205                 ExceptionCollector.appendException(
206                     TypeMismatchError(
207                         what='"requirements" of template "%s"' % self.name,
208                         type='list'))
209             for req in requires:
210                 for r1, value in req.items():
211                     if isinstance(value, dict):
212                         self._validate_requirements_keys(value)
213                         self._validate_requirements_properties(value)
214                         allowed_reqs.append(r1)
215                 self._common_validate_field(req, allowed_reqs, 'requirements')
216
217     def _validate_requirements_properties(self, requirements):
218         # TODO(anyone): Only occurrences property of the requirements is
219         # validated here. Validation of other requirement properties are being
220         # validated in different files. Better to keep all the requirements
221         # properties validation here.
222         for key, value in requirements.items():
223             if key == 'occurrences':
224                 self._validate_occurrences(value)
225                 break
226
227     def _validate_occurrences(self, occurrences):
228         DataEntity.validate_datatype('list', occurrences)
229         for value in occurrences:
230             DataEntity.validate_datatype('integer', value)
231         if len(occurrences) != 2 or not (0 <= occurrences[0] <= occurrences[1]) \
232             or occurrences[1] == 0:
233             ExceptionCollector.appendException(
234                 InvalidPropertyValueError(what=(occurrences)))
235
236     def _validate_requirements_keys(self, requirement):
237         for key in requirement.keys():
238             if key not in self.REQUIREMENTS_SECTION:
239                 ExceptionCollector.appendException(
240                     UnknownFieldError(
241                         what='"requirements" of template "%s"' % self.name,
242                         field=key))
243
244     def _validate_interfaces(self):
245         ifaces = self.type_definition.get_value(self.INTERFACES,
246                                                 self.entity_tpl)
247         if ifaces:
248             for i in ifaces:
249                 for name, value in ifaces.items():
250                     if name in (LIFECYCLE, LIFECYCLE_SHORTNAME):
251                         self._common_validate_field(
252                             value, InterfacesDef.
253                             interfaces_node_lifecycle_operations,
254                             'interfaces')
255                     elif name in (CONFIGURE, CONFIGURE_SHORTNAME):
256                         self._common_validate_field(
257                             value, InterfacesDef.
258                             interfaces_relationship_configure_operations,
259                             'interfaces')
260                     else:
261                         ExceptionCollector.appendException(
262                             UnknownFieldError(
263                                 what='"interfaces" of template "%s"' %
264                                 self.name, field=name))
265
266     def _validate_fields(self, nodetemplate):
267         for name in nodetemplate.keys():
268             if name not in self.SECTIONS and name not in self.SPECIAL_SECTIONS:
269                 ExceptionCollector.appendException(
270                     UnknownFieldError(what='Node template "%s"' % self.name,
271                                       field=name))