blob: b30f716650f2720fb6e48bc5f11073be01d30af8 [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
Matteo Scandolod12be212017-07-07 10:44:34 -070017from toscaparser.tosca_template import ToscaTemplate, ValidationError
Matteo Scandolo9ce18252017-06-22 10:48:25 -070018from default import TOSCA_RECIPES_DIR
Matteo Scandolo485b7132017-06-30 11:46:47 -070019from grpc_client.resources import RESOURCES
20from grpc_client.models_accessor import GRPCModelsAccessor
Matteo Scandolo21dde412017-07-11 18:54:12 -070021from grpc._channel import _Rendezvous
22import json
Matteo Scandolo9ce18252017-06-22 10:48:25 -070023
24class TOSCA_Parser:
25
Matteo Scandolo485b7132017-06-30 11:46:47 -070026 def compute_dependencies(self, template, models_by_name):
27 """
28 NOTE this method is augmenting self.template, isn't there a more explicit way to achieve it?
29 """
30 for nodetemplate in template.nodetemplates:
Matteo Scandolo9ce18252017-06-22 10:48:25 -070031 nodetemplate.dependencies = []
32 nodetemplate.dependencies_names = []
33 for reqs in nodetemplate.requirements:
34 for (k,v) in reqs.items():
35 name = v["node"]
Matteo Scandolo485b7132017-06-30 11:46:47 -070036 if (name in models_by_name):
37 nodetemplate.dependencies.append(models_by_name[name])
Matteo Scandolo9ce18252017-06-22 10:48:25 -070038 nodetemplate.dependencies_names.append(name)
39
40 # go another level deep, as our requirements can have requirements...
Matteo Scandolodf2600b2017-07-05 17:01:29 -070041 # NOTE do we still need to go deep?
Matteo Scandolo9ce18252017-06-22 10:48:25 -070042 for sd_req in v.get("requirements",[]):
43 for (sd_req_k, sd_req_v) in sd_req.items():
44 name = sd_req_v["node"]
Matteo Scandolo485b7132017-06-30 11:46:47 -070045 if (name in models_by_name):
46 nodetemplate.dependencies.append(models_by_name[name])
Matteo Scandolo9ce18252017-06-22 10:48:25 -070047 nodetemplate.dependencies_names.append(name)
48
Matteo Scandolo485b7132017-06-30 11:46:47 -070049 @staticmethod
50 def topsort_dependencies(g):
Matteo Scandolo9ce18252017-06-22 10:48:25 -070051
52 # Get set of all nodes, including those without outgoing edges
53 keys = set(g.keys())
54 values = set({})
55 for v in g.values():
56 values = values | set(v.dependencies_names)
57
58 all_nodes = list(keys | values)
59 steps = all_nodes
60
61
62 # Final order
63 order = []
64
65 # DFS stack, not using recursion
66 stack = []
67
68 # Unmarked set
69 unmarked = all_nodes
70
71 # visiting = [] - skip, don't expect 1000s of nodes, |E|/|V| is small
72
73 while unmarked:
74 stack.insert(0, unmarked[0]) # push first unmarked
75
76 while (stack):
77 n = stack[0]
78 add = True
79 try:
80 for m in g[n].dependencies_names:
81 if (m in unmarked):
82 add = False
83 stack.insert(0, m)
84 except KeyError:
85 pass
86 if (add):
87 if (n in steps and n not in order):
88 order.append(n)
89 item = stack.pop(0)
90 try:
91 unmarked.remove(item)
92 except ValueError:
93 pass
94
95 noorder = list(set(steps) - set(order))
96 return order + noorder
97
Matteo Scandolo9ce18252017-06-22 10:48:25 -070098 @staticmethod
99 def populate_model(model, data):
100 for k,v in data.iteritems():
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700101 # NOTE must-exists is a TOSCA implementation choice, remove it before saving the model
102 if k != "must-exist":
103 setattr(model, k, v)
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700104 return model
105
106 @staticmethod
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700107 def _translate_exception(msg):
108 readable = []
109 for line in msg.splitlines():
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700110 if line.strip().startswith('MissingRequiredFieldError') or \
111 line.strip().startswith('UnknownFieldError') or \
112 line.strip().startswith('ImportError') or \
113 line.strip().startswith('InvalidTypeError') or \
114 line.strip().startswith('TypeMismatchError'):
Matteo Scandolod12be212017-07-07 10:44:34 -0700115 readable.append(line)
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700116
117 if len(readable) > 0:
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700118 return '\n'.join(readable) + '\n'
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700119 else:
120 return msg
121
Matteo Scandolo485b7132017-06-30 11:46:47 -0700122 def save_recipe_to_tmp_file(self, recipe):
123 tmp_file = open(self.recipe_file, 'w')
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700124 tmp_file.write(recipe)
125 tmp_file.close()
126
Matteo Scandolo485b7132017-06-30 11:46:47 -0700127 @staticmethod
128 def get_tosca_models_by_name(template):
129 models_by_name = {}
130 for node in template.nodetemplates:
131 models_by_name[node.name] = node
132 return models_by_name
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700133
Matteo Scandolo485b7132017-06-30 11:46:47 -0700134 @staticmethod
135 def get_ordered_models_template(ordered_models_name, templates_by_model_name):
136 ordered_models_templates = []
137 for name in ordered_models_name:
138 if name in templates_by_model_name:
139 ordered_models_templates.append(templates_by_model_name[name])
140 return ordered_models_templates
141
Matteo Scandolo8bbb03a2017-07-05 14:03:33 -0700142 @staticmethod
143 def populate_dependencies(model, requirements, saved_models):
144 for dep in requirements:
145 class_name = dep.keys()[0]
146 related_model = saved_models[dep[class_name]['node']]
147 setattr(model, "%s_id" % class_name, related_model.id)
148 return model
Matteo Scandolo485b7132017-06-30 11:46:47 -0700149
Matteo Scandolo1bd10762017-10-18 09:53:14 +0200150 @staticmethod
151 def add_dependencies(data, requirements, saved_models):
152 for dep in requirements:
153 class_name = dep.keys()[0]
154 related_model = saved_models[dep[class_name]['node']]
155 data["%s_id" % class_name] = related_model.id
156 return data
157
Matteo Scandolo78ca3eb2017-07-13 16:58:22 -0700158 def __init__(self, recipe, username, password, **kwargs):
159
160 self.delete = False
161 if 'delete' in kwargs:
162 self.delete = True
Matteo Scandolo21dde412017-07-11 18:54:12 -0700163
164 # store username/password combination to read resources
165 self.username = username
166 self.password = password
Matteo Scandolo485b7132017-06-30 11:46:47 -0700167
168 # the template returned by TOSCA-Parser
169 self.template = None
170 # dictionary containing the models in the recipe and their template
171 self.templates_by_model_name = None
172 # list of models ordered by requirements
Matteo Scandolodf2600b2017-07-05 17:01:29 -0700173 self.ordered_models_name = []
Matteo Scandolo8bbb03a2017-07-05 14:03:33 -0700174 # dictionary containing the saved model
175 self.saved_model_by_name = {}
Matteo Scandolo485b7132017-06-30 11:46:47 -0700176
177 self.ordered_models_template = []
178 self.recipe_file = TOSCA_RECIPES_DIR + '/tmp.yaml'
179
Matteo Scandolodf2600b2017-07-05 17:01:29 -0700180 self.recipe = recipe
181
182 def execute(self):
Matteo Scandolo78ca3eb2017-07-13 16:58:22 -0700183
Matteo Scandolo485b7132017-06-30 11:46:47 -0700184 try:
185 # [] save the recipe to a tmp file
Matteo Scandolodf2600b2017-07-05 17:01:29 -0700186 self.save_recipe_to_tmp_file(self.recipe)
Matteo Scandolo485b7132017-06-30 11:46:47 -0700187 # [] parse the recipe with TOSCA Parse
188 self.template = ToscaTemplate(self.recipe_file)
189 # [] get all models in the recipe
190 self.templates_by_model_name = self.get_tosca_models_by_name(self.template)
191 # [] compute requirements
192 self.compute_dependencies(self.template, self.templates_by_model_name)
193 # [] topsort requirements
194 self.ordered_models_name = self.topsort_dependencies(self.templates_by_model_name)
195 # [] topsort templates
196 self.ordered_models_template = self.get_ordered_models_template(self.ordered_models_name, self.templates_by_model_name)
197
198 for recipe in self.ordered_models_template:
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700199 try:
200 # get properties from tosca
201 if not 'properties' in recipe.templates[recipe.name]:
202 data = {}
203 else:
204 data = recipe.templates[recipe.name]['properties']
205 if data == None:
206 data = {}
207 # [] get model by class name
208 class_name = recipe.type.replace("tosca.nodes.", "")
Matteo Scandolo1bd10762017-10-18 09:53:14 +0200209
210 # augemnt data with relations
211 data = self.add_dependencies(data, recipe.requirements, self.saved_model_by_name)
212
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700213 model = GRPCModelsAccessor.get_model_from_classname(class_name, data, self.username, self.password)
214 # [] populate model with data
215 model = self.populate_model(model, data)
216 # [] check if the model has requirements
217 # [] if it has populate them
218 model = self.populate_dependencies(model, recipe.requirements, self.saved_model_by_name)
219 # [] save, update or delete
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700220
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700221 if self.delete and not model.is_new:
222 model.delete()
223 elif not self.delete:
224 model.save()
225
226 self.saved_model_by_name[recipe.name] = model
227 except Exception, e:
228 print "[XOS-TOSCA] Failed to save model: %s [%s]" % (class_name, recipe.name)
229 raise e
Matteo Scandolo8bbb03a2017-07-05 14:03:33 -0700230
Matteo Scandolod12be212017-07-07 10:44:34 -0700231 except ValidationError as e:
Matteo Scandolo485b7132017-06-30 11:46:47 -0700232 if e.message:
Matteo Scandolod12be212017-07-07 10:44:34 -0700233 exception_msg = TOSCA_Parser._translate_exception(e.message)
Matteo Scandolo485b7132017-06-30 11:46:47 -0700234 else:
Matteo Scandolod12be212017-07-07 10:44:34 -0700235 exception_msg = TOSCA_Parser._translate_exception(str(e))
Matteo Scandolo485b7132017-06-30 11:46:47 -0700236 raise Exception(exception_msg)
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700237
Matteo Scandolo21dde412017-07-11 18:54:12 -0700238 except _Rendezvous, e:
239 try:
240 exception_msg = json.loads(e._state.details)["error"]
241 except Exception:
242 exception_msg = e._state.details
243 raise Exception(exception_msg)
244 except Exception, e:
Matteo Scandolo1fedfae2017-10-09 13:57:00 -0700245 raise Exception(e)
Matteo Scandolo21dde412017-07-11 18:54:12 -0700246
Matteo Scandolo9ce18252017-06-22 10:48:25 -0700247