blob: f388ad186b03b481c4b161dae9c7023ff1fccbe4 [file] [log] [blame]
tiernob24258a2018-10-04 18:39:49 +02001# -*- coding: utf-8 -*-
2
tiernod125caf2018-11-22 16:05:54 +00003# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
tiernob24258a2018-10-04 18:39:49 +020016import tarfile
17import yaml
18import json
19# import logging
20from hashlib import md5
21from osm_common.dbbase import DbException, deep_update_rfc7396
22from http import HTTPStatus
delacruzramo26301bb2019-11-15 14:45:32 +010023from time import time
tierno23acf402019-08-28 13:36:34 +000024from osm_nbi.validation import ValidationError, pdu_new_schema, pdu_edit_schema
25from osm_nbi.base_topic import BaseTopic, EngineException, get_iterable
gcalvino46e4cb82018-10-26 13:10:22 +020026from osm_im.vnfd import vnfd as vnfd_im
27from osm_im.nsd import nsd as nsd_im
gcalvino70434c12018-11-27 15:17:04 +010028from osm_im.nst import nst as nst_im
gcalvino46e4cb82018-10-26 13:10:22 +020029from pyangbind.lib.serialise import pybindJSONDecoder
30import pyangbind.lib.pybindJSON as pybindJSON
tiernob24258a2018-10-04 18:39:49 +020031
32__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
33
34
35class DescriptorTopic(BaseTopic):
36
delacruzramo32bab472019-09-13 12:24:22 +020037 def __init__(self, db, fs, msg, auth):
38 BaseTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +020039
tierno65ca36d2019-02-12 19:27:52 +010040 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
41 super().check_conflict_on_edit(session, final_content, edit_content, _id)
K Sai Kiran45bd94c2019-11-25 17:30:37 +053042
43 def _check_unique_id_name(descriptor, position=""):
44 for desc_key, desc_item in descriptor.items():
45 if isinstance(desc_item, list) and desc_item:
46 used_ids = []
47 desc_item_id = None
48 for index, list_item in enumerate(desc_item):
49 if isinstance(list_item, dict):
50 _check_unique_id_name(list_item, "{}.{}[{}]"
51 .format(position, desc_key, index))
52 # Base case
53 if index == 0 and (list_item.get("id") or list_item.get("name")):
54 desc_item_id = "id" if list_item.get("id") else "name"
55 if desc_item_id and list_item.get(desc_item_id):
56 if list_item[desc_item_id] in used_ids:
57 position = "{}.{}[{}]".format(position, desc_key, index)
58 raise EngineException("Error: identifier {} '{}' is not unique and repeats at '{}'"
59 .format(desc_item_id, list_item[desc_item_id],
60 position), HTTPStatus.UNPROCESSABLE_ENTITY)
61 used_ids.append(list_item[desc_item_id])
62 _check_unique_id_name(final_content)
tiernoaa1ca7b2018-11-08 19:00:20 +010063 # 1. validate again with pyangbind
64 # 1.1. remove internal keys
65 internal_keys = {}
66 for k in ("_id", "_admin"):
67 if k in final_content:
68 internal_keys[k] = final_content.pop(k)
gcalvinoa6fe0002019-01-09 13:27:11 +010069 storage_params = internal_keys["_admin"].get("storage")
tierno65ca36d2019-02-12 19:27:52 +010070 serialized = self._validate_input_new(final_content, storage_params, session["force"])
tiernoaa1ca7b2018-11-08 19:00:20 +010071 # 1.2. modify final_content with a serialized version
72 final_content.clear()
73 final_content.update(serialized)
74 # 1.3. restore internal keys
75 for k, v in internal_keys.items():
76 final_content[k] = v
tiernob24258a2018-10-04 18:39:49 +020077
tierno65ca36d2019-02-12 19:27:52 +010078 if session["force"]:
tierno5a5c2182018-11-20 12:27:42 +000079 return
tiernoaa1ca7b2018-11-08 19:00:20 +010080 # 2. check that this id is not present
81 if "id" in edit_content:
tierno65ca36d2019-02-12 19:27:52 +010082 _filter = self._get_project_filter(session)
tiernoaa1ca7b2018-11-08 19:00:20 +010083 _filter["id"] = final_content["id"]
84 _filter["_id.neq"] = _id
85 if self.db.get_one(self.topic, _filter, fail_on_empty=False):
86 raise EngineException("{} with id '{}' already exists for this project".format(self.topic[:-1],
87 final_content["id"]),
88 HTTPStatus.CONFLICT)
tiernob24258a2018-10-04 18:39:49 +020089
90 @staticmethod
91 def format_on_new(content, project_id=None, make_public=False):
92 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
93 content["_admin"]["onboardingState"] = "CREATED"
94 content["_admin"]["operationalState"] = "DISABLED"
tierno36ec8602018-11-02 17:27:11 +010095 content["_admin"]["usageState"] = "NOT_IN_USE"
tiernob24258a2018-10-04 18:39:49 +020096
tiernob4844ab2019-05-23 08:42:12 +000097 def delete_extra(self, session, _id, db_content):
98 """
99 Deletes file system storage associated with the descriptor
100 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
101 :param _id: server internal id
102 :param db_content: The database content of the descriptor
103 :return: None if ok or raises EngineException with the problem
104 """
tiernob24258a2018-10-04 18:39:49 +0200105 self.fs.file_delete(_id, ignore_non_exist=True)
tiernof717cbe2018-12-03 16:35:42 +0000106 self.fs.file_delete(_id + "_", ignore_non_exist=True) # remove temp folder
tiernob24258a2018-10-04 18:39:49 +0200107
108 @staticmethod
109 def get_one_by_id(db, session, topic, id):
110 # find owned by this project
tierno65ca36d2019-02-12 19:27:52 +0100111 _filter = BaseTopic._get_project_filter(session)
tiernob24258a2018-10-04 18:39:49 +0200112 _filter["id"] = id
113 desc_list = db.get_list(topic, _filter)
114 if len(desc_list) == 1:
115 return desc_list[0]
116 elif len(desc_list) > 1:
117 raise DbException("Found more than one {} with id='{}' belonging to this project".format(topic[:-1], id),
118 HTTPStatus.CONFLICT)
119
120 # not found any: try to find public
tierno65ca36d2019-02-12 19:27:52 +0100121 _filter = BaseTopic._get_project_filter(session)
tiernob24258a2018-10-04 18:39:49 +0200122 _filter["id"] = id
123 desc_list = db.get_list(topic, _filter)
124 if not desc_list:
125 raise DbException("Not found any {} with id='{}'".format(topic[:-1], id), HTTPStatus.NOT_FOUND)
126 elif len(desc_list) == 1:
127 return desc_list[0]
128 else:
129 raise DbException("Found more than one public {} with id='{}'; and no one belonging to this project".format(
130 topic[:-1], id), HTTPStatus.CONFLICT)
131
tierno65ca36d2019-02-12 19:27:52 +0100132 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
tiernob24258a2018-10-04 18:39:49 +0200133 """
134 Creates a new almost empty DISABLED entry into database. Due to SOL005, it does not follow normal procedure.
135 Creating a VNFD or NSD is done in two steps: 1. Creates an empty descriptor (this step) and 2) upload content
136 (self.upload_content)
137 :param rollback: list to append created items at database in case a rollback may to be done
tierno65ca36d2019-02-12 19:27:52 +0100138 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200139 :param indata: data to be inserted
140 :param kwargs: used to override the indata descriptor
141 :param headers: http request headers
tiernobdebce92019-07-01 15:36:49 +0000142 :return: _id, None: identity of the inserted data; and None as there is not any operation
tiernob24258a2018-10-04 18:39:49 +0200143 """
144
145 try:
delacruzramo32bab472019-09-13 12:24:22 +0200146 # Check Quota
147 self.check_quota(session)
148
tiernob24258a2018-10-04 18:39:49 +0200149 # _remove_envelop
150 if indata:
151 if "userDefinedData" in indata:
152 indata = indata['userDefinedData']
153
154 # Override descriptor with query string kwargs
155 self._update_input_with_kwargs(indata, kwargs)
156 # uncomment when this method is implemented.
157 # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors
tierno65ca36d2019-02-12 19:27:52 +0100158 # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"])
tiernob24258a2018-10-04 18:39:49 +0200159
160 content = {"_admin": {"userDefinedData": indata}}
tierno65ca36d2019-02-12 19:27:52 +0100161 self.format_on_new(content, session["project_id"], make_public=session["public"])
tiernob24258a2018-10-04 18:39:49 +0200162 _id = self.db.create(self.topic, content)
163 rollback.append({"topic": self.topic, "_id": _id})
K Sai Kiranc96fd692019-10-16 17:50:53 +0530164 self._send_msg("created", {"_id": _id})
tiernobdebce92019-07-01 15:36:49 +0000165 return _id, None
tiernob24258a2018-10-04 18:39:49 +0200166 except ValidationError as e:
167 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
168
tierno65ca36d2019-02-12 19:27:52 +0100169 def upload_content(self, session, _id, indata, kwargs, headers):
tiernob24258a2018-10-04 18:39:49 +0200170 """
171 Used for receiving content by chunks (with a transaction_id header and/or gzip file. It will store and extract)
tierno65ca36d2019-02-12 19:27:52 +0100172 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200173 :param _id : the nsd,vnfd is already created, this is the id
174 :param indata: http body request
175 :param kwargs: user query string to override parameters. NOT USED
176 :param headers: http request headers
tierno5a5c2182018-11-20 12:27:42 +0000177 :return: True if package is completely uploaded or False if partial content has been uploded
tiernob24258a2018-10-04 18:39:49 +0200178 Raise exception on error
179 """
180 # Check that _id exists and it is valid
181 current_desc = self.show(session, _id)
182
183 content_range_text = headers.get("Content-Range")
184 expected_md5 = headers.get("Content-File-MD5")
185 compressed = None
186 content_type = headers.get("Content-Type")
187 if content_type and "application/gzip" in content_type or "application/x-gzip" in content_type or \
188 "application/zip" in content_type:
189 compressed = "gzip"
190 filename = headers.get("Content-Filename")
191 if not filename:
192 filename = "package.tar.gz" if compressed else "package"
193 # TODO change to Content-Disposition filename https://tools.ietf.org/html/rfc6266
194 file_pkg = None
195 error_text = ""
196 try:
197 if content_range_text:
198 content_range = content_range_text.replace("-", " ").replace("/", " ").split()
199 if content_range[0] != "bytes": # TODO check x<y not negative < total....
200 raise IndexError()
201 start = int(content_range[1])
202 end = int(content_range[2]) + 1
203 total = int(content_range[3])
204 else:
205 start = 0
tiernof717cbe2018-12-03 16:35:42 +0000206 temp_folder = _id + "_" # all the content is upload here and if ok, it is rename from id_ to is folder
tiernob24258a2018-10-04 18:39:49 +0200207
208 if start:
tiernof717cbe2018-12-03 16:35:42 +0000209 if not self.fs.file_exists(temp_folder, 'dir'):
tiernob24258a2018-10-04 18:39:49 +0200210 raise EngineException("invalid Transaction-Id header", HTTPStatus.NOT_FOUND)
211 else:
tiernof717cbe2018-12-03 16:35:42 +0000212 self.fs.file_delete(temp_folder, ignore_non_exist=True)
213 self.fs.mkdir(temp_folder)
tiernob24258a2018-10-04 18:39:49 +0200214
215 storage = self.fs.get_params()
216 storage["folder"] = _id
217
tiernof717cbe2018-12-03 16:35:42 +0000218 file_path = (temp_folder, filename)
tiernob24258a2018-10-04 18:39:49 +0200219 if self.fs.file_exists(file_path, 'file'):
220 file_size = self.fs.file_size(file_path)
221 else:
222 file_size = 0
223 if file_size != start:
224 raise EngineException("invalid Content-Range start sequence, expected '{}' but received '{}'".format(
225 file_size, start), HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
226 file_pkg = self.fs.file_open(file_path, 'a+b')
227 if isinstance(indata, dict):
228 indata_text = yaml.safe_dump(indata, indent=4, default_flow_style=False)
229 file_pkg.write(indata_text.encode(encoding="utf-8"))
230 else:
231 indata_len = 0
232 while True:
233 indata_text = indata.read(4096)
234 indata_len += len(indata_text)
235 if not indata_text:
236 break
237 file_pkg.write(indata_text)
238 if content_range_text:
239 if indata_len != end-start:
240 raise EngineException("Mismatch between Content-Range header {}-{} and body length of {}".format(
241 start, end-1, indata_len), HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
242 if end != total:
243 # TODO update to UPLOADING
244 return False
245
246 # PACKAGE UPLOADED
247 if expected_md5:
248 file_pkg.seek(0, 0)
249 file_md5 = md5()
250 chunk_data = file_pkg.read(1024)
251 while chunk_data:
252 file_md5.update(chunk_data)
253 chunk_data = file_pkg.read(1024)
254 if expected_md5 != file_md5.hexdigest():
255 raise EngineException("Error, MD5 mismatch", HTTPStatus.CONFLICT)
256 file_pkg.seek(0, 0)
257 if compressed == "gzip":
258 tar = tarfile.open(mode='r', fileobj=file_pkg)
259 descriptor_file_name = None
260 for tarinfo in tar:
261 tarname = tarinfo.name
262 tarname_path = tarname.split("/")
263 if not tarname_path[0] or ".." in tarname_path: # if start with "/" means absolute path
264 raise EngineException("Absolute path or '..' are not allowed for package descriptor tar.gz")
265 if len(tarname_path) == 1 and not tarinfo.isdir():
266 raise EngineException("All files must be inside a dir for package descriptor tar.gz")
267 if tarname.endswith(".yaml") or tarname.endswith(".json") or tarname.endswith(".yml"):
268 storage["pkg-dir"] = tarname_path[0]
269 if len(tarname_path) == 2:
270 if descriptor_file_name:
271 raise EngineException(
272 "Found more than one descriptor file at package descriptor tar.gz")
273 descriptor_file_name = tarname
274 if not descriptor_file_name:
275 raise EngineException("Not found any descriptor file at package descriptor tar.gz")
276 storage["descriptor"] = descriptor_file_name
277 storage["zipfile"] = filename
tiernof717cbe2018-12-03 16:35:42 +0000278 self.fs.file_extract(tar, temp_folder)
279 with self.fs.file_open((temp_folder, descriptor_file_name), "r") as descriptor_file:
tiernob24258a2018-10-04 18:39:49 +0200280 content = descriptor_file.read()
281 else:
282 content = file_pkg.read()
283 storage["descriptor"] = descriptor_file_name = filename
284
285 if descriptor_file_name.endswith(".json"):
286 error_text = "Invalid json format "
287 indata = json.load(content)
288 else:
289 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200290 indata = yaml.load(content, Loader=yaml.SafeLoader)
tiernob24258a2018-10-04 18:39:49 +0200291
292 current_desc["_admin"]["storage"] = storage
293 current_desc["_admin"]["onboardingState"] = "ONBOARDED"
294 current_desc["_admin"]["operationalState"] = "ENABLED"
295
296 indata = self._remove_envelop(indata)
297
298 # Override descriptor with query string kwargs
299 if kwargs:
300 self._update_input_with_kwargs(indata, kwargs)
301 # it will call overrides method at VnfdTopic or NsdTopic
tierno65ca36d2019-02-12 19:27:52 +0100302 # indata = self._validate_input_edit(indata, force=session["force"])
tiernob24258a2018-10-04 18:39:49 +0200303
304 deep_update_rfc7396(current_desc, indata)
tierno65ca36d2019-02-12 19:27:52 +0100305 self.check_conflict_on_edit(session, current_desc, indata, _id=_id)
delacruzramo26301bb2019-11-15 14:45:32 +0100306 current_desc["_admin"]["modified"] = time()
tiernob24258a2018-10-04 18:39:49 +0200307 self.db.replace(self.topic, _id, current_desc)
tiernof717cbe2018-12-03 16:35:42 +0000308 self.fs.dir_rename(temp_folder, _id)
tiernob24258a2018-10-04 18:39:49 +0200309
310 indata["_id"] = _id
K Sai Kiranc96fd692019-10-16 17:50:53 +0530311 self._send_msg("edited", indata)
tiernob24258a2018-10-04 18:39:49 +0200312
313 # TODO if descriptor has changed because kwargs update content and remove cached zip
314 # TODO if zip is not present creates one
315 return True
316
317 except EngineException:
318 raise
319 except IndexError:
320 raise EngineException("invalid Content-Range header format. Expected 'bytes start-end/total'",
321 HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
322 except IOError as e:
323 raise EngineException("invalid upload transaction sequence: '{}'".format(e), HTTPStatus.BAD_REQUEST)
324 except tarfile.ReadError as e:
325 raise EngineException("invalid file content {}".format(e), HTTPStatus.BAD_REQUEST)
326 except (ValueError, yaml.YAMLError) as e:
327 raise EngineException(error_text + str(e))
328 except ValidationError as e:
329 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
330 finally:
331 if file_pkg:
332 file_pkg.close()
333
334 def get_file(self, session, _id, path=None, accept_header=None):
335 """
336 Return the file content of a vnfd or nsd
tierno65ca36d2019-02-12 19:27:52 +0100337 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tierno87006042018-10-24 12:50:20 +0200338 :param _id: Identity of the vnfd, nsd
tiernob24258a2018-10-04 18:39:49 +0200339 :param path: artifact path or "$DESCRIPTOR" or None
340 :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
tierno87006042018-10-24 12:50:20 +0200341 :return: opened file plus Accept format or raises an exception
tiernob24258a2018-10-04 18:39:49 +0200342 """
343 accept_text = accept_zip = False
344 if accept_header:
345 if 'text/plain' in accept_header or '*/*' in accept_header:
346 accept_text = True
347 if 'application/zip' in accept_header or '*/*' in accept_header:
tierno87006042018-10-24 12:50:20 +0200348 accept_zip = 'application/zip'
349 elif 'application/gzip' in accept_header:
350 accept_zip = 'application/gzip'
351
tiernob24258a2018-10-04 18:39:49 +0200352 if not accept_text and not accept_zip:
353 raise EngineException("provide request header 'Accept' with 'application/zip' or 'text/plain'",
354 http_code=HTTPStatus.NOT_ACCEPTABLE)
355
356 content = self.show(session, _id)
357 if content["_admin"]["onboardingState"] != "ONBOARDED":
358 raise EngineException("Cannot get content because this resource is not at 'ONBOARDED' state. "
359 "onboardingState is {}".format(content["_admin"]["onboardingState"]),
360 http_code=HTTPStatus.CONFLICT)
361 storage = content["_admin"]["storage"]
362 if path is not None and path != "$DESCRIPTOR": # artifacts
363 if not storage.get('pkg-dir'):
364 raise EngineException("Packages does not contains artifacts", http_code=HTTPStatus.BAD_REQUEST)
365 if self.fs.file_exists((storage['folder'], storage['pkg-dir'], *path), 'dir'):
366 folder_content = self.fs.dir_ls((storage['folder'], storage['pkg-dir'], *path))
367 return folder_content, "text/plain"
368 # TODO manage folders in http
369 else:
370 return self.fs.file_open((storage['folder'], storage['pkg-dir'], *path), "rb"),\
371 "application/octet-stream"
372
373 # pkgtype accept ZIP TEXT -> result
374 # manyfiles yes X -> zip
375 # no yes -> error
376 # onefile yes no -> zip
377 # X yes -> text
378
379 if accept_text and (not storage.get('pkg-dir') or path == "$DESCRIPTOR"):
380 return self.fs.file_open((storage['folder'], storage['descriptor']), "r"), "text/plain"
381 elif storage.get('pkg-dir') and not accept_zip:
382 raise EngineException("Packages that contains several files need to be retrieved with 'application/zip'"
383 "Accept header", http_code=HTTPStatus.NOT_ACCEPTABLE)
384 else:
385 if not storage.get('zipfile'):
386 # TODO generate zipfile if not present
387 raise EngineException("Only allowed 'text/plain' Accept header for this descriptor. To be solved in "
388 "future versions", http_code=HTTPStatus.NOT_ACCEPTABLE)
tierno87006042018-10-24 12:50:20 +0200389 return self.fs.file_open((storage['folder'], storage['zipfile']), "rb"), accept_zip
tiernob24258a2018-10-04 18:39:49 +0200390
gcalvino46e4cb82018-10-26 13:10:22 +0200391 def pyangbind_validation(self, item, data, force=False):
392 try:
393 if item == "vnfds":
394 myvnfd = vnfd_im()
395 pybindJSONDecoder.load_ietf_json({'vnfd:vnfd-catalog': {'vnfd': [data]}}, None, None, obj=myvnfd,
396 path_helper=True, skip_unknown=force)
397 out = pybindJSON.dumps(myvnfd, mode="ietf")
398 elif item == "nsds":
399 mynsd = nsd_im()
400 pybindJSONDecoder.load_ietf_json({'nsd:nsd-catalog': {'nsd': [data]}}, None, None, obj=mynsd,
401 path_helper=True, skip_unknown=force)
402 out = pybindJSON.dumps(mynsd, mode="ietf")
gcalvino70434c12018-11-27 15:17:04 +0100403 elif item == "nsts":
404 mynst = nst_im()
405 pybindJSONDecoder.load_ietf_json({'nst': [data]}, None, None, obj=mynst,
406 path_helper=True, skip_unknown=force)
407 out = pybindJSON.dumps(mynst, mode="ietf")
gcalvino46e4cb82018-10-26 13:10:22 +0200408 else:
409 raise EngineException("Not possible to validate '{}' item".format(item),
410 http_code=HTTPStatus.INTERNAL_SERVER_ERROR)
411
412 desc_out = self._remove_envelop(yaml.safe_load(out))
413 return desc_out
414
415 except Exception as e:
416 raise EngineException("Error in pyangbind validation: {}".format(str(e)),
417 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
418
tiernob24258a2018-10-04 18:39:49 +0200419
420class VnfdTopic(DescriptorTopic):
421 topic = "vnfds"
422 topic_msg = "vnfd"
423
delacruzramo32bab472019-09-13 12:24:22 +0200424 def __init__(self, db, fs, msg, auth):
425 DescriptorTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200426
427 @staticmethod
428 def _remove_envelop(indata=None):
429 if not indata:
430 return {}
431 clean_indata = indata
432 if clean_indata.get('vnfd:vnfd-catalog'):
433 clean_indata = clean_indata['vnfd:vnfd-catalog']
434 elif clean_indata.get('vnfd-catalog'):
435 clean_indata = clean_indata['vnfd-catalog']
436 if clean_indata.get('vnfd'):
437 if not isinstance(clean_indata['vnfd'], list) or len(clean_indata['vnfd']) != 1:
gcalvino46e4cb82018-10-26 13:10:22 +0200438 raise EngineException("'vnfd' must be a list of only one element")
tiernob24258a2018-10-04 18:39:49 +0200439 clean_indata = clean_indata['vnfd'][0]
gcalvino46e4cb82018-10-26 13:10:22 +0200440 elif clean_indata.get('vnfd:vnfd'):
441 if not isinstance(clean_indata['vnfd:vnfd'], list) or len(clean_indata['vnfd:vnfd']) != 1:
442 raise EngineException("'vnfd:vnfd' must be a list of only one element")
443 clean_indata = clean_indata['vnfd:vnfd'][0]
tiernob24258a2018-10-04 18:39:49 +0200444 return clean_indata
445
tierno65ca36d2019-02-12 19:27:52 +0100446 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
447 super().check_conflict_on_edit(session, final_content, edit_content, _id)
tierno36ec8602018-11-02 17:27:11 +0100448
449 # set type of vnfd
450 contains_pdu = False
451 contains_vdu = False
452 for vdu in get_iterable(final_content.get("vdu")):
453 if vdu.get("pdu-type"):
454 contains_pdu = True
455 else:
456 contains_vdu = True
457 if contains_pdu:
458 final_content["_admin"]["type"] = "hnfd" if contains_vdu else "pnfd"
459 elif contains_vdu:
460 final_content["_admin"]["type"] = "vnfd"
461 # if neither vud nor pdu do not fill type
462
tiernob4844ab2019-05-23 08:42:12 +0000463 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200464 """
465 Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
466 that VNFD can be public and be used by NSD of other projects. Also check there are not deployments, or vnfr
467 that uses this vnfd
tierno65ca36d2019-02-12 19:27:52 +0100468 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob4844ab2019-05-23 08:42:12 +0000469 :param _id: vnfd internal id
470 :param db_content: The database content of the _id.
tiernob24258a2018-10-04 18:39:49 +0200471 :return: None or raises EngineException with the conflict
472 """
tierno65ca36d2019-02-12 19:27:52 +0100473 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200474 return
tiernob4844ab2019-05-23 08:42:12 +0000475 descriptor = db_content
tiernob24258a2018-10-04 18:39:49 +0200476 descriptor_id = descriptor.get("id")
477 if not descriptor_id: # empty vnfd not uploaded
478 return
479
tierno65ca36d2019-02-12 19:27:52 +0100480 _filter = self._get_project_filter(session)
tiernob4844ab2019-05-23 08:42:12 +0000481
tiernob24258a2018-10-04 18:39:49 +0200482 # check vnfrs using this vnfd
483 _filter["vnfd-id"] = _id
484 if self.db.get_list("vnfrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000485 raise EngineException("There is at least one VNF using this descriptor", http_code=HTTPStatus.CONFLICT)
486
487 # check NSD referencing this VNFD
tiernob24258a2018-10-04 18:39:49 +0200488 del _filter["vnfd-id"]
tiernob24258a2018-10-04 18:39:49 +0200489 _filter["constituent-vnfd.ANYINDEX.vnfd-id-ref"] = descriptor_id
490 if self.db.get_list("nsds", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000491 raise EngineException("There is at least one NSD referencing this descriptor",
492 http_code=HTTPStatus.CONFLICT)
tiernob24258a2018-10-04 18:39:49 +0200493
gcalvinoa6fe0002019-01-09 13:27:11 +0100494 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino46e4cb82018-10-26 13:10:22 +0200495 indata = self.pyangbind_validation("vnfds", indata, force)
gcalvino5e72d152018-10-23 11:46:57 +0200496 # Cross references validation in the descriptor
gcalvinoe45aded2018-11-13 17:17:28 +0100497 if indata.get("vdu"):
498 if not indata.get("mgmt-interface"):
499 raise EngineException("'mgmt-interface' is a mandatory field and it is not defined",
tierno40fbcad2018-10-26 10:58:15 +0200500 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvinoe45aded2018-11-13 17:17:28 +0100501 if indata["mgmt-interface"].get("cp"):
502 for cp in get_iterable(indata.get("connection-point")):
503 if cp["name"] == indata["mgmt-interface"]["cp"]:
504 break
505 else:
506 raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point"
507 .format(indata["mgmt-interface"]["cp"]),
508 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvino5e72d152018-10-23 11:46:57 +0200509
510 for vdu in get_iterable(indata.get("vdu")):
511 for interface in get_iterable(vdu.get("interface")):
512 if interface.get("external-connection-point-ref"):
513 for cp in get_iterable(indata.get("connection-point")):
tierno40fbcad2018-10-26 10:58:15 +0200514 if cp["name"] == interface["external-connection-point-ref"]:
gcalvino5e72d152018-10-23 11:46:57 +0200515 break
516 else:
tierno40fbcad2018-10-26 10:58:15 +0200517 raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' "
gcalvino5e72d152018-10-23 11:46:57 +0200518 "must match an existing connection-point"
519 .format(vdu["id"], interface["name"],
520 interface["external-connection-point-ref"]),
521 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno40fbcad2018-10-26 10:58:15 +0200522
523 elif interface.get("internal-connection-point-ref"):
gcalvino5e72d152018-10-23 11:46:57 +0200524 for internal_cp in get_iterable(vdu.get("internal-connection-point")):
tierno40fbcad2018-10-26 10:58:15 +0200525 if interface["internal-connection-point-ref"] == internal_cp.get("id"):
gcalvino5e72d152018-10-23 11:46:57 +0200526 break
527 else:
tierno40fbcad2018-10-26 10:58:15 +0200528 raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' "
529 "must match an existing vdu:internal-connection-point"
530 .format(vdu["id"], interface["name"],
531 interface["internal-connection-point-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200532 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvinoa6fe0002019-01-09 13:27:11 +0100533 # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
534 if vdu.get("vdu-configuration"):
535 if vdu["vdu-configuration"].get("juju"):
536 if not self._validate_package_folders(storage_params, 'charms'):
537 raise EngineException("Charm defined in vnf[id={}]:vdu[id={}] but not present in "
538 "package".format(indata["id"], vdu["id"]))
539 # Validate that if descriptor contains cloud-init, artifacts _admin.storage."pkg-dir" is not none
540 if vdu.get("cloud-init-file"):
541 if not self._validate_package_folders(storage_params, 'cloud_init', vdu["cloud-init-file"]):
542 raise EngineException("Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in "
543 "package".format(indata["id"], vdu["id"]))
544 # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
545 if indata.get("vnf-configuration"):
546 if indata["vnf-configuration"].get("juju"):
547 if not self._validate_package_folders(storage_params, 'charms'):
548 raise EngineException("Charm defined in vnf[id={}] but not present in "
549 "package".format(indata["id"]))
delacruzramo5727a372019-03-28 12:29:04 +0100550 vld_names = [] # For detection of duplicated VLD names
gcalvino5e72d152018-10-23 11:46:57 +0200551 for ivld in get_iterable(indata.get("internal-vld")):
delacruzramo5727a372019-03-28 12:29:04 +0100552 # BEGIN Detection of duplicated VLD names
553 ivld_name = ivld["name"]
554 if ivld_name in vld_names:
555 raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]"
556 .format(ivld["name"], indata["id"], ivld["id"]),
557 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
558 else:
559 vld_names.append(ivld_name)
560 # END Detection of duplicated VLD names
gcalvino5e72d152018-10-23 11:46:57 +0200561 for icp in get_iterable(ivld.get("internal-connection-point")):
562 icp_mark = False
563 for vdu in get_iterable(indata.get("vdu")):
564 for internal_cp in get_iterable(vdu.get("internal-connection-point")):
565 if icp["id-ref"] == internal_cp["id"]:
566 icp_mark = True
567 break
568 if icp_mark:
569 break
570 else:
tierno40fbcad2018-10-26 10:58:15 +0200571 raise EngineException("internal-vld[id='{}']:internal-connection-point='{}' must match an existing "
572 "vdu:internal-connection-point".format(ivld["id"], icp["id-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200573 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
574 if ivld.get("ip-profile-ref"):
tierno40fbcad2018-10-26 10:58:15 +0200575 for ip_prof in get_iterable(indata.get("ip-profiles")):
gcalvino5e72d152018-10-23 11:46:57 +0200576 if ip_prof["name"] == get_iterable(ivld.get("ip-profile-ref")):
577 break
578 else:
tierno40fbcad2018-10-26 10:58:15 +0200579 raise EngineException("internal-vld[id='{}']:ip-profile-ref='{}' does not exist".format(
gcalvino5e72d152018-10-23 11:46:57 +0200580 ivld["id"], ivld["ip-profile-ref"]),
581 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
582 for mp in get_iterable(indata.get("monitoring-param")):
583 if mp.get("vdu-monitoring-param"):
584 mp_vmp_mark = False
585 for vdu in get_iterable(indata.get("vdu")):
586 for vmp in get_iterable(vdu.get("monitoring-param")):
tierno40fbcad2018-10-26 10:58:15 +0200587 if vmp["id"] == mp["vdu-monitoring-param"].get("vdu-monitoring-param-ref") and vdu["id"] ==\
gcalvino5e72d152018-10-23 11:46:57 +0200588 mp["vdu-monitoring-param"]["vdu-ref"]:
589 mp_vmp_mark = True
590 break
591 if mp_vmp_mark:
592 break
593 else:
594 raise EngineException("monitoring-param:vdu-monitoring-param:vdu-monitoring-param-ref='{}' not "
tierno40fbcad2018-10-26 10:58:15 +0200595 "defined at vdu[id='{}'] or vdu does not exist"
gcalvino5e72d152018-10-23 11:46:57 +0200596 .format(mp["vdu-monitoring-param"]["vdu-monitoring-param-ref"],
597 mp["vdu-monitoring-param"]["vdu-ref"]),
598 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
599 elif mp.get("vdu-metric"):
600 mp_vm_mark = False
601 for vdu in get_iterable(indata.get("vdu")):
602 if vdu.get("vdu-configuration"):
603 for metric in get_iterable(vdu["vdu-configuration"].get("metrics")):
604 if metric["name"] == mp["vdu-metric"]["vdu-metric-name-ref"] and vdu["id"] == \
605 mp["vdu-metric"]["vdu-ref"]:
606 mp_vm_mark = True
607 break
608 if mp_vm_mark:
609 break
610 else:
tierno40fbcad2018-10-26 10:58:15 +0200611 raise EngineException("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined at "
612 "vdu[id='{}'] or vdu does not exist"
gcalvino5e72d152018-10-23 11:46:57 +0200613 .format(mp["vdu-metric"]["vdu-metric-name-ref"],
614 mp["vdu-metric"]["vdu-ref"]),
615 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
616
617 for sgd in get_iterable(indata.get("scaling-group-descriptor")):
618 for sp in get_iterable(sgd.get("scaling-policy")):
619 for sc in get_iterable(sp.get("scaling-criteria")):
620 for mp in get_iterable(indata.get("monitoring-param")):
621 if mp["id"] == get_iterable(sc.get("vnf-monitoring-param-ref")):
622 break
623 else:
tierno40fbcad2018-10-26 10:58:15 +0200624 raise EngineException("scaling-group-descriptor[name='{}']:scaling-criteria[name='{}']:"
625 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param"
626 .format(sgd["name"], sc["name"], sc["vnf-monitoring-param-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200627 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
628 for sgd_vdu in get_iterable(sgd.get("vdu")):
629 sgd_vdu_mark = False
630 for vdu in get_iterable(indata.get("vdu")):
631 if vdu["id"] == sgd_vdu["vdu-id-ref"]:
632 sgd_vdu_mark = True
633 break
634 if sgd_vdu_mark:
635 break
636 else:
tierno40fbcad2018-10-26 10:58:15 +0200637 raise EngineException("scaling-group-descriptor[name='{}']:vdu-id-ref={} does not match any vdu"
638 .format(sgd["name"], sgd_vdu["vdu-id-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200639 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
640 for sca in get_iterable(sgd.get("scaling-config-action")):
tierno40fbcad2018-10-26 10:58:15 +0200641 if not indata.get("vnf-configuration"):
642 raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced by "
643 "scaling-group-descriptor[name='{}']:scaling-config-action"
644 .format(sgd["name"]),
gcalvino5e72d152018-10-23 11:46:57 +0200645 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno40fbcad2018-10-26 10:58:15 +0200646 for primitive in get_iterable(indata["vnf-configuration"].get("config-primitive")):
647 if primitive["name"] == sca["vnf-config-primitive-name-ref"]:
648 break
649 else:
650 raise EngineException("scaling-group-descriptor[name='{}']:scaling-config-action:vnf-config-"
651 "primitive-name-ref='{}' does not match any "
652 "vnf-configuration:config-primitive:name"
653 .format(sgd["name"], sca["vnf-config-primitive-name-ref"]),
654 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200655 return indata
656
657 def _validate_input_edit(self, indata, force=False):
tiernoaa1ca7b2018-11-08 19:00:20 +0100658 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
tiernob24258a2018-10-04 18:39:49 +0200659 return indata
660
gcalvinoa6fe0002019-01-09 13:27:11 +0100661 def _validate_package_folders(self, storage_params, folder, file=None):
662 if not storage_params or not storage_params.get("pkg-dir"):
663 return False
664 else:
665 if self.fs.file_exists("{}_".format(storage_params["folder"]), 'dir'):
666 f = "{}_/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder)
667 else:
668 f = "{}/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder)
669 if file:
670 return self.fs.file_exists("{}/{}".format(f, file), 'file')
671 else:
672 if self.fs.file_exists(f, 'dir'):
673 if self.fs.dir_ls(f):
674 return True
675 return False
676
tiernob24258a2018-10-04 18:39:49 +0200677
678class NsdTopic(DescriptorTopic):
679 topic = "nsds"
680 topic_msg = "nsd"
681
delacruzramo32bab472019-09-13 12:24:22 +0200682 def __init__(self, db, fs, msg, auth):
683 DescriptorTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200684
685 @staticmethod
686 def _remove_envelop(indata=None):
687 if not indata:
688 return {}
689 clean_indata = indata
690
691 if clean_indata.get('nsd:nsd-catalog'):
692 clean_indata = clean_indata['nsd:nsd-catalog']
693 elif clean_indata.get('nsd-catalog'):
694 clean_indata = clean_indata['nsd-catalog']
695 if clean_indata.get('nsd'):
696 if not isinstance(clean_indata['nsd'], list) or len(clean_indata['nsd']) != 1:
gcalvino46e4cb82018-10-26 13:10:22 +0200697 raise EngineException("'nsd' must be a list of only one element")
tiernob24258a2018-10-04 18:39:49 +0200698 clean_indata = clean_indata['nsd'][0]
gcalvino46e4cb82018-10-26 13:10:22 +0200699 elif clean_indata.get('nsd:nsd'):
700 if not isinstance(clean_indata['nsd:nsd'], list) or len(clean_indata['nsd:nsd']) != 1:
701 raise EngineException("'nsd:nsd' must be a list of only one element")
702 clean_indata = clean_indata['nsd:nsd'][0]
tiernob24258a2018-10-04 18:39:49 +0200703 return clean_indata
704
gcalvinoa6fe0002019-01-09 13:27:11 +0100705 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino46e4cb82018-10-26 13:10:22 +0200706 indata = self.pyangbind_validation("nsds", indata, force)
tierno5a5c2182018-11-20 12:27:42 +0000707 # Cross references validation in the descriptor
tiernoaa1ca7b2018-11-08 19:00:20 +0100708 # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
tierno5a5c2182018-11-20 12:27:42 +0000709 for vld in get_iterable(indata.get("vld")):
vijay.r8ba4e052019-03-29 17:52:26 +0530710 if vld.get("mgmt-network") and vld.get("ip-profile-ref"):
711 raise EngineException("Error at vld[id='{}']:ip-profile-ref"
712 " You cannot set an ip-profile when mgmt-network is True"
713 .format(vld["id"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno5a5c2182018-11-20 12:27:42 +0000714 for vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
715 for constituent_vnfd in get_iterable(indata.get("constituent-vnfd")):
716 if vnfd_cp["member-vnf-index-ref"] == constituent_vnfd["member-vnf-index"]:
717 if vnfd_cp.get("vnfd-id-ref") and vnfd_cp["vnfd-id-ref"] != constituent_vnfd["vnfd-id-ref"]:
718 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[vnfd-id-ref='{}'] "
719 "does not match constituent-vnfd[member-vnf-index='{}']:vnfd-id-ref"
720 " '{}'".format(vld["id"], vnfd_cp["vnfd-id-ref"],
721 constituent_vnfd["member-vnf-index"],
722 constituent_vnfd["vnfd-id-ref"]),
723 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
724 break
725 else:
726 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
727 "does not match any constituent-vnfd:member-vnf-index"
728 .format(vld["id"], vnfd_cp["member-vnf-index-ref"]),
729 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
delacruzramo6a92a532019-11-22 15:06:43 +0100730 # Check VNFFGD
731 for fgd in get_iterable(indata.get("vnffgd")):
732 for cls in get_iterable(fgd.get("classifier")):
733 rspref = cls.get("rsp-id-ref")
734 for rsp in get_iterable(fgd.get("rsp")):
735 rspid = rsp.get("id")
736 if rspid and rspref and rspid == rspref:
737 break
738 else:
739 raise EngineException(
740 "Error at vnffgd[id='{}']:classifier[id='{}']:rsp-id-ref '{}' does not match any rsp:id"
741 .format(fgd["id"], cls["id"], rspref),
742 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200743 return indata
744
745 def _validate_input_edit(self, indata, force=False):
tiernoaa1ca7b2018-11-08 19:00:20 +0100746 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
tiernob24258a2018-10-04 18:39:49 +0200747 return indata
748
tierno65ca36d2019-02-12 19:27:52 +0100749 def _check_descriptor_dependencies(self, session, descriptor):
tiernob24258a2018-10-04 18:39:49 +0200750 """
tierno5a5c2182018-11-20 12:27:42 +0000751 Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
752 connection points are ok
tierno65ca36d2019-02-12 19:27:52 +0100753 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200754 :param descriptor: descriptor to be inserted or edit
755 :return: None or raises exception
756 """
tierno65ca36d2019-02-12 19:27:52 +0100757 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200758 return
tierno5a5c2182018-11-20 12:27:42 +0000759 member_vnfd_index = {}
tierno65ca36d2019-02-12 19:27:52 +0100760 if descriptor.get("constituent-vnfd") and not session["force"]:
tierno5a5c2182018-11-20 12:27:42 +0000761 for vnf in descriptor["constituent-vnfd"]:
762 vnfd_id = vnf["vnfd-id-ref"]
tierno65ca36d2019-02-12 19:27:52 +0100763 filter_q = self._get_project_filter(session)
tierno5a5c2182018-11-20 12:27:42 +0000764 filter_q["id"] = vnfd_id
765 vnf_list = self.db.get_list("vnfds", filter_q)
766 if not vnf_list:
767 raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non "
768 "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT)
769 # elif len(vnf_list) > 1:
770 # raise EngineException("More than one vnfd found for id='{}'".format(vnfd_id),
771 # http_code=HTTPStatus.CONFLICT)
772 member_vnfd_index[vnf["member-vnf-index"]] = vnf_list[0]
773
774 # Cross references validation in the descriptor and vnfd connection point validation
775 for vld in get_iterable(descriptor.get("vld")):
776 for referenced_vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
777 # look if this vnfd contains this connection point
778 vnfd = member_vnfd_index.get(referenced_vnfd_cp["member-vnf-index-ref"])
tierno5a5c2182018-11-20 12:27:42 +0000779 for vnfd_cp in get_iterable(vnfd.get("connection-point")):
780 if referenced_vnfd_cp.get("vnfd-connection-point-ref") == vnfd_cp["name"]:
781 break
782 else:
783 raise EngineException(
784 "Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}']:vnfd-"
785 "connection-point-ref='{}' references a non existing conection-point:name inside vnfd '{}'"
786 .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"],
787 referenced_vnfd_cp["vnfd-connection-point-ref"], vnfd["id"]),
788 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200789
tierno65ca36d2019-02-12 19:27:52 +0100790 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
791 super().check_conflict_on_edit(session, final_content, edit_content, _id)
tiernob24258a2018-10-04 18:39:49 +0200792
tierno65ca36d2019-02-12 19:27:52 +0100793 self._check_descriptor_dependencies(session, final_content)
tiernob24258a2018-10-04 18:39:49 +0200794
tiernob4844ab2019-05-23 08:42:12 +0000795 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200796 """
797 Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note
798 that NSD can be public and be used by other projects.
tierno65ca36d2019-02-12 19:27:52 +0100799 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob4844ab2019-05-23 08:42:12 +0000800 :param _id: nsd internal id
801 :param db_content: The database content of the _id
tiernob24258a2018-10-04 18:39:49 +0200802 :return: None or raises EngineException with the conflict
803 """
tierno65ca36d2019-02-12 19:27:52 +0100804 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200805 return
tiernob4844ab2019-05-23 08:42:12 +0000806 descriptor = db_content
807 descriptor_id = descriptor.get("id")
808 if not descriptor_id: # empty nsd not uploaded
809 return
810
811 # check NSD used by NS
tierno65ca36d2019-02-12 19:27:52 +0100812 _filter = self._get_project_filter(session)
tiernob4844ab2019-05-23 08:42:12 +0000813 _filter["nsd-id"] = _id
tiernob24258a2018-10-04 18:39:49 +0200814 if self.db.get_list("nsrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000815 raise EngineException("There is at least one NS using this descriptor", http_code=HTTPStatus.CONFLICT)
816
817 # check NSD referenced by NST
818 del _filter["nsd-id"]
819 _filter["netslice-subnet.ANYINDEX.nsd-ref"] = descriptor_id
820 if self.db.get_list("nsts", _filter):
821 raise EngineException("There is at least one NetSlice Template referencing this descriptor",
822 http_code=HTTPStatus.CONFLICT)
tiernob24258a2018-10-04 18:39:49 +0200823
824
Felipe Vicensb57758d2018-10-16 16:00:20 +0200825class NstTopic(DescriptorTopic):
826 topic = "nsts"
827 topic_msg = "nst"
828
delacruzramo32bab472019-09-13 12:24:22 +0200829 def __init__(self, db, fs, msg, auth):
830 DescriptorTopic.__init__(self, db, fs, msg, auth)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200831
832 @staticmethod
833 def _remove_envelop(indata=None):
834 if not indata:
835 return {}
836 clean_indata = indata
837
Felipe Vicensb57758d2018-10-16 16:00:20 +0200838 if clean_indata.get('nst'):
839 if not isinstance(clean_indata['nst'], list) or len(clean_indata['nst']) != 1:
840 raise EngineException("'nst' must be a list only one element")
841 clean_indata = clean_indata['nst'][0]
gcalvino70434c12018-11-27 15:17:04 +0100842 elif clean_indata.get('nst:nst'):
843 if not isinstance(clean_indata['nst:nst'], list) or len(clean_indata['nst:nst']) != 1:
844 raise EngineException("'nst:nst' must be a list only one element")
845 clean_indata = clean_indata['nst:nst'][0]
Felipe Vicensb57758d2018-10-16 16:00:20 +0200846 return clean_indata
847
Felipe Vicensb57758d2018-10-16 16:00:20 +0200848 def _validate_input_edit(self, indata, force=False):
849 # TODO validate with pyangbind, serialize
850 return indata
851
gcalvinoa6fe0002019-01-09 13:27:11 +0100852 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino70434c12018-11-27 15:17:04 +0100853 indata = self.pyangbind_validation("nsts", indata, force)
Felipe Vicense36ab852018-11-23 14:12:09 +0100854 return indata.copy()
855
Felipe Vicensb57758d2018-10-16 16:00:20 +0200856 def _check_descriptor_dependencies(self, session, descriptor):
857 """
858 Check that the dependent descriptors exist on a new descriptor or edition
tierno65ca36d2019-02-12 19:27:52 +0100859 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Felipe Vicensb57758d2018-10-16 16:00:20 +0200860 :param descriptor: descriptor to be inserted or edit
861 :return: None or raises exception
862 """
863 if not descriptor.get("netslice-subnet"):
864 return
865 for nsd in descriptor["netslice-subnet"]:
866 nsd_id = nsd["nsd-ref"]
tierno65ca36d2019-02-12 19:27:52 +0100867 filter_q = self._get_project_filter(session)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200868 filter_q["id"] = nsd_id
869 if not self.db.get_list("nsds", filter_q):
870 raise EngineException("Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non "
871 "existing nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT)
872
tierno65ca36d2019-02-12 19:27:52 +0100873 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
874 super().check_conflict_on_edit(session, final_content, edit_content, _id)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200875
876 self._check_descriptor_dependencies(session, final_content)
877
tiernob4844ab2019-05-23 08:42:12 +0000878 def check_conflict_on_del(self, session, _id, db_content):
Felipe Vicensb57758d2018-10-16 16:00:20 +0200879 """
880 Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note
881 that NST can be public and be used by other projects.
tierno65ca36d2019-02-12 19:27:52 +0100882 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Felipe Vicens07f31722018-10-29 15:16:44 +0100883 :param _id: nst internal id
tiernob4844ab2019-05-23 08:42:12 +0000884 :param db_content: The database content of the _id.
Felipe Vicensb57758d2018-10-16 16:00:20 +0200885 :return: None or raises EngineException with the conflict
886 """
887 # TODO: Check this method
tierno65ca36d2019-02-12 19:27:52 +0100888 if session["force"]:
Felipe Vicensb57758d2018-10-16 16:00:20 +0200889 return
Felipe Vicens07f31722018-10-29 15:16:44 +0100890 # Get Network Slice Template from Database
tierno65ca36d2019-02-12 19:27:52 +0100891 _filter = self._get_project_filter(session)
tiernoea97c042019-09-13 09:44:42 +0000892 _filter["_admin.nst-id"] = _id
tiernob4844ab2019-05-23 08:42:12 +0000893 if self.db.get_list("nsis", _filter):
894 raise EngineException("there is at least one Netslice Instance using this descriptor",
895 http_code=HTTPStatus.CONFLICT)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200896
897
tiernob24258a2018-10-04 18:39:49 +0200898class PduTopic(BaseTopic):
899 topic = "pdus"
900 topic_msg = "pdu"
901 schema_new = pdu_new_schema
902 schema_edit = pdu_edit_schema
903
delacruzramo32bab472019-09-13 12:24:22 +0200904 def __init__(self, db, fs, msg, auth):
905 BaseTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200906
907 @staticmethod
908 def format_on_new(content, project_id=None, make_public=False):
tierno36ec8602018-11-02 17:27:11 +0100909 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
tiernob24258a2018-10-04 18:39:49 +0200910 content["_admin"]["onboardingState"] = "CREATED"
tierno36ec8602018-11-02 17:27:11 +0100911 content["_admin"]["operationalState"] = "ENABLED"
912 content["_admin"]["usageState"] = "NOT_IN_USE"
tiernob24258a2018-10-04 18:39:49 +0200913
tiernob4844ab2019-05-23 08:42:12 +0000914 def check_conflict_on_del(self, session, _id, db_content):
915 """
916 Check that there is not any vnfr that uses this PDU
917 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
918 :param _id: pdu internal id
919 :param db_content: The database content of the _id.
920 :return: None or raises EngineException with the conflict
921 """
tierno65ca36d2019-02-12 19:27:52 +0100922 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200923 return
tiernob4844ab2019-05-23 08:42:12 +0000924
925 _filter = self._get_project_filter(session)
926 _filter["vdur.pdu-id"] = _id
tiernob24258a2018-10-04 18:39:49 +0200927 if self.db.get_list("vnfrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000928 raise EngineException("There is at least one VNF using this PDU", http_code=HTTPStatus.CONFLICT)