blob: 3f44c502aa80a6264251a17f79c1c9e3404b1eee [file] [log] [blame]
Matteo Scandolo920e8fd2017-08-08 13:05:24 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16
Test Userfb023562018-10-18 10:56:45 -070017import os
18from tempfile import NamedTemporaryFile
Matteo Scandolo5a07a2c2018-06-05 18:04:00 -070019from xosconfig import Config
20from multistructlog import create_logger
21log = create_logger(Config().get('logging'))
22
Matteo Scandolod12be212017-07-07 10:44:34 -070023from toscaparser.tosca_template import ToscaTemplate, ValidationError
Matteo Scandolo9ce18252017-06-22 10:48:25 -070024from default import TOSCA_RECIPES_DIR
Matteo Scandolo485b7132017-06-30 11:46:47 -070025from grpc_client.resources import RESOURCES
26from grpc_client.models_accessor import GRPCModelsAccessor
Matteo Scandolo21dde412017-07-11 18:54:12 -070027from grpc._channel import _Rendezvous
28import json
Matteo Scandolo5ccdb132018-01-17 14:02:12 -080029import traceback
Matteo Scandolo9ce18252017-06-22 10:48:25 -070030
31class TOSCA_Parser:
32
Matteo Scandolo485b7132017-06-30 11:46:47 -070033 def compute_dependencies(self, template, models_by_name):
34 """
35 NOTE this method is augmenting self.template, isn't there a more explicit way to achieve it?
36 """
37 for nodetemplate in template.nodetemplates:
Matteo Scandolo9ce18252017-06-22 10:48:25 -070038 nodetemplate.dependencies = []
39 nodetemplate.dependencies_names = []
40 for reqs in nodetemplate.requirements:
41 for (k,v) in reqs.items():
42 name = v["node"]
Matteo Scandolo485b7132017-06-30 11:46:47 -070043 if (name in models_by_name):
44 nodetemplate.dependencies.append(models_by_name[name])
Matteo Scandolo9ce18252017-06-22 10:48:25 -070045 nodetemplate.dependencies_names.append(name)
46
47 # go another level deep, as our requirements can have requirements...
Matteo Scandolodf2600b2017-07-05 17:01:29 -070048 # NOTE do we still need to go deep?
Matteo Scandolo9ce18252017-06-22 10:48:25 -070049 for sd_req in v.get("requirements",[]):
50 for (sd_req_k, sd_req_v) in sd_req.items():
51 name = sd_req_v["node"]
Matteo Scandolo485b7132017-06-30 11:46:47 -070052 if (name in models_by_name):
53 nodetemplate.dependencies.append(models_by_name[name])
Matteo Scandolo9ce18252017-06-22 10:48:25 -070054 nodetemplate.dependencies_names.append(name)
55
Matteo Scandolo485b7132017-06-30 11:46:47 -070056 @staticmethod
57 def topsort_dependencies(g):
Matteo Scandolo9ce18252017-06-22 10:48:25 -070058
59 # Get set of all nodes, including those without outgoing edges
60 keys = set(g.keys())
61 values = set({})
62 for v in g.values():
63 values = values | set(v.dependencies_names)
64
65 all_nodes = list(keys | values)
66 steps = all_nodes
67
68
69 # Final order
70 order = []
71
72 # DFS stack, not using recursion
73 stack = []
74
75 # Unmarked set
76 unmarked = all_nodes
77
78 # visiting = [] - skip, don't expect 1000s of nodes, |E|/|V| is small
79
80 while unmarked:
81 stack.insert(0, unmarked[0]) # push first unmarked
82
83 while (stack):
84 n = stack[0]
85 add = True
86 try:
87 for m in g[n].dependencies_names:
88 if (m in unmarked):
89 add = False
90 stack.insert(0, m)
91 except KeyError:
92 pass
93 if (add):
94 if (n in steps and n not in order):
95 order.append(n)
96 item = stack.pop(0)
97 try:
98 unmarked.remove(item)
99 except ValueError:
100 pass
101
102 noorder = list(set(steps) - set(order))
103 return order + noorder
104
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700105 @staticmethod
106 def populate_model(model, data):
107 for k,v in data.iteritems():
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700108 # NOTE must-exists is a TOSCA implementation choice, remove it before saving the model
109 if k != "must-exist":
Matteo Scandolo5ccdb132018-01-17 14:02:12 -0800110 try:
111 setattr(model, k, v)
112 except TypeError, e:
113 raise Exception('Failed to set %s on field %s for class %s, Exception was: "%s"' % (v, k, model.model_name, e))
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700114 return model
115
116 @staticmethod
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700117 def _translate_exception(msg):
118 readable = []
119 for line in msg.splitlines():
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700120 if line.strip().startswith('MissingRequiredFieldError') or \
121 line.strip().startswith('UnknownFieldError') or \
122 line.strip().startswith('ImportError') or \
123 line.strip().startswith('InvalidTypeError') or \
124 line.strip().startswith('TypeMismatchError'):
Matteo Scandolod12be212017-07-07 10:44:34 -0700125 readable.append(line)
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700126
127 if len(readable) > 0:
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700128 return '\n'.join(readable) + '\n'
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700129 else:
130 return msg
131
Matteo Scandolo485b7132017-06-30 11:46:47 -0700132 @staticmethod
133 def get_tosca_models_by_name(template):
134 models_by_name = {}
135 for node in template.nodetemplates:
136 models_by_name[node.name] = node
137 return models_by_name
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700138
Matteo Scandolo485b7132017-06-30 11:46:47 -0700139 @staticmethod
140 def get_ordered_models_template(ordered_models_name, templates_by_model_name):
141 ordered_models_templates = []
142 for name in ordered_models_name:
143 if name in templates_by_model_name:
144 ordered_models_templates.append(templates_by_model_name[name])
145 return ordered_models_templates
146
Matteo Scandolo8bbb03a2017-07-05 14:03:33 -0700147 @staticmethod
148 def populate_dependencies(model, requirements, saved_models):
149 for dep in requirements:
150 class_name = dep.keys()[0]
151 related_model = saved_models[dep[class_name]['node']]
152 setattr(model, "%s_id" % class_name, related_model.id)
153 return model
Matteo Scandolo485b7132017-06-30 11:46:47 -0700154
Matteo Scandolo1bd10762017-10-18 09:53:14 +0200155 @staticmethod
156 def add_dependencies(data, requirements, saved_models):
157 for dep in requirements:
158 class_name = dep.keys()[0]
159 related_model = saved_models[dep[class_name]['node']]
160 data["%s_id" % class_name] = related_model.id
161 return data
162
Matteo Scandolo78ca3eb2017-07-13 16:58:22 -0700163 def __init__(self, recipe, username, password, **kwargs):
164
165 self.delete = False
166 if 'delete' in kwargs:
167 self.delete = True
Matteo Scandolo21dde412017-07-11 18:54:12 -0700168
169 # store username/password combination to read resources
170 self.username = username
171 self.password = password
Matteo Scandolo485b7132017-06-30 11:46:47 -0700172
173 # the template returned by TOSCA-Parser
174 self.template = None
175 # dictionary containing the models in the recipe and their template
176 self.templates_by_model_name = None
177 # list of models ordered by requirements
Matteo Scandolodf2600b2017-07-05 17:01:29 -0700178 self.ordered_models_name = []
Matteo Scandolo8bbb03a2017-07-05 14:03:33 -0700179 # dictionary containing the saved model
180 self.saved_model_by_name = {}
Matteo Scandolo485b7132017-06-30 11:46:47 -0700181
182 self.ordered_models_template = []
Matteo Scandolo485b7132017-06-30 11:46:47 -0700183
Matteo Scandolodf2600b2017-07-05 17:01:29 -0700184 self.recipe = recipe
185
186 def execute(self):
Matteo Scandolo78ca3eb2017-07-13 16:58:22 -0700187
Matteo Scandolo485b7132017-06-30 11:46:47 -0700188 try:
189 # [] save the recipe to a tmp file
Test Userfb023562018-10-18 10:56:45 -0700190 with NamedTemporaryFile(delete=False, suffix=".yaml", dir=TOSCA_RECIPES_DIR) as recipe_file:
191 try:
192 recipe_file.write(self.recipe)
193 recipe_file.close()
194
195 # [] parse the recipe with TOSCA Parse
196 self.template = ToscaTemplate(recipe_file.name)
197 finally:
198 # [] Make sure the temporary file is cleaned up
199 os.remove(recipe_file.name)
200
Matteo Scandolo485b7132017-06-30 11:46:47 -0700201 # [] get all models in the recipe
202 self.templates_by_model_name = self.get_tosca_models_by_name(self.template)
203 # [] compute requirements
204 self.compute_dependencies(self.template, self.templates_by_model_name)
205 # [] topsort requirements
206 self.ordered_models_name = self.topsort_dependencies(self.templates_by_model_name)
207 # [] topsort templates
208 self.ordered_models_template = self.get_ordered_models_template(self.ordered_models_name, self.templates_by_model_name)
209
210 for recipe in self.ordered_models_template:
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700211 try:
212 # get properties from tosca
213 if not 'properties' in recipe.templates[recipe.name]:
214 data = {}
215 else:
216 data = recipe.templates[recipe.name]['properties']
217 if data == None:
218 data = {}
219 # [] get model by class name
220 class_name = recipe.type.replace("tosca.nodes.", "")
Matteo Scandolo1bd10762017-10-18 09:53:14 +0200221
222 # augemnt data with relations
223 data = self.add_dependencies(data, recipe.requirements, self.saved_model_by_name)
224
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700225 model = GRPCModelsAccessor.get_model_from_classname(class_name, data, self.username, self.password)
226 # [] populate model with data
227 model = self.populate_model(model, data)
228 # [] check if the model has requirements
229 # [] if it has populate them
230 model = self.populate_dependencies(model, recipe.requirements, self.saved_model_by_name)
231 # [] save, update or delete
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700232
Matteo Scandolo91d7aeb2018-02-09 14:54:09 -0800233 reference_only = False
234 if 'must-exist' in data:
235 reference_only = True
236
237 if self.delete and not model.is_new and not reference_only:
Matteo Scandolo5a07a2c2018-06-05 18:04:00 -0700238 log.info("[XOS-Tosca] Deleting model %s[%s]" % (class_name, model.id))
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700239 model.delete()
240 elif not self.delete:
Matteo Scandolo5a07a2c2018-06-05 18:04:00 -0700241 log.info("[XOS-Tosca] Saving model %s[%s]" % (class_name, model.id))
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700242 model.save()
243
Matteo Scandoloc4680752018-05-17 12:16:30 -0700244
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700245 self.saved_model_by_name[recipe.name] = model
246 except Exception, e:
Matteo Scandolo5a07a2c2018-06-05 18:04:00 -0700247 log.exception("[XOS-TOSCA] Failed to save model: %s [%s]" % (class_name, recipe.name))
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700248 raise e
Matteo Scandolo8bbb03a2017-07-05 14:03:33 -0700249
Matteo Scandolod12be212017-07-07 10:44:34 -0700250 except ValidationError as e:
Matteo Scandolo485b7132017-06-30 11:46:47 -0700251 if e.message:
Matteo Scandolod12be212017-07-07 10:44:34 -0700252 exception_msg = TOSCA_Parser._translate_exception(e.message)
Matteo Scandolo485b7132017-06-30 11:46:47 -0700253 else:
Matteo Scandolod12be212017-07-07 10:44:34 -0700254 exception_msg = TOSCA_Parser._translate_exception(str(e))
Matteo Scandolo485b7132017-06-30 11:46:47 -0700255 raise Exception(exception_msg)
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700256
Matteo Scandolo21dde412017-07-11 18:54:12 -0700257 except _Rendezvous, e:
258 try:
Matteo Scandoloc4680752018-05-17 12:16:30 -0700259 details = json.loads(e._state.details)
260 exception_msg = details["error"]
261 if "specific_error" in details:
262 exception_msg = "%s: %s" % (exception_msg, details["specific_error"])
Matteo Scandolo21dde412017-07-11 18:54:12 -0700263 except Exception:
264 exception_msg = e._state.details
265 raise Exception(exception_msg)
266 except Exception, e:
Matteo Scandolo5a07a2c2018-06-05 18:04:00 -0700267 log.exception(e)
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700268 raise Exception(e)
Matteo Scandolo21dde412017-07-11 18:54:12 -0700269
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700270