blob: 0939000b2bb6cc970ab0080744a896f70defa916 [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})
K Sai Kiranc96fd692019-10-16 17:50:53 +0530142 self._send_msg("created", {"_id": _id})
tiernobdebce92019-07-01 15:36:49 +0000143 return _id, None
tiernob24258a2018-10-04 18:39:49 +0200144 except ValidationError as e:
145 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
146
tierno65ca36d2019-02-12 19:27:52 +0100147 def upload_content(self, session, _id, indata, kwargs, headers):
tiernob24258a2018-10-04 18:39:49 +0200148 """
149 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 +0100150 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200151 :param _id : the nsd,vnfd is already created, this is the id
152 :param indata: http body request
153 :param kwargs: user query string to override parameters. NOT USED
154 :param headers: http request headers
tierno5a5c2182018-11-20 12:27:42 +0000155 :return: True if package is completely uploaded or False if partial content has been uploded
tiernob24258a2018-10-04 18:39:49 +0200156 Raise exception on error
157 """
158 # Check that _id exists and it is valid
159 current_desc = self.show(session, _id)
160
161 content_range_text = headers.get("Content-Range")
162 expected_md5 = headers.get("Content-File-MD5")
163 compressed = None
164 content_type = headers.get("Content-Type")
165 if content_type and "application/gzip" in content_type or "application/x-gzip" in content_type or \
166 "application/zip" in content_type:
167 compressed = "gzip"
168 filename = headers.get("Content-Filename")
169 if not filename:
170 filename = "package.tar.gz" if compressed else "package"
171 # TODO change to Content-Disposition filename https://tools.ietf.org/html/rfc6266
172 file_pkg = None
173 error_text = ""
174 try:
175 if content_range_text:
176 content_range = content_range_text.replace("-", " ").replace("/", " ").split()
177 if content_range[0] != "bytes": # TODO check x<y not negative < total....
178 raise IndexError()
179 start = int(content_range[1])
180 end = int(content_range[2]) + 1
181 total = int(content_range[3])
182 else:
183 start = 0
tiernof717cbe2018-12-03 16:35:42 +0000184 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 +0200185
186 if start:
tiernof717cbe2018-12-03 16:35:42 +0000187 if not self.fs.file_exists(temp_folder, 'dir'):
tiernob24258a2018-10-04 18:39:49 +0200188 raise EngineException("invalid Transaction-Id header", HTTPStatus.NOT_FOUND)
189 else:
tiernof717cbe2018-12-03 16:35:42 +0000190 self.fs.file_delete(temp_folder, ignore_non_exist=True)
191 self.fs.mkdir(temp_folder)
tiernob24258a2018-10-04 18:39:49 +0200192
193 storage = self.fs.get_params()
194 storage["folder"] = _id
195
tiernof717cbe2018-12-03 16:35:42 +0000196 file_path = (temp_folder, filename)
tiernob24258a2018-10-04 18:39:49 +0200197 if self.fs.file_exists(file_path, 'file'):
198 file_size = self.fs.file_size(file_path)
199 else:
200 file_size = 0
201 if file_size != start:
202 raise EngineException("invalid Content-Range start sequence, expected '{}' but received '{}'".format(
203 file_size, start), HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
204 file_pkg = self.fs.file_open(file_path, 'a+b')
205 if isinstance(indata, dict):
206 indata_text = yaml.safe_dump(indata, indent=4, default_flow_style=False)
207 file_pkg.write(indata_text.encode(encoding="utf-8"))
208 else:
209 indata_len = 0
210 while True:
211 indata_text = indata.read(4096)
212 indata_len += len(indata_text)
213 if not indata_text:
214 break
215 file_pkg.write(indata_text)
216 if content_range_text:
217 if indata_len != end-start:
218 raise EngineException("Mismatch between Content-Range header {}-{} and body length of {}".format(
219 start, end-1, indata_len), HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
220 if end != total:
221 # TODO update to UPLOADING
222 return False
223
224 # PACKAGE UPLOADED
225 if expected_md5:
226 file_pkg.seek(0, 0)
227 file_md5 = md5()
228 chunk_data = file_pkg.read(1024)
229 while chunk_data:
230 file_md5.update(chunk_data)
231 chunk_data = file_pkg.read(1024)
232 if expected_md5 != file_md5.hexdigest():
233 raise EngineException("Error, MD5 mismatch", HTTPStatus.CONFLICT)
234 file_pkg.seek(0, 0)
235 if compressed == "gzip":
236 tar = tarfile.open(mode='r', fileobj=file_pkg)
237 descriptor_file_name = None
238 for tarinfo in tar:
239 tarname = tarinfo.name
240 tarname_path = tarname.split("/")
241 if not tarname_path[0] or ".." in tarname_path: # if start with "/" means absolute path
242 raise EngineException("Absolute path or '..' are not allowed for package descriptor tar.gz")
243 if len(tarname_path) == 1 and not tarinfo.isdir():
244 raise EngineException("All files must be inside a dir for package descriptor tar.gz")
245 if tarname.endswith(".yaml") or tarname.endswith(".json") or tarname.endswith(".yml"):
246 storage["pkg-dir"] = tarname_path[0]
247 if len(tarname_path) == 2:
248 if descriptor_file_name:
249 raise EngineException(
250 "Found more than one descriptor file at package descriptor tar.gz")
251 descriptor_file_name = tarname
252 if not descriptor_file_name:
253 raise EngineException("Not found any descriptor file at package descriptor tar.gz")
254 storage["descriptor"] = descriptor_file_name
255 storage["zipfile"] = filename
tiernof717cbe2018-12-03 16:35:42 +0000256 self.fs.file_extract(tar, temp_folder)
257 with self.fs.file_open((temp_folder, descriptor_file_name), "r") as descriptor_file:
tiernob24258a2018-10-04 18:39:49 +0200258 content = descriptor_file.read()
259 else:
260 content = file_pkg.read()
261 storage["descriptor"] = descriptor_file_name = filename
262
263 if descriptor_file_name.endswith(".json"):
264 error_text = "Invalid json format "
265 indata = json.load(content)
266 else:
267 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200268 indata = yaml.load(content, Loader=yaml.SafeLoader)
tiernob24258a2018-10-04 18:39:49 +0200269
270 current_desc["_admin"]["storage"] = storage
271 current_desc["_admin"]["onboardingState"] = "ONBOARDED"
272 current_desc["_admin"]["operationalState"] = "ENABLED"
273
274 indata = self._remove_envelop(indata)
275
276 # Override descriptor with query string kwargs
277 if kwargs:
278 self._update_input_with_kwargs(indata, kwargs)
279 # it will call overrides method at VnfdTopic or NsdTopic
tierno65ca36d2019-02-12 19:27:52 +0100280 # indata = self._validate_input_edit(indata, force=session["force"])
tiernob24258a2018-10-04 18:39:49 +0200281
282 deep_update_rfc7396(current_desc, indata)
tierno65ca36d2019-02-12 19:27:52 +0100283 self.check_conflict_on_edit(session, current_desc, indata, _id=_id)
tiernob24258a2018-10-04 18:39:49 +0200284 self.db.replace(self.topic, _id, current_desc)
tiernof717cbe2018-12-03 16:35:42 +0000285 self.fs.dir_rename(temp_folder, _id)
tiernob24258a2018-10-04 18:39:49 +0200286
287 indata["_id"] = _id
K Sai Kiranc96fd692019-10-16 17:50:53 +0530288 self._send_msg("edited", indata)
tiernob24258a2018-10-04 18:39:49 +0200289
290 # TODO if descriptor has changed because kwargs update content and remove cached zip
291 # TODO if zip is not present creates one
292 return True
293
294 except EngineException:
295 raise
296 except IndexError:
297 raise EngineException("invalid Content-Range header format. Expected 'bytes start-end/total'",
298 HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
299 except IOError as e:
300 raise EngineException("invalid upload transaction sequence: '{}'".format(e), HTTPStatus.BAD_REQUEST)
301 except tarfile.ReadError as e:
302 raise EngineException("invalid file content {}".format(e), HTTPStatus.BAD_REQUEST)
303 except (ValueError, yaml.YAMLError) as e:
304 raise EngineException(error_text + str(e))
305 except ValidationError as e:
306 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
307 finally:
308 if file_pkg:
309 file_pkg.close()
310
311 def get_file(self, session, _id, path=None, accept_header=None):
312 """
313 Return the file content of a vnfd or nsd
tierno65ca36d2019-02-12 19:27:52 +0100314 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tierno87006042018-10-24 12:50:20 +0200315 :param _id: Identity of the vnfd, nsd
tiernob24258a2018-10-04 18:39:49 +0200316 :param path: artifact path or "$DESCRIPTOR" or None
317 :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
tierno87006042018-10-24 12:50:20 +0200318 :return: opened file plus Accept format or raises an exception
tiernob24258a2018-10-04 18:39:49 +0200319 """
320 accept_text = accept_zip = False
321 if accept_header:
322 if 'text/plain' in accept_header or '*/*' in accept_header:
323 accept_text = True
324 if 'application/zip' in accept_header or '*/*' in accept_header:
tierno87006042018-10-24 12:50:20 +0200325 accept_zip = 'application/zip'
326 elif 'application/gzip' in accept_header:
327 accept_zip = 'application/gzip'
328
tiernob24258a2018-10-04 18:39:49 +0200329 if not accept_text and not accept_zip:
330 raise EngineException("provide request header 'Accept' with 'application/zip' or 'text/plain'",
331 http_code=HTTPStatus.NOT_ACCEPTABLE)
332
333 content = self.show(session, _id)
334 if content["_admin"]["onboardingState"] != "ONBOARDED":
335 raise EngineException("Cannot get content because this resource is not at 'ONBOARDED' state. "
336 "onboardingState is {}".format(content["_admin"]["onboardingState"]),
337 http_code=HTTPStatus.CONFLICT)
338 storage = content["_admin"]["storage"]
339 if path is not None and path != "$DESCRIPTOR": # artifacts
340 if not storage.get('pkg-dir'):
341 raise EngineException("Packages does not contains artifacts", http_code=HTTPStatus.BAD_REQUEST)
342 if self.fs.file_exists((storage['folder'], storage['pkg-dir'], *path), 'dir'):
343 folder_content = self.fs.dir_ls((storage['folder'], storage['pkg-dir'], *path))
344 return folder_content, "text/plain"
345 # TODO manage folders in http
346 else:
347 return self.fs.file_open((storage['folder'], storage['pkg-dir'], *path), "rb"),\
348 "application/octet-stream"
349
350 # pkgtype accept ZIP TEXT -> result
351 # manyfiles yes X -> zip
352 # no yes -> error
353 # onefile yes no -> zip
354 # X yes -> text
355
356 if accept_text and (not storage.get('pkg-dir') or path == "$DESCRIPTOR"):
357 return self.fs.file_open((storage['folder'], storage['descriptor']), "r"), "text/plain"
358 elif storage.get('pkg-dir') and not accept_zip:
359 raise EngineException("Packages that contains several files need to be retrieved with 'application/zip'"
360 "Accept header", http_code=HTTPStatus.NOT_ACCEPTABLE)
361 else:
362 if not storage.get('zipfile'):
363 # TODO generate zipfile if not present
364 raise EngineException("Only allowed 'text/plain' Accept header for this descriptor. To be solved in "
365 "future versions", http_code=HTTPStatus.NOT_ACCEPTABLE)
tierno87006042018-10-24 12:50:20 +0200366 return self.fs.file_open((storage['folder'], storage['zipfile']), "rb"), accept_zip
tiernob24258a2018-10-04 18:39:49 +0200367
gcalvino46e4cb82018-10-26 13:10:22 +0200368 def pyangbind_validation(self, item, data, force=False):
369 try:
370 if item == "vnfds":
371 myvnfd = vnfd_im()
372 pybindJSONDecoder.load_ietf_json({'vnfd:vnfd-catalog': {'vnfd': [data]}}, None, None, obj=myvnfd,
373 path_helper=True, skip_unknown=force)
374 out = pybindJSON.dumps(myvnfd, mode="ietf")
375 elif item == "nsds":
376 mynsd = nsd_im()
377 pybindJSONDecoder.load_ietf_json({'nsd:nsd-catalog': {'nsd': [data]}}, None, None, obj=mynsd,
378 path_helper=True, skip_unknown=force)
379 out = pybindJSON.dumps(mynsd, mode="ietf")
gcalvino70434c12018-11-27 15:17:04 +0100380 elif item == "nsts":
381 mynst = nst_im()
382 pybindJSONDecoder.load_ietf_json({'nst': [data]}, None, None, obj=mynst,
383 path_helper=True, skip_unknown=force)
384 out = pybindJSON.dumps(mynst, mode="ietf")
gcalvino46e4cb82018-10-26 13:10:22 +0200385 else:
386 raise EngineException("Not possible to validate '{}' item".format(item),
387 http_code=HTTPStatus.INTERNAL_SERVER_ERROR)
388
389 desc_out = self._remove_envelop(yaml.safe_load(out))
390 return desc_out
391
392 except Exception as e:
393 raise EngineException("Error in pyangbind validation: {}".format(str(e)),
394 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
395
tiernob24258a2018-10-04 18:39:49 +0200396
397class VnfdTopic(DescriptorTopic):
398 topic = "vnfds"
399 topic_msg = "vnfd"
400
delacruzramo32bab472019-09-13 12:24:22 +0200401 def __init__(self, db, fs, msg, auth):
402 DescriptorTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200403
404 @staticmethod
405 def _remove_envelop(indata=None):
406 if not indata:
407 return {}
408 clean_indata = indata
409 if clean_indata.get('vnfd:vnfd-catalog'):
410 clean_indata = clean_indata['vnfd:vnfd-catalog']
411 elif clean_indata.get('vnfd-catalog'):
412 clean_indata = clean_indata['vnfd-catalog']
413 if clean_indata.get('vnfd'):
414 if not isinstance(clean_indata['vnfd'], list) or len(clean_indata['vnfd']) != 1:
gcalvino46e4cb82018-10-26 13:10:22 +0200415 raise EngineException("'vnfd' must be a list of only one element")
tiernob24258a2018-10-04 18:39:49 +0200416 clean_indata = clean_indata['vnfd'][0]
gcalvino46e4cb82018-10-26 13:10:22 +0200417 elif clean_indata.get('vnfd:vnfd'):
418 if not isinstance(clean_indata['vnfd:vnfd'], list) or len(clean_indata['vnfd:vnfd']) != 1:
419 raise EngineException("'vnfd:vnfd' must be a list of only one element")
420 clean_indata = clean_indata['vnfd:vnfd'][0]
tiernob24258a2018-10-04 18:39:49 +0200421 return clean_indata
422
tierno65ca36d2019-02-12 19:27:52 +0100423 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
424 super().check_conflict_on_edit(session, final_content, edit_content, _id)
tierno36ec8602018-11-02 17:27:11 +0100425
426 # set type of vnfd
427 contains_pdu = False
428 contains_vdu = False
429 for vdu in get_iterable(final_content.get("vdu")):
430 if vdu.get("pdu-type"):
431 contains_pdu = True
432 else:
433 contains_vdu = True
434 if contains_pdu:
435 final_content["_admin"]["type"] = "hnfd" if contains_vdu else "pnfd"
436 elif contains_vdu:
437 final_content["_admin"]["type"] = "vnfd"
438 # if neither vud nor pdu do not fill type
439
tiernob4844ab2019-05-23 08:42:12 +0000440 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200441 """
442 Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
443 that VNFD can be public and be used by NSD of other projects. Also check there are not deployments, or vnfr
444 that uses this vnfd
tierno65ca36d2019-02-12 19:27:52 +0100445 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob4844ab2019-05-23 08:42:12 +0000446 :param _id: vnfd internal id
447 :param db_content: The database content of the _id.
tiernob24258a2018-10-04 18:39:49 +0200448 :return: None or raises EngineException with the conflict
449 """
tierno65ca36d2019-02-12 19:27:52 +0100450 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200451 return
tiernob4844ab2019-05-23 08:42:12 +0000452 descriptor = db_content
tiernob24258a2018-10-04 18:39:49 +0200453 descriptor_id = descriptor.get("id")
454 if not descriptor_id: # empty vnfd not uploaded
455 return
456
tierno65ca36d2019-02-12 19:27:52 +0100457 _filter = self._get_project_filter(session)
tiernob4844ab2019-05-23 08:42:12 +0000458
tiernob24258a2018-10-04 18:39:49 +0200459 # check vnfrs using this vnfd
460 _filter["vnfd-id"] = _id
461 if self.db.get_list("vnfrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000462 raise EngineException("There is at least one VNF using this descriptor", http_code=HTTPStatus.CONFLICT)
463
464 # check NSD referencing this VNFD
tiernob24258a2018-10-04 18:39:49 +0200465 del _filter["vnfd-id"]
tiernob24258a2018-10-04 18:39:49 +0200466 _filter["constituent-vnfd.ANYINDEX.vnfd-id-ref"] = descriptor_id
467 if self.db.get_list("nsds", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000468 raise EngineException("There is at least one NSD referencing this descriptor",
469 http_code=HTTPStatus.CONFLICT)
tiernob24258a2018-10-04 18:39:49 +0200470
gcalvinoa6fe0002019-01-09 13:27:11 +0100471 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino46e4cb82018-10-26 13:10:22 +0200472 indata = self.pyangbind_validation("vnfds", indata, force)
gcalvino5e72d152018-10-23 11:46:57 +0200473 # Cross references validation in the descriptor
gcalvinoe45aded2018-11-13 17:17:28 +0100474 if indata.get("vdu"):
475 if not indata.get("mgmt-interface"):
476 raise EngineException("'mgmt-interface' is a mandatory field and it is not defined",
tierno40fbcad2018-10-26 10:58:15 +0200477 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvinoe45aded2018-11-13 17:17:28 +0100478 if indata["mgmt-interface"].get("cp"):
479 for cp in get_iterable(indata.get("connection-point")):
480 if cp["name"] == indata["mgmt-interface"]["cp"]:
481 break
482 else:
483 raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point"
484 .format(indata["mgmt-interface"]["cp"]),
485 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvino5e72d152018-10-23 11:46:57 +0200486
487 for vdu in get_iterable(indata.get("vdu")):
488 for interface in get_iterable(vdu.get("interface")):
489 if interface.get("external-connection-point-ref"):
490 for cp in get_iterable(indata.get("connection-point")):
tierno40fbcad2018-10-26 10:58:15 +0200491 if cp["name"] == interface["external-connection-point-ref"]:
gcalvino5e72d152018-10-23 11:46:57 +0200492 break
493 else:
tierno40fbcad2018-10-26 10:58:15 +0200494 raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' "
gcalvino5e72d152018-10-23 11:46:57 +0200495 "must match an existing connection-point"
496 .format(vdu["id"], interface["name"],
497 interface["external-connection-point-ref"]),
498 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno40fbcad2018-10-26 10:58:15 +0200499
500 elif interface.get("internal-connection-point-ref"):
gcalvino5e72d152018-10-23 11:46:57 +0200501 for internal_cp in get_iterable(vdu.get("internal-connection-point")):
tierno40fbcad2018-10-26 10:58:15 +0200502 if interface["internal-connection-point-ref"] == internal_cp.get("id"):
gcalvino5e72d152018-10-23 11:46:57 +0200503 break
504 else:
tierno40fbcad2018-10-26 10:58:15 +0200505 raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' "
506 "must match an existing vdu:internal-connection-point"
507 .format(vdu["id"], interface["name"],
508 interface["internal-connection-point-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200509 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
gcalvinoa6fe0002019-01-09 13:27:11 +0100510 # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
511 if vdu.get("vdu-configuration"):
512 if vdu["vdu-configuration"].get("juju"):
513 if not self._validate_package_folders(storage_params, 'charms'):
514 raise EngineException("Charm defined in vnf[id={}]:vdu[id={}] but not present in "
515 "package".format(indata["id"], vdu["id"]))
516 # Validate that if descriptor contains cloud-init, artifacts _admin.storage."pkg-dir" is not none
517 if vdu.get("cloud-init-file"):
518 if not self._validate_package_folders(storage_params, 'cloud_init', vdu["cloud-init-file"]):
519 raise EngineException("Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in "
520 "package".format(indata["id"], vdu["id"]))
521 # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
522 if indata.get("vnf-configuration"):
523 if indata["vnf-configuration"].get("juju"):
524 if not self._validate_package_folders(storage_params, 'charms'):
525 raise EngineException("Charm defined in vnf[id={}] but not present in "
526 "package".format(indata["id"]))
delacruzramo5727a372019-03-28 12:29:04 +0100527 vld_names = [] # For detection of duplicated VLD names
gcalvino5e72d152018-10-23 11:46:57 +0200528 for ivld in get_iterable(indata.get("internal-vld")):
delacruzramo5727a372019-03-28 12:29:04 +0100529 # BEGIN Detection of duplicated VLD names
530 ivld_name = ivld["name"]
531 if ivld_name in vld_names:
532 raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]"
533 .format(ivld["name"], indata["id"], ivld["id"]),
534 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
535 else:
536 vld_names.append(ivld_name)
537 # END Detection of duplicated VLD names
gcalvino5e72d152018-10-23 11:46:57 +0200538 for icp in get_iterable(ivld.get("internal-connection-point")):
539 icp_mark = False
540 for vdu in get_iterable(indata.get("vdu")):
541 for internal_cp in get_iterable(vdu.get("internal-connection-point")):
542 if icp["id-ref"] == internal_cp["id"]:
543 icp_mark = True
544 break
545 if icp_mark:
546 break
547 else:
tierno40fbcad2018-10-26 10:58:15 +0200548 raise EngineException("internal-vld[id='{}']:internal-connection-point='{}' must match an existing "
549 "vdu:internal-connection-point".format(ivld["id"], icp["id-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200550 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
551 if ivld.get("ip-profile-ref"):
tierno40fbcad2018-10-26 10:58:15 +0200552 for ip_prof in get_iterable(indata.get("ip-profiles")):
gcalvino5e72d152018-10-23 11:46:57 +0200553 if ip_prof["name"] == get_iterable(ivld.get("ip-profile-ref")):
554 break
555 else:
tierno40fbcad2018-10-26 10:58:15 +0200556 raise EngineException("internal-vld[id='{}']:ip-profile-ref='{}' does not exist".format(
gcalvino5e72d152018-10-23 11:46:57 +0200557 ivld["id"], ivld["ip-profile-ref"]),
558 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
559 for mp in get_iterable(indata.get("monitoring-param")):
560 if mp.get("vdu-monitoring-param"):
561 mp_vmp_mark = False
562 for vdu in get_iterable(indata.get("vdu")):
563 for vmp in get_iterable(vdu.get("monitoring-param")):
tierno40fbcad2018-10-26 10:58:15 +0200564 if vmp["id"] == mp["vdu-monitoring-param"].get("vdu-monitoring-param-ref") and vdu["id"] ==\
gcalvino5e72d152018-10-23 11:46:57 +0200565 mp["vdu-monitoring-param"]["vdu-ref"]:
566 mp_vmp_mark = True
567 break
568 if mp_vmp_mark:
569 break
570 else:
571 raise EngineException("monitoring-param:vdu-monitoring-param:vdu-monitoring-param-ref='{}' not "
tierno40fbcad2018-10-26 10:58:15 +0200572 "defined at vdu[id='{}'] or vdu does not exist"
gcalvino5e72d152018-10-23 11:46:57 +0200573 .format(mp["vdu-monitoring-param"]["vdu-monitoring-param-ref"],
574 mp["vdu-monitoring-param"]["vdu-ref"]),
575 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
576 elif mp.get("vdu-metric"):
577 mp_vm_mark = False
578 for vdu in get_iterable(indata.get("vdu")):
579 if vdu.get("vdu-configuration"):
580 for metric in get_iterable(vdu["vdu-configuration"].get("metrics")):
581 if metric["name"] == mp["vdu-metric"]["vdu-metric-name-ref"] and vdu["id"] == \
582 mp["vdu-metric"]["vdu-ref"]:
583 mp_vm_mark = True
584 break
585 if mp_vm_mark:
586 break
587 else:
tierno40fbcad2018-10-26 10:58:15 +0200588 raise EngineException("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined at "
589 "vdu[id='{}'] or vdu does not exist"
gcalvino5e72d152018-10-23 11:46:57 +0200590 .format(mp["vdu-metric"]["vdu-metric-name-ref"],
591 mp["vdu-metric"]["vdu-ref"]),
592 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
593
594 for sgd in get_iterable(indata.get("scaling-group-descriptor")):
595 for sp in get_iterable(sgd.get("scaling-policy")):
596 for sc in get_iterable(sp.get("scaling-criteria")):
597 for mp in get_iterable(indata.get("monitoring-param")):
598 if mp["id"] == get_iterable(sc.get("vnf-monitoring-param-ref")):
599 break
600 else:
tierno40fbcad2018-10-26 10:58:15 +0200601 raise EngineException("scaling-group-descriptor[name='{}']:scaling-criteria[name='{}']:"
602 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param"
603 .format(sgd["name"], sc["name"], sc["vnf-monitoring-param-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200604 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
605 for sgd_vdu in get_iterable(sgd.get("vdu")):
606 sgd_vdu_mark = False
607 for vdu in get_iterable(indata.get("vdu")):
608 if vdu["id"] == sgd_vdu["vdu-id-ref"]:
609 sgd_vdu_mark = True
610 break
611 if sgd_vdu_mark:
612 break
613 else:
tierno40fbcad2018-10-26 10:58:15 +0200614 raise EngineException("scaling-group-descriptor[name='{}']:vdu-id-ref={} does not match any vdu"
615 .format(sgd["name"], sgd_vdu["vdu-id-ref"]),
gcalvino5e72d152018-10-23 11:46:57 +0200616 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
617 for sca in get_iterable(sgd.get("scaling-config-action")):
tierno40fbcad2018-10-26 10:58:15 +0200618 if not indata.get("vnf-configuration"):
619 raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced by "
620 "scaling-group-descriptor[name='{}']:scaling-config-action"
621 .format(sgd["name"]),
gcalvino5e72d152018-10-23 11:46:57 +0200622 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno40fbcad2018-10-26 10:58:15 +0200623 for primitive in get_iterable(indata["vnf-configuration"].get("config-primitive")):
624 if primitive["name"] == sca["vnf-config-primitive-name-ref"]:
625 break
626 else:
627 raise EngineException("scaling-group-descriptor[name='{}']:scaling-config-action:vnf-config-"
628 "primitive-name-ref='{}' does not match any "
629 "vnf-configuration:config-primitive:name"
630 .format(sgd["name"], sca["vnf-config-primitive-name-ref"]),
631 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200632 return indata
633
634 def _validate_input_edit(self, indata, force=False):
tiernoaa1ca7b2018-11-08 19:00:20 +0100635 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
tiernob24258a2018-10-04 18:39:49 +0200636 return indata
637
gcalvinoa6fe0002019-01-09 13:27:11 +0100638 def _validate_package_folders(self, storage_params, folder, file=None):
639 if not storage_params or not storage_params.get("pkg-dir"):
640 return False
641 else:
642 if self.fs.file_exists("{}_".format(storage_params["folder"]), 'dir'):
643 f = "{}_/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder)
644 else:
645 f = "{}/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder)
646 if file:
647 return self.fs.file_exists("{}/{}".format(f, file), 'file')
648 else:
649 if self.fs.file_exists(f, 'dir'):
650 if self.fs.dir_ls(f):
651 return True
652 return False
653
tiernob24258a2018-10-04 18:39:49 +0200654
655class NsdTopic(DescriptorTopic):
656 topic = "nsds"
657 topic_msg = "nsd"
658
delacruzramo32bab472019-09-13 12:24:22 +0200659 def __init__(self, db, fs, msg, auth):
660 DescriptorTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200661
662 @staticmethod
663 def _remove_envelop(indata=None):
664 if not indata:
665 return {}
666 clean_indata = indata
667
668 if clean_indata.get('nsd:nsd-catalog'):
669 clean_indata = clean_indata['nsd:nsd-catalog']
670 elif clean_indata.get('nsd-catalog'):
671 clean_indata = clean_indata['nsd-catalog']
672 if clean_indata.get('nsd'):
673 if not isinstance(clean_indata['nsd'], list) or len(clean_indata['nsd']) != 1:
gcalvino46e4cb82018-10-26 13:10:22 +0200674 raise EngineException("'nsd' must be a list of only one element")
tiernob24258a2018-10-04 18:39:49 +0200675 clean_indata = clean_indata['nsd'][0]
gcalvino46e4cb82018-10-26 13:10:22 +0200676 elif clean_indata.get('nsd:nsd'):
677 if not isinstance(clean_indata['nsd:nsd'], list) or len(clean_indata['nsd:nsd']) != 1:
678 raise EngineException("'nsd:nsd' must be a list of only one element")
679 clean_indata = clean_indata['nsd:nsd'][0]
tiernob24258a2018-10-04 18:39:49 +0200680 return clean_indata
681
gcalvinoa6fe0002019-01-09 13:27:11 +0100682 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino46e4cb82018-10-26 13:10:22 +0200683 indata = self.pyangbind_validation("nsds", indata, force)
tierno5a5c2182018-11-20 12:27:42 +0000684 # Cross references validation in the descriptor
tiernoaa1ca7b2018-11-08 19:00:20 +0100685 # 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 +0000686 for vld in get_iterable(indata.get("vld")):
vijay.r8ba4e052019-03-29 17:52:26 +0530687 if vld.get("mgmt-network") and vld.get("ip-profile-ref"):
688 raise EngineException("Error at vld[id='{}']:ip-profile-ref"
689 " You cannot set an ip-profile when mgmt-network is True"
690 .format(vld["id"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tierno5a5c2182018-11-20 12:27:42 +0000691 for vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
692 for constituent_vnfd in get_iterable(indata.get("constituent-vnfd")):
693 if vnfd_cp["member-vnf-index-ref"] == constituent_vnfd["member-vnf-index"]:
694 if vnfd_cp.get("vnfd-id-ref") and vnfd_cp["vnfd-id-ref"] != constituent_vnfd["vnfd-id-ref"]:
695 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[vnfd-id-ref='{}'] "
696 "does not match constituent-vnfd[member-vnf-index='{}']:vnfd-id-ref"
697 " '{}'".format(vld["id"], vnfd_cp["vnfd-id-ref"],
698 constituent_vnfd["member-vnf-index"],
699 constituent_vnfd["vnfd-id-ref"]),
700 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
701 break
702 else:
703 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
704 "does not match any constituent-vnfd:member-vnf-index"
705 .format(vld["id"], vnfd_cp["member-vnf-index-ref"]),
706 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
delacruzramo6a92a532019-11-22 15:06:43 +0100707 # Check VNFFGD
708 for fgd in get_iterable(indata.get("vnffgd")):
709 for cls in get_iterable(fgd.get("classifier")):
710 rspref = cls.get("rsp-id-ref")
711 for rsp in get_iterable(fgd.get("rsp")):
712 rspid = rsp.get("id")
713 if rspid and rspref and rspid == rspref:
714 break
715 else:
716 raise EngineException(
717 "Error at vnffgd[id='{}']:classifier[id='{}']:rsp-id-ref '{}' does not match any rsp:id"
718 .format(fgd["id"], cls["id"], rspref),
719 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200720 return indata
721
722 def _validate_input_edit(self, indata, force=False):
tiernoaa1ca7b2018-11-08 19:00:20 +0100723 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
tiernob24258a2018-10-04 18:39:49 +0200724 return indata
725
tierno65ca36d2019-02-12 19:27:52 +0100726 def _check_descriptor_dependencies(self, session, descriptor):
tiernob24258a2018-10-04 18:39:49 +0200727 """
tierno5a5c2182018-11-20 12:27:42 +0000728 Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
729 connection points are ok
tierno65ca36d2019-02-12 19:27:52 +0100730 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200731 :param descriptor: descriptor to be inserted or edit
732 :return: None or raises exception
733 """
tierno65ca36d2019-02-12 19:27:52 +0100734 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200735 return
tierno5a5c2182018-11-20 12:27:42 +0000736 member_vnfd_index = {}
tierno65ca36d2019-02-12 19:27:52 +0100737 if descriptor.get("constituent-vnfd") and not session["force"]:
tierno5a5c2182018-11-20 12:27:42 +0000738 for vnf in descriptor["constituent-vnfd"]:
739 vnfd_id = vnf["vnfd-id-ref"]
tierno65ca36d2019-02-12 19:27:52 +0100740 filter_q = self._get_project_filter(session)
tierno5a5c2182018-11-20 12:27:42 +0000741 filter_q["id"] = vnfd_id
742 vnf_list = self.db.get_list("vnfds", filter_q)
743 if not vnf_list:
744 raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non "
745 "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT)
746 # elif len(vnf_list) > 1:
747 # raise EngineException("More than one vnfd found for id='{}'".format(vnfd_id),
748 # http_code=HTTPStatus.CONFLICT)
749 member_vnfd_index[vnf["member-vnf-index"]] = vnf_list[0]
750
751 # Cross references validation in the descriptor and vnfd connection point validation
752 for vld in get_iterable(descriptor.get("vld")):
753 for referenced_vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")):
754 # look if this vnfd contains this connection point
755 vnfd = member_vnfd_index.get(referenced_vnfd_cp["member-vnf-index-ref"])
756 if not vnfd:
757 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
758 "does not match any constituent-vnfd:member-vnf-index"
759 .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"]),
760 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
761 for vnfd_cp in get_iterable(vnfd.get("connection-point")):
762 if referenced_vnfd_cp.get("vnfd-connection-point-ref") == vnfd_cp["name"]:
763 break
764 else:
765 raise EngineException(
766 "Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}']:vnfd-"
767 "connection-point-ref='{}' references a non existing conection-point:name inside vnfd '{}'"
768 .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"],
769 referenced_vnfd_cp["vnfd-connection-point-ref"], vnfd["id"]),
770 http_code=HTTPStatus.UNPROCESSABLE_ENTITY)
tiernob24258a2018-10-04 18:39:49 +0200771
tierno65ca36d2019-02-12 19:27:52 +0100772 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
773 super().check_conflict_on_edit(session, final_content, edit_content, _id)
tiernob24258a2018-10-04 18:39:49 +0200774
tierno65ca36d2019-02-12 19:27:52 +0100775 self._check_descriptor_dependencies(session, final_content)
tiernob24258a2018-10-04 18:39:49 +0200776
tiernob4844ab2019-05-23 08:42:12 +0000777 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200778 """
779 Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note
780 that NSD can be public and be used by other projects.
tierno65ca36d2019-02-12 19:27:52 +0100781 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob4844ab2019-05-23 08:42:12 +0000782 :param _id: nsd internal id
783 :param db_content: The database content of the _id
tiernob24258a2018-10-04 18:39:49 +0200784 :return: None or raises EngineException with the conflict
785 """
tierno65ca36d2019-02-12 19:27:52 +0100786 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200787 return
tiernob4844ab2019-05-23 08:42:12 +0000788 descriptor = db_content
789 descriptor_id = descriptor.get("id")
790 if not descriptor_id: # empty nsd not uploaded
791 return
792
793 # check NSD used by NS
tierno65ca36d2019-02-12 19:27:52 +0100794 _filter = self._get_project_filter(session)
tiernob4844ab2019-05-23 08:42:12 +0000795 _filter["nsd-id"] = _id
tiernob24258a2018-10-04 18:39:49 +0200796 if self.db.get_list("nsrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000797 raise EngineException("There is at least one NS using this descriptor", http_code=HTTPStatus.CONFLICT)
798
799 # check NSD referenced by NST
800 del _filter["nsd-id"]
801 _filter["netslice-subnet.ANYINDEX.nsd-ref"] = descriptor_id
802 if self.db.get_list("nsts", _filter):
803 raise EngineException("There is at least one NetSlice Template referencing this descriptor",
804 http_code=HTTPStatus.CONFLICT)
tiernob24258a2018-10-04 18:39:49 +0200805
806
Felipe Vicensb57758d2018-10-16 16:00:20 +0200807class NstTopic(DescriptorTopic):
808 topic = "nsts"
809 topic_msg = "nst"
810
delacruzramo32bab472019-09-13 12:24:22 +0200811 def __init__(self, db, fs, msg, auth):
812 DescriptorTopic.__init__(self, db, fs, msg, auth)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200813
814 @staticmethod
815 def _remove_envelop(indata=None):
816 if not indata:
817 return {}
818 clean_indata = indata
819
Felipe Vicensb57758d2018-10-16 16:00:20 +0200820 if clean_indata.get('nst'):
821 if not isinstance(clean_indata['nst'], list) or len(clean_indata['nst']) != 1:
822 raise EngineException("'nst' must be a list only one element")
823 clean_indata = clean_indata['nst'][0]
gcalvino70434c12018-11-27 15:17:04 +0100824 elif clean_indata.get('nst:nst'):
825 if not isinstance(clean_indata['nst:nst'], list) or len(clean_indata['nst:nst']) != 1:
826 raise EngineException("'nst:nst' must be a list only one element")
827 clean_indata = clean_indata['nst:nst'][0]
Felipe Vicensb57758d2018-10-16 16:00:20 +0200828 return clean_indata
829
Felipe Vicensb57758d2018-10-16 16:00:20 +0200830 def _validate_input_edit(self, indata, force=False):
831 # TODO validate with pyangbind, serialize
832 return indata
833
gcalvinoa6fe0002019-01-09 13:27:11 +0100834 def _validate_input_new(self, indata, storage_params, force=False):
gcalvino70434c12018-11-27 15:17:04 +0100835 indata = self.pyangbind_validation("nsts", indata, force)
Felipe Vicense36ab852018-11-23 14:12:09 +0100836 return indata.copy()
837
Felipe Vicensb57758d2018-10-16 16:00:20 +0200838 def _check_descriptor_dependencies(self, session, descriptor):
839 """
840 Check that the dependent descriptors exist on a new descriptor or edition
tierno65ca36d2019-02-12 19:27:52 +0100841 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Felipe Vicensb57758d2018-10-16 16:00:20 +0200842 :param descriptor: descriptor to be inserted or edit
843 :return: None or raises exception
844 """
845 if not descriptor.get("netslice-subnet"):
846 return
847 for nsd in descriptor["netslice-subnet"]:
848 nsd_id = nsd["nsd-ref"]
tierno65ca36d2019-02-12 19:27:52 +0100849 filter_q = self._get_project_filter(session)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200850 filter_q["id"] = nsd_id
851 if not self.db.get_list("nsds", filter_q):
852 raise EngineException("Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non "
853 "existing nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT)
854
tierno65ca36d2019-02-12 19:27:52 +0100855 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
856 super().check_conflict_on_edit(session, final_content, edit_content, _id)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200857
858 self._check_descriptor_dependencies(session, final_content)
859
tiernob4844ab2019-05-23 08:42:12 +0000860 def check_conflict_on_del(self, session, _id, db_content):
Felipe Vicensb57758d2018-10-16 16:00:20 +0200861 """
862 Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note
863 that NST can be public and be used by other projects.
tierno65ca36d2019-02-12 19:27:52 +0100864 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Felipe Vicens07f31722018-10-29 15:16:44 +0100865 :param _id: nst internal id
tiernob4844ab2019-05-23 08:42:12 +0000866 :param db_content: The database content of the _id.
Felipe Vicensb57758d2018-10-16 16:00:20 +0200867 :return: None or raises EngineException with the conflict
868 """
869 # TODO: Check this method
tierno65ca36d2019-02-12 19:27:52 +0100870 if session["force"]:
Felipe Vicensb57758d2018-10-16 16:00:20 +0200871 return
Felipe Vicens07f31722018-10-29 15:16:44 +0100872 # Get Network Slice Template from Database
tierno65ca36d2019-02-12 19:27:52 +0100873 _filter = self._get_project_filter(session)
tiernoea97c042019-09-13 09:44:42 +0000874 _filter["_admin.nst-id"] = _id
tiernob4844ab2019-05-23 08:42:12 +0000875 if self.db.get_list("nsis", _filter):
876 raise EngineException("there is at least one Netslice Instance using this descriptor",
877 http_code=HTTPStatus.CONFLICT)
Felipe Vicensb57758d2018-10-16 16:00:20 +0200878
879
tiernob24258a2018-10-04 18:39:49 +0200880class PduTopic(BaseTopic):
881 topic = "pdus"
882 topic_msg = "pdu"
883 schema_new = pdu_new_schema
884 schema_edit = pdu_edit_schema
885
delacruzramo32bab472019-09-13 12:24:22 +0200886 def __init__(self, db, fs, msg, auth):
887 BaseTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200888
889 @staticmethod
890 def format_on_new(content, project_id=None, make_public=False):
tierno36ec8602018-11-02 17:27:11 +0100891 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
tiernob24258a2018-10-04 18:39:49 +0200892 content["_admin"]["onboardingState"] = "CREATED"
tierno36ec8602018-11-02 17:27:11 +0100893 content["_admin"]["operationalState"] = "ENABLED"
894 content["_admin"]["usageState"] = "NOT_IN_USE"
tiernob24258a2018-10-04 18:39:49 +0200895
tiernob4844ab2019-05-23 08:42:12 +0000896 def check_conflict_on_del(self, session, _id, db_content):
897 """
898 Check that there is not any vnfr that uses this PDU
899 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
900 :param _id: pdu internal id
901 :param db_content: The database content of the _id.
902 :return: None or raises EngineException with the conflict
903 """
tierno65ca36d2019-02-12 19:27:52 +0100904 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200905 return
tiernob4844ab2019-05-23 08:42:12 +0000906
907 _filter = self._get_project_filter(session)
908 _filter["vdur.pdu-id"] = _id
tiernob24258a2018-10-04 18:39:49 +0200909 if self.db.get_list("vnfrs", _filter):
tiernob4844ab2019-05-23 08:42:12 +0000910 raise EngineException("There is at least one VNF using this PDU", http_code=HTTPStatus.CONFLICT)