blob: 95ced0d62f93c402ebeb2d3459d270f9bcab212b [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
tierno23acf402019-08-28 13:36:34 +000023from osm_nbi.validation import ValidationError, pdu_new_schema, pdu_edit_schema
24from osm_nbi.base_topic import BaseTopic, EngineException, get_iterable
gcalvino46e4cb82018-10-26 13:10:22 +020025from osm_im.vnfd import vnfd as vnfd_im
26from osm_im.nsd import nsd as nsd_im
gcalvino70434c12018-11-27 15:17:04 +010027from osm_im.nst import nst as nst_im
gcalvino46e4cb82018-10-26 13:10:22 +020028from pyangbind.lib.serialise import pybindJSONDecoder
29import pyangbind.lib.pybindJSON as pybindJSON
tiernob24258a2018-10-04 18:39:49 +020030
31__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
32
33
34class DescriptorTopic(BaseTopic):
35
delacruzramo32bab472019-09-13 12:24:22 +020036 def __init__(self, db, fs, msg, auth):
37 BaseTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +020038
tierno65ca36d2019-02-12 19:27:52 +010039 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
40 super().check_conflict_on_edit(session, final_content, edit_content, _id)
tiernoaa1ca7b2018-11-08 19:00:20 +010041 # 1. validate again with pyangbind
42 # 1.1. remove internal keys
43 internal_keys = {}
44 for k in ("_id", "_admin"):
45 if k in final_content:
46 internal_keys[k] = final_content.pop(k)
gcalvinoa6fe0002019-01-09 13:27:11 +010047 storage_params = internal_keys["_admin"].get("storage")
tierno65ca36d2019-02-12 19:27:52 +010048 serialized = self._validate_input_new(final_content, storage_params, session["force"])
tiernoaa1ca7b2018-11-08 19:00:20 +010049 # 1.2. modify final_content with a serialized version
50 final_content.clear()
51 final_content.update(serialized)
52 # 1.3. restore internal keys
53 for k, v in internal_keys.items():
54 final_content[k] = v
tiernob24258a2018-10-04 18:39:49 +020055
tierno65ca36d2019-02-12 19:27:52 +010056 if session["force"]:
tierno5a5c2182018-11-20 12:27:42 +000057 return
tiernoaa1ca7b2018-11-08 19:00:20 +010058 # 2. check that this id is not present
59 if "id" in edit_content:
tierno65ca36d2019-02-12 19:27:52 +010060 _filter = self._get_project_filter(session)
tiernoaa1ca7b2018-11-08 19:00:20 +010061 _filter["id"] = final_content["id"]
62 _filter["_id.neq"] = _id
63 if self.db.get_one(self.topic, _filter, fail_on_empty=False):
64 raise EngineException("{} with id '{}' already exists for this project".format(self.topic[:-1],
65 final_content["id"]),
66 HTTPStatus.CONFLICT)
tiernob24258a2018-10-04 18:39:49 +020067
68 @staticmethod
69 def format_on_new(content, project_id=None, make_public=False):
70 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
71 content["_admin"]["onboardingState"] = "CREATED"
72 content["_admin"]["operationalState"] = "DISABLED"
tierno36ec8602018-11-02 17:27:11 +010073 content["_admin"]["usageState"] = "NOT_IN_USE"
tiernob24258a2018-10-04 18:39:49 +020074
tiernob4844ab2019-05-23 08:42:12 +000075 def delete_extra(self, session, _id, db_content):
76 """
77 Deletes file system storage associated with the descriptor
78 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
79 :param _id: server internal id
80 :param db_content: The database content of the descriptor
81 :return: None if ok or raises EngineException with the problem
82 """
tiernob24258a2018-10-04 18:39:49 +020083 self.fs.file_delete(_id, ignore_non_exist=True)
tiernof717cbe2018-12-03 16:35:42 +000084 self.fs.file_delete(_id + "_", ignore_non_exist=True) # remove temp folder
tiernob24258a2018-10-04 18:39:49 +020085
86 @staticmethod
87 def get_one_by_id(db, session, topic, id):
88 # find owned by this project
tierno65ca36d2019-02-12 19:27:52 +010089 _filter = BaseTopic._get_project_filter(session)
tiernob24258a2018-10-04 18:39:49 +020090 _filter["id"] = id
91 desc_list = db.get_list(topic, _filter)
92 if len(desc_list) == 1:
93 return desc_list[0]
94 elif len(desc_list) > 1:
95 raise DbException("Found more than one {} with id='{}' belonging to this project".format(topic[:-1], id),
96 HTTPStatus.CONFLICT)
97
98 # not found any: try to find public
tierno65ca36d2019-02-12 19:27:52 +010099 _filter = BaseTopic._get_project_filter(session)
tiernob24258a2018-10-04 18:39:49 +0200100 _filter["id"] = id
101 desc_list = db.get_list(topic, _filter)
102 if not desc_list:
103 raise DbException("Not found any {} with id='{}'".format(topic[:-1], id), HTTPStatus.NOT_FOUND)
104 elif len(desc_list) == 1:
105 return desc_list[0]
106 else:
107 raise DbException("Found more than one public {} with id='{}'; and no one belonging to this project".format(
108 topic[:-1], id), HTTPStatus.CONFLICT)
109
tierno65ca36d2019-02-12 19:27:52 +0100110 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
tiernob24258a2018-10-04 18:39:49 +0200111 """
112 Creates a new almost empty DISABLED entry into database. Due to SOL005, it does not follow normal procedure.
113 Creating a VNFD or NSD is done in two steps: 1. Creates an empty descriptor (this step) and 2) upload content
114 (self.upload_content)
115 :param rollback: list to append created items at database in case a rollback may to be done
tierno65ca36d2019-02-12 19:27:52 +0100116 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200117 :param indata: data to be inserted
118 :param kwargs: used to override the indata descriptor
119 :param headers: http request headers
tiernobdebce92019-07-01 15:36:49 +0000120 :return: _id, None: identity of the inserted data; and None as there is not any operation
tiernob24258a2018-10-04 18:39:49 +0200121 """
122
123 try:
delacruzramo32bab472019-09-13 12:24:22 +0200124 # Check Quota
125 self.check_quota(session)
126
tiernob24258a2018-10-04 18:39:49 +0200127 # _remove_envelop
128 if indata:
129 if "userDefinedData" in indata:
130 indata = indata['userDefinedData']
131
132 # Override descriptor with query string kwargs
133 self._update_input_with_kwargs(indata, kwargs)
134 # uncomment when this method is implemented.
135 # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors
tierno65ca36d2019-02-12 19:27:52 +0100136 # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"])
tiernob24258a2018-10-04 18:39:49 +0200137
138 content = {"_admin": {"userDefinedData": indata}}
tierno65ca36d2019-02-12 19:27:52 +0100139 self.format_on_new(content, session["project_id"], make_public=session["public"])
tiernob24258a2018-10-04 18:39:49 +0200140 _id = self.db.create(self.topic, content)
141 rollback.append({"topic": self.topic, "_id": _id})
tiernobdebce92019-07-01 15:36:49 +0000142 return _id, None
tiernob24258a2018-10-04 18:39:49 +0200143 except ValidationError as e:
144 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
145
tierno65ca36d2019-02-12 19:27:52 +0100146 def upload_content(self, session, _id, indata, kwargs, headers):
tiernob24258a2018-10-04 18:39:49 +0200147 """
148 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 +0100149 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200150 :param _id : the nsd,vnfd is already created, this is the id
151 :param indata: http body request
152 :param kwargs: user query string to override parameters. NOT USED
153 :param headers: http request headers
tierno5a5c2182018-11-20 12:27:42 +0000154 :return: True if package is completely uploaded or False if partial content has been uploded
tiernob24258a2018-10-04 18:39:49 +0200155 Raise exception on error
156 """
157 # Check that _id exists and it is valid
158 current_desc = self.show(session, _id)
159
160 content_range_text = headers.get("Content-Range")
161 expected_md5 = headers.get("Content-File-MD5")
162 compressed = None
163 content_type = headers.get("Content-Type")
164 if content_type and "application/gzip" in content_type or "application/x-gzip" in content_type or \
165 "application/zip" in content_type:
166 compressed = "gzip"
167 filename = headers.get("Content-Filename")
168 if not filename:
169 filename = "package.tar.gz" if compressed else "package"
170 # TODO change to Content-Disposition filename https://tools.ietf.org/html/rfc6266
171 file_pkg = None
172 error_text = ""
173 try:
174 if content_range_text:
175 content_range = content_range_text.replace("-", " ").replace("/", " ").split()
176 if content_range[0] != "bytes": # TODO check x<y not negative < total....
177 raise IndexError()
178 start = int(content_range[1])
179 end = int(content_range[2]) + 1
180 total = int(content_range[3])
181 else:
182 start = 0
tiernof717cbe2018-12-03 16:35:42 +0000183 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 +0200184
185 if start:
tiernof717cbe2018-12-03 16:35:42 +0000186 if not self.fs.file_exists(temp_folder, 'dir'):
tiernob24258a2018-10-04 18:39:49 +0200187 raise EngineException("invalid Transaction-Id header", HTTPStatus.NOT_FOUND)
188 else:
tiernof717cbe2018-12-03 16:35:42 +0000189 self.fs.file_delete(temp_folder, ignore_non_exist=True)
190 self.fs.mkdir(temp_folder)
tiernob24258a2018-10-04 18:39:49 +0200191
192 storage = self.fs.get_params()
193 storage["folder"] = _id
194
tiernof717cbe2018-12-03 16:35:42 +0000195 file_path = (temp_folder, filename)
tiernob24258a2018-10-04 18:39:49 +0200196 if self.fs.file_exists(file_path, 'file'):
197 file_size = self.fs.file_size(file_path)
198 else:
199 file_size = 0
200 if file_size != start:
201 raise EngineException("invalid Content-Range start sequence, expected '{}' but received '{}'".format(
202 file_size, start), HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
203 file_pkg = self.fs.file_open(file_path, 'a+b')
204 if isinstance(indata, dict):
205 indata_text = yaml.safe_dump(indata, indent=4, default_flow_style=False)
206 file_pkg.write(indata_text.encode(encoding="utf-8"))
207 else:
208 indata_len = 0
209 while True:
210 indata_text = indata.read(4096)
211 indata_len += len(indata_text)
212 if not indata_text:
213 break
214 file_pkg.write(indata_text)
215 if content_range_text:
216 if indata_len != end-start:
217 raise EngineException("Mismatch between Content-Range header {}-{} and body length of {}".format(
218 start, end-1, indata_len), HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
219 if end != total:
220 # TODO update to UPLOADING
221 return False
222
223 # PACKAGE UPLOADED
224 if expected_md5:
225 file_pkg.seek(0, 0)
226 file_md5 = md5()
227 chunk_data = file_pkg.read(1024)
228 while chunk_data:
229 file_md5.update(chunk_data)
230 chunk_data = file_pkg.read(1024)
231 if expected_md5 != file_md5.hexdigest():
232 raise EngineException("Error, MD5 mismatch", HTTPStatus.CONFLICT)
233 file_pkg.seek(0, 0)
234 if compressed == "gzip":
235 tar = tarfile.open(mode='r', fileobj=file_pkg)
236 descriptor_file_name = None
237 for tarinfo in tar:
238 tarname = tarinfo.name
239 tarname_path = tarname.split("/")
240 if not tarname_path[0] or ".." in tarname_path: # if start with "/" means absolute path
241 raise EngineException("Absolute path or '..' are not allowed for package descriptor tar.gz")
242 if len(tarname_path) == 1 and not tarinfo.isdir():
243 raise EngineException("All files must be inside a dir for package descriptor tar.gz")
244 if tarname.endswith(".yaml") or tarname.endswith(".json") or tarname.endswith(".yml"):
245 storage["pkg-dir"] = tarname_path[0]
246 if len(tarname_path) == 2:
247 if descriptor_file_name:
248 raise EngineException(
249 "Found more than one descriptor file at package descriptor tar.gz")
250 descriptor_file_name = tarname
251 if not descriptor_file_name:
252 raise EngineException("Not found any descriptor file at package descriptor tar.gz")
253 storage["descriptor"] = descriptor_file_name
254 storage["zipfile"] = filename
tiernof717cbe2018-12-03 16:35:42 +0000255 self.fs.file_extract(tar, temp_folder)
256 with self.fs.file_open((temp_folder, descriptor_file_name), "r") as descriptor_file:
tiernob24258a2018-10-04 18:39:49 +0200257 content = descriptor_file.read()
258 else:
259 content = file_pkg.read()
260 storage["descriptor"] = descriptor_file_name = filename
261
262 if descriptor_file_name.endswith(".json"):
263 error_text = "Invalid json format "
264 indata = json.load(content)
265 else:
266 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200267 indata = yaml.load(content, Loader=yaml.SafeLoader)
tiernob24258a2018-10-04 18:39:49 +0200268
269 current_desc["_admin"]["storage"] = storage
270 current_desc["_admin"]["onboardingState"] = "ONBOARDED"
271 current_desc["_admin"]["operationalState"] = "ENABLED"
272
273 indata = self._remove_envelop(indata)
274
275 # Override descriptor with query string kwargs
276 if kwargs:
277 self._update_input_with_kwargs(indata, kwargs)
278 # it will call overrides method at VnfdTopic or NsdTopic
tierno65ca36d2019-02-12 19:27:52 +0100279 # indata = self._validate_input_edit(indata, force=session["force"])
tiernob24258a2018-10-04 18:39:49 +0200280
281 deep_update_rfc7396(current_desc, indata)
tierno65ca36d2019-02-12 19:27:52 +0100282 self.check_conflict_on_edit(session, current_desc, indata, _id=_id)
tiernob24258a2018-10-04 18:39:49 +0200283 self.db.replace(self.topic, _id, current_desc)
tiernof717cbe2018-12-03 16:35:42 +0000284 self.fs.dir_rename(temp_folder, _id)
tiernob24258a2018-10-04 18:39:49 +0200285
286 indata["_id"] = _id
287 self._send_msg("created", indata)
288
289 # TODO if descriptor has changed because kwargs update content and remove cached zip
290 # TODO if zip is not present creates one
291 return True
292
293 except EngineException:
294 raise
295 except IndexError:
296 raise EngineException("invalid Content-Range header format. Expected 'bytes start-end/total'",
297 HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
298 except IOError as e:
299 raise EngineException("invalid upload transaction sequence: '{}'".format(e), HTTPStatus.BAD_REQUEST)
300 except tarfile.ReadError as e:
301 raise EngineException("invalid file content {}".format(e), HTTPStatus.BAD_REQUEST)
302 except (ValueError, yaml.YAMLError) as e:
303 raise EngineException(error_text + str(e))
304 except ValidationError as e:
305 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
306 finally:
307 if file_pkg:
308 file_pkg.close()
309
310 def get_file(self, session, _id, path=None, accept_header=None):
311 """
312 Return the file content of a vnfd or nsd
tierno65ca36d2019-02-12 19:27:52 +0100313 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tierno87006042018-10-24 12:50:20 +0200314 :param _id: Identity of the vnfd, nsd
tiernob24258a2018-10-04 18:39:49 +0200315 :param path: artifact path or "$DESCRIPTOR" or None
316 :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
tierno87006042018-10-24 12:50:20 +0200317 :return: opened file plus Accept format or raises an exception
tiernob24258a2018-10-04 18:39:49 +0200318 """
319 accept_text = accept_zip = False
320 if accept_header:
321 if 'text/plain' in accept_header or '*/*' in accept_header:
322 accept_text = True
323 if 'application/zip' in accept_header or '*/*' in accept_header:
tierno87006042018-10-24 12:50:20 +0200324 accept_zip = 'application/zip'
325 elif 'application/gzip' in accept_header:
326 accept_zip = 'application/gzip'
327
tiernob24258a2018-10-04 18:39:49 +0200328 if not accept_text and not accept_zip:
329 raise EngineException("provide request header 'Accept' with 'application/zip' or 'text/plain'",
330 http_code=HTTPStatus.NOT_ACCEPTABLE)
331
332 content = self.show(session, _id)
333 if content["_admin"]["onboardingState"] != "ONBOARDED":
334 raise EngineException("Cannot get content because this resource is not at 'ONBOARDED' state. "
335 "onboardingState is {}".format(content["_admin"]["onboardingState"]),
336 http_code=HTTPStatus.CONFLICT)
337 storage = content["_admin"]["storage"]
338 if path is not None and path != "$DESCRIPTOR": # artifacts
339 if not storage.get('pkg-dir'):
340 raise EngineException("Packages does not contains artifacts", http_code=HTTPStatus.BAD_REQUEST)
341 if self.fs.file_exists((storage['folder'], storage['pkg-dir'], *path), 'dir'):
342 folder_content = self.fs.dir_ls((storage['folder'], storage['pkg-dir'], *path))
343 return folder_content, "text/plain"
344 # TODO manage folders in http
345 else:
346 return self.fs.file_open((storage['folder'], storage['pkg-dir'], *path), "rb"),\
347 "application/octet-stream"
348
349 # pkgtype accept ZIP TEXT -> result
350 # manyfiles yes X -> zip
351 # no yes -> error
352 # onefile yes no -> zip
353 # X yes -> text
354
355 if accept_text and (not storage.get('pkg-dir') or path == "$DESCRIPTOR"):
356 return self.fs.file_open((storage['folder'], storage['descriptor']), "r"), "text/plain"
357 elif storage.get('pkg-dir') and not accept_zip:
358 raise EngineException("Packages that contains several files need to be retrieved with 'application/zip'"
359 "Accept header", http_code=HTTPStatus.NOT_ACCEPTABLE)
360 else:
361 if not storage.get('zipfile'):
362 # TODO generate zipfile if not present
363 raise EngineException("Only allowed 'text/plain' Accept header for this descriptor. To be solved in "
364 "future versions", http_code=HTTPStatus.NOT_ACCEPTABLE)
tierno87006042018-10-24 12:50:20 +0200365 return self.fs.file_open((storage['folder'], storage['zipfile']), "rb"), accept_zip
tiernob24258a2018-10-04 18:39:49 +0200366
gcalvino46e4cb82018-10-26 13:10:22 +0200367 def pyangbind_validation(self, item, data, force=False):
368 try:
369 if item == "vnfds":
370 myvnfd = vnfd_im()
371 pybindJSONDecoder.load_ietf_json({'vnfd:vnfd-catalog': {'vnfd': [data]}}, None, None, obj=myvnfd,
372 path_helper=True, skip_unknown=force)
373 out = pybindJSON.dumps(myvnfd, mode="ietf")
374 elif item == "nsds":
375 mynsd = nsd_im()
376 pybindJSONDecoder.load_ietf_json({'nsd:nsd-catalog': {'nsd': [data]}}, None, None, obj=mynsd,
377 path_helper=True, skip_unknown=force)
378 out = pybindJSON.dumps(mynsd, mode="ietf")
gcalvino70434c12018-11-27 15:17:04 +0100379 elif item == "nsts":
380 mynst = nst_im()
381 pybindJSONDecoder.load_ietf_json({'nst': [data]}, None, None, obj=mynst,
382 path_helper=True, skip_unknown=force)
383 out = pybindJSON.dumps(mynst, mode="ietf")
gcalvino46e4cb82018-10-26 13:10:22 +0200384 else:
385 raise EngineException("Not possible to validate '{}' item".format(item),
386 http_code=HTTPStatus.INTERNAL_SERVER_ERROR)
387
388 desc_out = self._remove_envelop(yaml.safe_load(out))
389 return desc_out
390
391 except Exception as e:
392 raise EngineException("Error in pyangbind validation: {}".format(str(e)),
393 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
394
tiernob24258a2018-10-04 18:39:49 +0200395
396class VnfdTopic(DescriptorTopic):
397 topic = "vnfds"
398 topic_msg = "vnfd"
399
delacruzramo32bab472019-09-13 12:24:22 +0200400 def __init__(self, db, fs, msg, auth):
401 DescriptorTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200402
403 @staticmethod
404 def _remove_envelop(indata=None):
405 if not indata:
406 return {}
407 clean_indata = indata
408 if clean_indata.get('vnfd:vnfd-catalog'):
409 clean_indata = clean_indata['vnfd:vnfd-catalog']
410 elif clean_indata.get('vnfd-catalog'):
411 clean_indata = clean_indata['vnfd-catalog']
412 if clean_indata.get('vnfd'):
413 if not isinstance(clean_indata['vnfd'], list) or len(clean_indata['vnfd']) != 1:
gcalvino46e4cb82018-10-26 13:10:22 +0200414 raise EngineException("'vnfd' must be a list of only one element")
tiernob24258a2018-10-04 18:39:49 +0200415 clean_indata = clean_indata['vnfd'][0]
gcalvino46e4cb82018-10-26 13:10:22 +0200416 elif clean_indata.get('vnfd:vnfd'):
417 if not isinstance(clean_indata['vnfd:vnfd'], list) or len(clean_indata['vnfd:vnfd']) != 1:
418 raise EngineException("'vnfd:vnfd' must be a list of only one element")
419 clean_indata = clean_indata['vnfd:vnfd'][0]
tiernob24258a2018-10-04 18:39:49 +0200420 return clean_indata
421
tierno65ca36d2019-02-12 19:27:52 +0100422 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
423 super().check_conflict_on_edit(session, final_content, edit_content, _id)
tierno36ec8602018-11-02 17:27:11 +0100424
425 # set type of vnfd
426 contains_pdu = False
427 contains_vdu = False
428 for vdu in get_iterable(final_content.get("vdu")):
429 if vdu.get("pdu-type"):
430 contains_pdu = True
431 else:
432 contains_vdu = True
433 if contains_pdu:
434 final_content["_admin"]["type"] = "hnfd" if contains_vdu else "pnfd"
435 elif contains_vdu:
436 final_content["_admin"]["type"] = "vnfd"
437 # if neither vud nor pdu do not fill type
438
tiernob4844ab2019-05-23 08:42:12 +0000439 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200440 """
441 Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
442 that VNFD can be public and be used by NSD of other projects. Also check there are not deployments, or vnfr
443 that uses this vnfd
tierno65ca36d2019-02-12 19:27:52 +0100444 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob4844ab2019-05-23 08:42:12 +0000445 :param _id: vnfd internal id
446 :param db_content: The database content of the _id.
tiernob24258a2018-10-04 18:39:49 +0200447 :return: None or raises EngineException with the conflict
448 """
tierno65ca36d2019-02-12 19:27:52 +0100449 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200450 return
tiernob4844ab2019-05-23 08:42:12 +0000451 descriptor = db_content
tiernob24258a2018-10-04 18:39:49 +0200452 descriptor_id = descriptor.get("id")
453 if not descriptor_id: # empty vnfd not uploaded
454 return
455
tierno65ca36d2019-02-12 19:27:52 +0100456 _filter = self._get_project_filter(session)
tiernob4844ab2019-05-23 08:42:12 +0000457
tiernob24258a2018-10-04 18:39:49 +0200458 # check vnfrs using this vnfd
459 _filter["vnfd-id"] = _id
460 if self.db.get_list("vnfrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000461 raise EngineException("There is at least one VNF using this descriptor", http_code=HTTPStatus.CONFLICT)
462
463 # check NSD referencing this VNFD
tiernob24258a2018-10-04 18:39:49 +0200464 del _filter["vnfd-id"]
tiernob24258a2018-10-04 18:39:49 +0200465 _filter["constituent-vnfd.ANYINDEX.vnfd-id-ref"] = descriptor_id
466 if self.db.get_list("nsds", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000467 raise EngineException("There is at least one NSD referencing this descriptor",
468 http_code=HTTPStatus.CONFLICT)
tiernob24258a2018-10-04 18:39:49 +0200469
gcalvinoa6fe0002019-01-09 13:27:11 +0100470 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino46e4cb82018-10-26 13:10:22 +0200471 indata = self.pyangbind_validation("vnfds", indata, force)
gcalvino5e72d152018-10-23 11:46:57 +0200472 # Cross references validation in the descriptor
gcalvinoe45aded2018-11-13 17:17:28 +0100473 if indata.get("vdu"):
474 if not indata.get("mgmt-interface"):
475 raise EngineException("'mgmt-interface' is a mandatory field and it is not defined",
tierno40fbcad2018-10-26 10:58:15 +0200476 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvinoe45aded2018-11-13 17:17:28 +0100477 if indata["mgmt-interface"].get("cp"):
478 for cp in get_iterable(indata.get("connection-point")):
479 if cp["name"] == indata["mgmt-interface"]["cp"]:
480 break
481 else:
482 raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point"
483 .format(indata["mgmt-interface"]["cp"]),
484 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvino5e72d152018-10-23 11:46:57 +0200485
486 for vdu in get_iterable(indata.get("vdu")):
487 for interface in get_iterable(vdu.get("interface")):
488 if interface.get("external-connection-point-ref"):
489 for cp in get_iterable(indata.get("connection-point")):
tierno40fbcad2018-10-26 10:58:15 +0200490 if cp["name"] == interface["external-connection-point-ref"]:
gcalvino5e72d152018-10-23 11:46:57 +0200491 break
492 else:
tierno40fbcad2018-10-26 10:58:15 +0200493 raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' "
gcalvino5e72d152018-10-23 11:46:57 +0200494 "must match an existing connection-point"
495 .format(vdu["id"], interface["name"],
496 interface["external-connection-point-ref"]),
497 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno40fbcad2018-10-26 10:58:15 +0200498
499 elif interface.get("internal-connection-point-ref"):
gcalvino5e72d152018-10-23 11:46:57 +0200500 for internal_cp in get_iterable(vdu.get("internal-connection-point")):
tierno40fbcad2018-10-26 10:58:15 +0200501 if interface["internal-connection-point-ref"] == internal_cp.get("id"):
gcalvino5e72d152018-10-23 11:46:57 +0200502 break
503 else:
tierno40fbcad2018-10-26 10:58:15 +0200504 raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' "
505 "must match an existing vdu:internal-connection-point"
506 .format(vdu["id"], interface["name"],
507 interface["internal-connection-point-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200508 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvinoa6fe0002019-01-09 13:27:11 +0100509 # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
510 if vdu.get("vdu-configuration"):
511 if vdu["vdu-configuration"].get("juju"):
512 if not self._validate_package_folders(storage_params, 'charms'):
513 raise EngineException("Charm defined in vnf[id={}]:vdu[id={}] but not present in "
514 "package".format(indata["id"], vdu["id"]))
515 # Validate that if descriptor contains cloud-init, artifacts _admin.storage."pkg-dir" is not none
516 if vdu.get("cloud-init-file"):
517 if not self._validate_package_folders(storage_params, 'cloud_init', vdu["cloud-init-file"]):
518 raise EngineException("Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in "
519 "package".format(indata["id"], vdu["id"]))
520 # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
521 if indata.get("vnf-configuration"):
522 if indata["vnf-configuration"].get("juju"):
523 if not self._validate_package_folders(storage_params, 'charms'):
524 raise EngineException("Charm defined in vnf[id={}] but not present in "
525 "package".format(indata["id"]))
delacruzramo5727a372019-03-28 12:29:04 +0100526 vld_names = [] # For detection of duplicated VLD names
gcalvino5e72d152018-10-23 11:46:57 +0200527 for ivld in get_iterable(indata.get("internal-vld")):
delacruzramo5727a372019-03-28 12:29:04 +0100528 # BEGIN Detection of duplicated VLD names
529 ivld_name = ivld["name"]
530 if ivld_name in vld_names:
531 raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]"
532 .format(ivld["name"], indata["id"], ivld["id"]),
533 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
534 else:
535 vld_names.append(ivld_name)
536 # END Detection of duplicated VLD names
gcalvino5e72d152018-10-23 11:46:57 +0200537 for icp in get_iterable(ivld.get("internal-connection-point")):
538 icp_mark = False
539 for vdu in get_iterable(indata.get("vdu")):
540 for internal_cp in get_iterable(vdu.get("internal-connection-point")):
541 if icp["id-ref"] == internal_cp["id"]:
542 icp_mark = True
543 break
544 if icp_mark:
545 break
546 else:
tierno40fbcad2018-10-26 10:58:15 +0200547 raise EngineException("internal-vld[id='{}']:internal-connection-point='{}' must match an existing "
548 "vdu:internal-connection-point".format(ivld["id"], icp["id-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200549 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
550 if ivld.get("ip-profile-ref"):
tierno40fbcad2018-10-26 10:58:15 +0200551 for ip_prof in get_iterable(indata.get("ip-profiles")):
gcalvino5e72d152018-10-23 11:46:57 +0200552 if ip_prof["name"] == get_iterable(ivld.get("ip-profile-ref")):
553 break
554 else:
tierno40fbcad2018-10-26 10:58:15 +0200555 raise EngineException("internal-vld[id='{}']:ip-profile-ref='{}' does not exist".format(
gcalvino5e72d152018-10-23 11:46:57 +0200556 ivld["id"], ivld["ip-profile-ref"]),
557 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
558 for mp in get_iterable(indata.get("monitoring-param")):
559 if mp.get("vdu-monitoring-param"):
560 mp_vmp_mark = False
561 for vdu in get_iterable(indata.get("vdu")):
562 for vmp in get_iterable(vdu.get("monitoring-param")):
tierno40fbcad2018-10-26 10:58:15 +0200563 if vmp["id"] == mp["vdu-monitoring-param"].get("vdu-monitoring-param-ref") and vdu["id"] ==\
gcalvino5e72d152018-10-23 11:46:57 +0200564 mp["vdu-monitoring-param"]["vdu-ref"]:
565 mp_vmp_mark = True
566 break
567 if mp_vmp_mark:
568 break
569 else:
570 raise EngineException("monitoring-param:vdu-monitoring-param:vdu-monitoring-param-ref='{}' not "
tierno40fbcad2018-10-26 10:58:15 +0200571 "defined at vdu[id='{}'] or vdu does not exist"
gcalvino5e72d152018-10-23 11:46:57 +0200572 .format(mp["vdu-monitoring-param"]["vdu-monitoring-param-ref"],
573 mp["vdu-monitoring-param"]["vdu-ref"]),
574 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
575 elif mp.get("vdu-metric"):
576 mp_vm_mark = False
577 for vdu in get_iterable(indata.get("vdu")):
578 if vdu.get("vdu-configuration"):
579 for metric in get_iterable(vdu["vdu-configuration"].get("metrics")):
580 if metric["name"] == mp["vdu-metric"]["vdu-metric-name-ref"] and vdu["id"] == \
581 mp["vdu-metric"]["vdu-ref"]:
582 mp_vm_mark = True
583 break
584 if mp_vm_mark:
585 break
586 else:
tierno40fbcad2018-10-26 10:58:15 +0200587 raise EngineException("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined at "
588 "vdu[id='{}'] or vdu does not exist"
gcalvino5e72d152018-10-23 11:46:57 +0200589 .format(mp["vdu-metric"]["vdu-metric-name-ref"],
590 mp["vdu-metric"]["vdu-ref"]),
591 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
592
593 for sgd in get_iterable(indata.get("scaling-group-descriptor")):
594 for sp in get_iterable(sgd.get("scaling-policy")):
595 for sc in get_iterable(sp.get("scaling-criteria")):
596 for mp in get_iterable(indata.get("monitoring-param")):
597 if mp["id"] == get_iterable(sc.get("vnf-monitoring-param-ref")):
598 break
599 else:
tierno40fbcad2018-10-26 10:58:15 +0200600 raise EngineException("scaling-group-descriptor[name='{}']:scaling-criteria[name='{}']:"
601 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param"
602 .format(sgd["name"], sc["name"], sc["vnf-monitoring-param-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200603 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
604 for sgd_vdu in get_iterable(sgd.get("vdu")):
605 sgd_vdu_mark = False
606 for vdu in get_iterable(indata.get("vdu")):
607 if vdu["id"] == sgd_vdu["vdu-id-ref"]:
608 sgd_vdu_mark = True
609 break
610 if sgd_vdu_mark:
611 break
612 else:
tierno40fbcad2018-10-26 10:58:15 +0200613 raise EngineException("scaling-group-descriptor[name='{}']:vdu-id-ref={} does not match any vdu"
614 .format(sgd["name"], sgd_vdu["vdu-id-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200615 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
616 for sca in get_iterable(sgd.get("scaling-config-action")):
tierno40fbcad2018-10-26 10:58:15 +0200617 if not indata.get("vnf-configuration"):
618 raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced by "
619 "scaling-group-descriptor[name='{}']:scaling-config-action"
620 .format(sgd["name"]),
gcalvino5e72d152018-10-23 11:46:57 +0200621 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno40fbcad2018-10-26 10:58:15 +0200622 for primitive in get_iterable(indata["vnf-configuration"].get("config-primitive")):
623 if primitive["name"] == sca["vnf-config-primitive-name-ref"]:
624 break
625 else:
626 raise EngineException("scaling-group-descriptor[name='{}']:scaling-config-action:vnf-config-"
627 "primitive-name-ref='{}' does not match any "
628 "vnf-configuration:config-primitive:name"
629 .format(sgd["name"], sca["vnf-config-primitive-name-ref"]),
630 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200631 return indata
632
633 def _validate_input_edit(self, indata, force=False):
tiernoaa1ca7b2018-11-08 19:00:20 +0100634 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
tiernob24258a2018-10-04 18:39:49 +0200635 return indata
636
gcalvinoa6fe0002019-01-09 13:27:11 +0100637 def _validate_package_folders(self, storage_params, folder, file=None):
638 if not storage_params or not storage_params.get("pkg-dir"):
639 return False
640 else:
641 if self.fs.file_exists("{}_".format(storage_params["folder"]), 'dir'):
642 f = "{}_/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder)
643 else:
644 f = "{}/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder)
645 if file:
646 return self.fs.file_exists("{}/{}".format(f, file), 'file')
647 else:
648 if self.fs.file_exists(f, 'dir'):
649 if self.fs.dir_ls(f):
650 return True
651 return False
652
tiernob24258a2018-10-04 18:39:49 +0200653
654class NsdTopic(DescriptorTopic):
655 topic = "nsds"
656 topic_msg = "nsd"
657
delacruzramo32bab472019-09-13 12:24:22 +0200658 def __init__(self, db, fs, msg, auth):
659 DescriptorTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200660
661 @staticmethod
662 def _remove_envelop(indata=None):
663 if not indata:
664 return {}
665 clean_indata = indata
666
667 if clean_indata.get('nsd:nsd-catalog'):
668 clean_indata = clean_indata['nsd:nsd-catalog']
669 elif clean_indata.get('nsd-catalog'):
670 clean_indata = clean_indata['nsd-catalog']
671 if clean_indata.get('nsd'):
672 if not isinstance(clean_indata['nsd'], list) or len(clean_indata['nsd']) != 1:
gcalvino46e4cb82018-10-26 13:10:22 +0200673 raise EngineException("'nsd' must be a list of only one element")
tiernob24258a2018-10-04 18:39:49 +0200674 clean_indata = clean_indata['nsd'][0]
gcalvino46e4cb82018-10-26 13:10:22 +0200675 elif clean_indata.get('nsd:nsd'):
676 if not isinstance(clean_indata['nsd:nsd'], list) or len(clean_indata['nsd:nsd']) != 1:
677 raise EngineException("'nsd:nsd' must be a list of only one element")
678 clean_indata = clean_indata['nsd:nsd'][0]
tiernob24258a2018-10-04 18:39:49 +0200679 return clean_indata
680
gcalvinoa6fe0002019-01-09 13:27:11 +0100681 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino46e4cb82018-10-26 13:10:22 +0200682 indata = self.pyangbind_validation("nsds", indata, force)
tierno5a5c2182018-11-20 12:27:42 +0000683 # Cross references validation in the descriptor
tiernoaa1ca7b2018-11-08 19:00:20 +0100684 # 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 +0000685 for vld in get_iterable(indata.get("vld")):
vijay.r8ba4e052019-03-29 17:52:26 +0530686 if vld.get("mgmt-network") and vld.get("ip-profile-ref"):
687 raise EngineException("Error at vld[id='{}']:ip-profile-ref"
688 " You cannot set an ip-profile when mgmt-network is True"
689 .format(vld["id"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno5a5c2182018-11-20 12:27:42 +0000690 for vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
691 for constituent_vnfd in get_iterable(indata.get("constituent-vnfd")):
692 if vnfd_cp["member-vnf-index-ref"] == constituent_vnfd["member-vnf-index"]:
693 if vnfd_cp.get("vnfd-id-ref") and vnfd_cp["vnfd-id-ref"] != constituent_vnfd["vnfd-id-ref"]:
694 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[vnfd-id-ref='{}'] "
695 "does not match constituent-vnfd[member-vnf-index='{}']:vnfd-id-ref"
696 " '{}'".format(vld["id"], vnfd_cp["vnfd-id-ref"],
697 constituent_vnfd["member-vnf-index"],
698 constituent_vnfd["vnfd-id-ref"]),
699 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
700 break
701 else:
702 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
703 "does not match any constituent-vnfd:member-vnf-index"
704 .format(vld["id"], vnfd_cp["member-vnf-index-ref"]),
705 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200706 return indata
707
708 def _validate_input_edit(self, indata, force=False):
tiernoaa1ca7b2018-11-08 19:00:20 +0100709 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
tiernob24258a2018-10-04 18:39:49 +0200710 return indata
711
tierno65ca36d2019-02-12 19:27:52 +0100712 def _check_descriptor_dependencies(self, session, descriptor):
tiernob24258a2018-10-04 18:39:49 +0200713 """
tierno5a5c2182018-11-20 12:27:42 +0000714 Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
715 connection points are ok
tierno65ca36d2019-02-12 19:27:52 +0100716 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200717 :param descriptor: descriptor to be inserted or edit
718 :return: None or raises exception
719 """
tierno65ca36d2019-02-12 19:27:52 +0100720 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200721 return
tierno5a5c2182018-11-20 12:27:42 +0000722 member_vnfd_index = {}
tierno65ca36d2019-02-12 19:27:52 +0100723 if descriptor.get("constituent-vnfd") and not session["force"]:
tierno5a5c2182018-11-20 12:27:42 +0000724 for vnf in descriptor["constituent-vnfd"]:
725 vnfd_id = vnf["vnfd-id-ref"]
tierno65ca36d2019-02-12 19:27:52 +0100726 filter_q = self._get_project_filter(session)
tierno5a5c2182018-11-20 12:27:42 +0000727 filter_q["id"] = vnfd_id
728 vnf_list = self.db.get_list("vnfds", filter_q)
729 if not vnf_list:
730 raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non "
731 "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT)
732 # elif len(vnf_list) > 1:
733 # raise EngineException("More than one vnfd found for id='{}'".format(vnfd_id),
734 # http_code=HTTPStatus.CONFLICT)
735 member_vnfd_index[vnf["member-vnf-index"]] = vnf_list[0]
736
737 # Cross references validation in the descriptor and vnfd connection point validation
738 for vld in get_iterable(descriptor.get("vld")):
739 for referenced_vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
740 # look if this vnfd contains this connection point
741 vnfd = member_vnfd_index.get(referenced_vnfd_cp["member-vnf-index-ref"])
742 if not vnfd:
743 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
744 "does not match any constituent-vnfd:member-vnf-index"
745 .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"]),
746 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
747 for vnfd_cp in get_iterable(vnfd.get("connection-point")):
748 if referenced_vnfd_cp.get("vnfd-connection-point-ref") == vnfd_cp["name"]:
749 break
750 else:
751 raise EngineException(
752 "Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}']:vnfd-"
753 "connection-point-ref='{}' references a non existing conection-point:name inside vnfd '{}'"
754 .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"],
755 referenced_vnfd_cp["vnfd-connection-point-ref"], vnfd["id"]),
756 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200757
tierno65ca36d2019-02-12 19:27:52 +0100758 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
759 super().check_conflict_on_edit(session, final_content, edit_content, _id)
tiernob24258a2018-10-04 18:39:49 +0200760
tierno65ca36d2019-02-12 19:27:52 +0100761 self._check_descriptor_dependencies(session, final_content)
tiernob24258a2018-10-04 18:39:49 +0200762
tiernob4844ab2019-05-23 08:42:12 +0000763 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200764 """
765 Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note
766 that NSD can be public and be used by other projects.
tierno65ca36d2019-02-12 19:27:52 +0100767 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob4844ab2019-05-23 08:42:12 +0000768 :param _id: nsd internal id
769 :param db_content: The database content of the _id
tiernob24258a2018-10-04 18:39:49 +0200770 :return: None or raises EngineException with the conflict
771 """
tierno65ca36d2019-02-12 19:27:52 +0100772 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200773 return
tiernob4844ab2019-05-23 08:42:12 +0000774 descriptor = db_content
775 descriptor_id = descriptor.get("id")
776 if not descriptor_id: # empty nsd not uploaded
777 return
778
779 # check NSD used by NS
tierno65ca36d2019-02-12 19:27:52 +0100780 _filter = self._get_project_filter(session)
tiernob4844ab2019-05-23 08:42:12 +0000781 _filter["nsd-id"] = _id
tiernob24258a2018-10-04 18:39:49 +0200782 if self.db.get_list("nsrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000783 raise EngineException("There is at least one NS using this descriptor", http_code=HTTPStatus.CONFLICT)
784
785 # check NSD referenced by NST
786 del _filter["nsd-id"]
787 _filter["netslice-subnet.ANYINDEX.nsd-ref"] = descriptor_id
788 if self.db.get_list("nsts", _filter):
789 raise EngineException("There is at least one NetSlice Template referencing this descriptor",
790 http_code=HTTPStatus.CONFLICT)
tiernob24258a2018-10-04 18:39:49 +0200791
792
Felipe Vicensb57758d2018-10-16 16:00:20 +0200793class NstTopic(DescriptorTopic):
794 topic = "nsts"
795 topic_msg = "nst"
796
delacruzramo32bab472019-09-13 12:24:22 +0200797 def __init__(self, db, fs, msg, auth):
798 DescriptorTopic.__init__(self, db, fs, msg, auth)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200799
800 @staticmethod
801 def _remove_envelop(indata=None):
802 if not indata:
803 return {}
804 clean_indata = indata
805
Felipe Vicensb57758d2018-10-16 16:00:20 +0200806 if clean_indata.get('nst'):
807 if not isinstance(clean_indata['nst'], list) or len(clean_indata['nst']) != 1:
808 raise EngineException("'nst' must be a list only one element")
809 clean_indata = clean_indata['nst'][0]
gcalvino70434c12018-11-27 15:17:04 +0100810 elif clean_indata.get('nst:nst'):
811 if not isinstance(clean_indata['nst:nst'], list) or len(clean_indata['nst:nst']) != 1:
812 raise EngineException("'nst:nst' must be a list only one element")
813 clean_indata = clean_indata['nst:nst'][0]
Felipe Vicensb57758d2018-10-16 16:00:20 +0200814 return clean_indata
815
Felipe Vicensb57758d2018-10-16 16:00:20 +0200816 def _validate_input_edit(self, indata, force=False):
817 # TODO validate with pyangbind, serialize
818 return indata
819
gcalvinoa6fe0002019-01-09 13:27:11 +0100820 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino70434c12018-11-27 15:17:04 +0100821 indata = self.pyangbind_validation("nsts", indata, force)
Felipe Vicense36ab852018-11-23 14:12:09 +0100822 return indata.copy()
823
Felipe Vicensb57758d2018-10-16 16:00:20 +0200824 def _check_descriptor_dependencies(self, session, descriptor):
825 """
826 Check that the dependent descriptors exist on a new descriptor or edition
tierno65ca36d2019-02-12 19:27:52 +0100827 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Felipe Vicensb57758d2018-10-16 16:00:20 +0200828 :param descriptor: descriptor to be inserted or edit
829 :return: None or raises exception
830 """
831 if not descriptor.get("netslice-subnet"):
832 return
833 for nsd in descriptor["netslice-subnet"]:
834 nsd_id = nsd["nsd-ref"]
tierno65ca36d2019-02-12 19:27:52 +0100835 filter_q = self._get_project_filter(session)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200836 filter_q["id"] = nsd_id
837 if not self.db.get_list("nsds", filter_q):
838 raise EngineException("Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non "
839 "existing nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT)
840
tierno65ca36d2019-02-12 19:27:52 +0100841 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
842 super().check_conflict_on_edit(session, final_content, edit_content, _id)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200843
844 self._check_descriptor_dependencies(session, final_content)
845
tiernob4844ab2019-05-23 08:42:12 +0000846 def check_conflict_on_del(self, session, _id, db_content):
Felipe Vicensb57758d2018-10-16 16:00:20 +0200847 """
848 Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note
849 that NST can be public and be used by other projects.
tierno65ca36d2019-02-12 19:27:52 +0100850 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Felipe Vicens07f31722018-10-29 15:16:44 +0100851 :param _id: nst internal id
tiernob4844ab2019-05-23 08:42:12 +0000852 :param db_content: The database content of the _id.
Felipe Vicensb57758d2018-10-16 16:00:20 +0200853 :return: None or raises EngineException with the conflict
854 """
855 # TODO: Check this method
tierno65ca36d2019-02-12 19:27:52 +0100856 if session["force"]:
Felipe Vicensb57758d2018-10-16 16:00:20 +0200857 return
Felipe Vicens07f31722018-10-29 15:16:44 +0100858 # Get Network Slice Template from Database
tierno65ca36d2019-02-12 19:27:52 +0100859 _filter = self._get_project_filter(session)
tiernoea97c042019-09-13 09:44:42 +0000860 _filter["_admin.nst-id"] = _id
tiernob4844ab2019-05-23 08:42:12 +0000861 if self.db.get_list("nsis", _filter):
862 raise EngineException("there is at least one Netslice Instance using this descriptor",
863 http_code=HTTPStatus.CONFLICT)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200864
865
tiernob24258a2018-10-04 18:39:49 +0200866class PduTopic(BaseTopic):
867 topic = "pdus"
868 topic_msg = "pdu"
869 schema_new = pdu_new_schema
870 schema_edit = pdu_edit_schema
871
delacruzramo32bab472019-09-13 12:24:22 +0200872 def __init__(self, db, fs, msg, auth):
873 BaseTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200874
875 @staticmethod
876 def format_on_new(content, project_id=None, make_public=False):
tierno36ec8602018-11-02 17:27:11 +0100877 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
tiernob24258a2018-10-04 18:39:49 +0200878 content["_admin"]["onboardingState"] = "CREATED"
tierno36ec8602018-11-02 17:27:11 +0100879 content["_admin"]["operationalState"] = "ENABLED"
880 content["_admin"]["usageState"] = "NOT_IN_USE"
tiernob24258a2018-10-04 18:39:49 +0200881
tiernob4844ab2019-05-23 08:42:12 +0000882 def check_conflict_on_del(self, session, _id, db_content):
883 """
884 Check that there is not any vnfr that uses this PDU
885 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
886 :param _id: pdu internal id
887 :param db_content: The database content of the _id.
888 :return: None or raises EngineException with the conflict
889 """
tierno65ca36d2019-02-12 19:27:52 +0100890 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200891 return
tiernob4844ab2019-05-23 08:42:12 +0000892
893 _filter = self._get_project_filter(session)
894 _filter["vdur.pdu-id"] = _id
tiernob24258a2018-10-04 18:39:49 +0200895 if self.db.get_list("vnfrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000896 raise EngineException("There is at least one VNF using this PDU", http_code=HTTPStatus.CONFLICT)