1 # -*- coding: utf-8 -*-
3 # 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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
21 from hashlib
import md5
22 from osm_common
.dbbase
import DbException
, deep_update_rfc7396
23 from http
import HTTPStatus
25 from uuid
import uuid4
26 from re
import fullmatch
27 from osm_nbi
.validation
import ValidationError
, pdu_new_schema
, pdu_edit_schema
, \
28 validate_input
, vnfpkgop_new_schema
29 from osm_nbi
.base_topic
import BaseTopic
, EngineException
, get_iterable
30 etsi_nfv_vnfd
= importlib
.import_module("osm_im.etsi-nfv-vnfd")
31 etsi_nfv_nsd
= importlib
.import_module("osm_im.etsi-nfv-nsd")
32 from osm_im
.nst
import nst
as nst_im
33 from pyangbind
.lib
.serialise
import pybindJSONDecoder
34 import pyangbind
.lib
.pybindJSON
as pybindJSON
36 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
39 class DescriptorTopic(BaseTopic
):
41 def __init__(self
, db
, fs
, msg
, auth
):
42 BaseTopic
.__init
__(self
, db
, fs
, msg
, auth
)
44 def check_conflict_on_edit(self
, session
, final_content
, edit_content
, _id
):
45 super().check_conflict_on_edit(session
, final_content
, edit_content
, _id
)
47 def _check_unique_id_name(descriptor
, position
=""):
48 for desc_key
, desc_item
in descriptor
.items():
49 if isinstance(desc_item
, list) and desc_item
:
52 for index
, list_item
in enumerate(desc_item
):
53 if isinstance(list_item
, dict):
54 _check_unique_id_name(list_item
, "{}.{}[{}]"
55 .format(position
, desc_key
, index
))
57 if index
== 0 and (list_item
.get("id") or list_item
.get("name")):
58 desc_item_id
= "id" if list_item
.get("id") else "name"
59 if desc_item_id
and list_item
.get(desc_item_id
):
60 if list_item
[desc_item_id
] in used_ids
:
61 position
= "{}.{}[{}]".format(position
, desc_key
, index
)
62 raise EngineException("Error: identifier {} '{}' is not unique and repeats at '{}'"
63 .format(desc_item_id
, list_item
[desc_item_id
],
64 position
), HTTPStatus
.UNPROCESSABLE_ENTITY
)
65 used_ids
.append(list_item
[desc_item_id
])
67 _check_unique_id_name(final_content
)
68 # 1. validate again with pyangbind
69 # 1.1. remove internal keys
71 for k
in ("_id", "_admin"):
72 if k
in final_content
:
73 internal_keys
[k
] = final_content
.pop(k
)
74 storage_params
= internal_keys
["_admin"].get("storage")
75 serialized
= self
._validate
_input
_new
(final_content
, storage_params
, session
["force"])
76 # 1.2. modify final_content with a serialized version
78 final_content
.update(serialized
)
79 # 1.3. restore internal keys
80 for k
, v
in internal_keys
.items():
84 # 2. check that this id is not present
85 if "id" in edit_content
:
86 _filter
= self
._get
_project
_filter
(session
)
87 _filter
["id"] = final_content
["id"]
88 _filter
["_id.neq"] = _id
89 if self
.db
.get_one(self
.topic
, _filter
, fail_on_empty
=False):
90 raise EngineException("{} with id '{}' already exists for this project".format(self
.topic
[:-1],
95 def format_on_new(content
, project_id
=None, make_public
=False):
96 BaseTopic
.format_on_new(content
, project_id
=project_id
, make_public
=make_public
)
97 content
["_admin"]["onboardingState"] = "CREATED"
98 content
["_admin"]["operationalState"] = "DISABLED"
99 content
["_admin"]["usageState"] = "NOT_IN_USE"
101 def delete_extra(self
, session
, _id
, db_content
, not_send_msg
=None):
103 Deletes file system storage associated with the descriptor
104 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
105 :param _id: server internal id
106 :param db_content: The database content of the descriptor
107 :param not_send_msg: To not send message (False) or store content (list) instead
108 :return: None if ok or raises EngineException with the problem
110 self
.fs
.file_delete(_id
, ignore_non_exist
=True)
111 self
.fs
.file_delete(_id
+ "_", ignore_non_exist
=True) # remove temp folder
114 def get_one_by_id(db
, session
, topic
, id):
115 # find owned by this project
116 _filter
= BaseTopic
._get
_project
_filter
(session
)
118 desc_list
= db
.get_list(topic
, _filter
)
119 if len(desc_list
) == 1:
121 elif len(desc_list
) > 1:
122 raise DbException("Found more than one {} with id='{}' belonging to this project".format(topic
[:-1], id),
125 # not found any: try to find public
126 _filter
= BaseTopic
._get
_project
_filter
(session
)
128 desc_list
= db
.get_list(topic
, _filter
)
130 raise DbException("Not found any {} with id='{}'".format(topic
[:-1], id), HTTPStatus
.NOT_FOUND
)
131 elif len(desc_list
) == 1:
134 raise DbException("Found more than one public {} with id='{}'; and no one belonging to this project".format(
135 topic
[:-1], id), HTTPStatus
.CONFLICT
)
137 def new(self
, rollback
, session
, indata
=None, kwargs
=None, headers
=None):
139 Creates a new almost empty DISABLED entry into database. Due to SOL005, it does not follow normal procedure.
140 Creating a VNFD or NSD is done in two steps: 1. Creates an empty descriptor (this step) and 2) upload content
141 (self.upload_content)
142 :param rollback: list to append created items at database in case a rollback may to be done
143 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
144 :param indata: data to be inserted
145 :param kwargs: used to override the indata descriptor
146 :param headers: http request headers
147 :return: _id, None: identity of the inserted data; and None as there is not any operation
150 # No needed to capture exceptions
152 self
.check_quota(session
)
156 if "userDefinedData" in indata
:
157 indata
= indata
['userDefinedData']
159 # Override descriptor with query string kwargs
160 self
._update
_input
_with
_kwargs
(indata
, kwargs
)
161 # uncomment when this method is implemented.
162 # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors
163 # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"])
165 content
= {"_admin": {"userDefinedData": indata
}}
166 self
.format_on_new(content
, session
["project_id"], make_public
=session
["public"])
167 _id
= self
.db
.create(self
.topic
, content
)
168 rollback
.append({"topic": self
.topic
, "_id": _id
})
169 self
._send
_msg
("created", {"_id": _id
})
172 def upload_content(self
, session
, _id
, indata
, kwargs
, headers
):
174 Used for receiving content by chunks (with a transaction_id header and/or gzip file. It will store and extract)
175 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
176 :param _id : the nsd,vnfd is already created, this is the id
177 :param indata: http body request
178 :param kwargs: user query string to override parameters. NOT USED
179 :param headers: http request headers
180 :return: True if package is completely uploaded or False if partial content has been uploded
181 Raise exception on error
183 # Check that _id exists and it is valid
184 current_desc
= self
.show(session
, _id
)
186 content_range_text
= headers
.get("Content-Range")
187 expected_md5
= headers
.get("Content-File-MD5")
189 content_type
= headers
.get("Content-Type")
190 if content_type
and "application/gzip" in content_type
or "application/x-gzip" in content_type
or \
191 "application/zip" in content_type
:
193 filename
= headers
.get("Content-Filename")
195 filename
= "package.tar.gz" if compressed
else "package"
196 # TODO change to Content-Disposition filename https://tools.ietf.org/html/rfc6266
200 if content_range_text
:
201 content_range
= content_range_text
.replace("-", " ").replace("/", " ").split()
202 if content_range
[0] != "bytes": # TODO check x<y not negative < total....
204 start
= int(content_range
[1])
205 end
= int(content_range
[2]) + 1
206 total
= int(content_range
[3])
209 temp_folder
= _id
+ "_" # all the content is upload here and if ok, it is rename from id_ to is folder
212 if not self
.fs
.file_exists(temp_folder
, 'dir'):
213 raise EngineException("invalid Transaction-Id header", HTTPStatus
.NOT_FOUND
)
215 self
.fs
.file_delete(temp_folder
, ignore_non_exist
=True)
216 self
.fs
.mkdir(temp_folder
)
218 storage
= self
.fs
.get_params()
219 storage
["folder"] = _id
221 file_path
= (temp_folder
, filename
)
222 if self
.fs
.file_exists(file_path
, 'file'):
223 file_size
= self
.fs
.file_size(file_path
)
226 if file_size
!= start
:
227 raise EngineException("invalid Content-Range start sequence, expected '{}' but received '{}'".format(
228 file_size
, start
), HTTPStatus
.REQUESTED_RANGE_NOT_SATISFIABLE
)
229 file_pkg
= self
.fs
.file_open(file_path
, 'a+b')
230 if isinstance(indata
, dict):
231 indata_text
= yaml
.safe_dump(indata
, indent
=4, default_flow_style
=False)
232 file_pkg
.write(indata_text
.encode(encoding
="utf-8"))
236 indata_text
= indata
.read(4096)
237 indata_len
+= len(indata_text
)
240 file_pkg
.write(indata_text
)
241 if content_range_text
:
242 if indata_len
!= end
- start
:
243 raise EngineException("Mismatch between Content-Range header {}-{} and body length of {}".format(
244 start
, end
- 1, indata_len
), HTTPStatus
.REQUESTED_RANGE_NOT_SATISFIABLE
)
246 # TODO update to UPLOADING
253 chunk_data
= file_pkg
.read(1024)
255 file_md5
.update(chunk_data
)
256 chunk_data
= file_pkg
.read(1024)
257 if expected_md5
!= file_md5
.hexdigest():
258 raise EngineException("Error, MD5 mismatch", HTTPStatus
.CONFLICT
)
260 if compressed
== "gzip":
261 tar
= tarfile
.open(mode
='r', fileobj
=file_pkg
)
262 descriptor_file_name
= None
264 tarname
= tarinfo
.name
265 tarname_path
= tarname
.split("/")
266 if not tarname_path
[0] or ".." in tarname_path
: # if start with "/" means absolute path
267 raise EngineException("Absolute path or '..' are not allowed for package descriptor tar.gz")
268 if len(tarname_path
) == 1 and not tarinfo
.isdir():
269 raise EngineException("All files must be inside a dir for package descriptor tar.gz")
270 if tarname
.endswith(".yaml") or tarname
.endswith(".json") or tarname
.endswith(".yml"):
271 storage
["pkg-dir"] = tarname_path
[0]
272 if len(tarname_path
) == 2:
273 if descriptor_file_name
:
274 raise EngineException(
275 "Found more than one descriptor file at package descriptor tar.gz")
276 descriptor_file_name
= tarname
277 if not descriptor_file_name
:
278 raise EngineException("Not found any descriptor file at package descriptor tar.gz")
279 storage
["descriptor"] = descriptor_file_name
280 storage
["zipfile"] = filename
281 self
.fs
.file_extract(tar
, temp_folder
)
282 with self
.fs
.file_open((temp_folder
, descriptor_file_name
), "r") as descriptor_file
:
283 content
= descriptor_file
.read()
285 content
= file_pkg
.read()
286 storage
["descriptor"] = descriptor_file_name
= filename
288 if descriptor_file_name
.endswith(".json"):
289 error_text
= "Invalid json format "
290 indata
= json
.load(content
)
292 error_text
= "Invalid yaml format "
293 indata
= yaml
.load(content
, Loader
=yaml
.SafeLoader
)
295 current_desc
["_admin"]["storage"] = storage
296 current_desc
["_admin"]["onboardingState"] = "ONBOARDED"
297 current_desc
["_admin"]["operationalState"] = "ENABLED"
299 indata
= self
._remove
_envelop
(indata
)
301 # Override descriptor with query string kwargs
303 self
._update
_input
_with
_kwargs
(indata
, kwargs
)
305 deep_update_rfc7396(current_desc
, indata
)
306 self
.check_conflict_on_edit(session
, current_desc
, indata
, _id
=_id
)
307 current_desc
["_admin"]["modified"] = time()
308 self
.db
.replace(self
.topic
, _id
, current_desc
)
309 self
.fs
.dir_rename(temp_folder
, _id
)
312 self
._send
_msg
("edited", indata
)
314 # TODO if descriptor has changed because kwargs update content and remove cached zip
315 # TODO if zip is not present creates one
318 except EngineException
:
321 raise EngineException("invalid Content-Range header format. Expected 'bytes start-end/total'",
322 HTTPStatus
.REQUESTED_RANGE_NOT_SATISFIABLE
)
324 raise EngineException("invalid upload transaction sequence: '{}'".format(e
), HTTPStatus
.BAD_REQUEST
)
325 except tarfile
.ReadError
as e
:
326 raise EngineException("invalid file content {}".format(e
), HTTPStatus
.BAD_REQUEST
)
327 except (ValueError, yaml
.YAMLError
) as e
:
328 raise EngineException(error_text
+ str(e
))
329 except ValidationError
as e
:
330 raise EngineException(e
, HTTPStatus
.UNPROCESSABLE_ENTITY
)
335 def get_file(self
, session
, _id
, path
=None, accept_header
=None):
337 Return the file content of a vnfd or nsd
338 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
339 :param _id: Identity of the vnfd, nsd
340 :param path: artifact path or "$DESCRIPTOR" or None
341 :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
342 :return: opened file plus Accept format or raises an exception
344 accept_text
= accept_zip
= False
346 if 'text/plain' in accept_header
or '*/*' in accept_header
:
348 if 'application/zip' in accept_header
or '*/*' in accept_header
:
349 accept_zip
= 'application/zip'
350 elif 'application/gzip' in accept_header
:
351 accept_zip
= 'application/gzip'
353 if not accept_text
and not accept_zip
:
354 raise EngineException("provide request header 'Accept' with 'application/zip' or 'text/plain'",
355 http_code
=HTTPStatus
.NOT_ACCEPTABLE
)
357 content
= self
.show(session
, _id
)
358 if content
["_admin"]["onboardingState"] != "ONBOARDED":
359 raise EngineException("Cannot get content because this resource is not at 'ONBOARDED' state. "
360 "onboardingState is {}".format(content
["_admin"]["onboardingState"]),
361 http_code
=HTTPStatus
.CONFLICT
)
362 storage
= content
["_admin"]["storage"]
363 if path
is not None and path
!= "$DESCRIPTOR": # artifacts
364 if not storage
.get('pkg-dir'):
365 raise EngineException("Packages does not contains artifacts", http_code
=HTTPStatus
.BAD_REQUEST
)
366 if self
.fs
.file_exists((storage
['folder'], storage
['pkg-dir'], *path
), 'dir'):
367 folder_content
= self
.fs
.dir_ls((storage
['folder'], storage
['pkg-dir'], *path
))
368 return folder_content
, "text/plain"
369 # TODO manage folders in http
371 return self
.fs
.file_open((storage
['folder'], storage
['pkg-dir'], *path
), "rb"), \
372 "application/octet-stream"
374 # pkgtype accept ZIP TEXT -> result
375 # manyfiles yes X -> zip
377 # onefile yes no -> zip
379 contain_many_files
= False
380 if storage
.get('pkg-dir'):
381 # check if there are more than one file in the package, ignoring checksums.txt.
382 pkg_files
= self
.fs
.dir_ls((storage
['folder'], storage
['pkg-dir']))
383 if len(pkg_files
) >= 3 or (len(pkg_files
) == 2 and 'checksums.txt' not in pkg_files
):
384 contain_many_files
= True
385 if accept_text
and (not contain_many_files
or path
== "$DESCRIPTOR"):
386 return self
.fs
.file_open((storage
['folder'], storage
['descriptor']), "r"), "text/plain"
387 elif contain_many_files
and not accept_zip
:
388 raise EngineException("Packages that contains several files need to be retrieved with 'application/zip'"
389 "Accept header", http_code
=HTTPStatus
.NOT_ACCEPTABLE
)
391 if not storage
.get('zipfile'):
392 # TODO generate zipfile if not present
393 raise EngineException("Only allowed 'text/plain' Accept header for this descriptor. To be solved in "
394 "future versions", http_code
=HTTPStatus
.NOT_ACCEPTABLE
)
395 return self
.fs
.file_open((storage
['folder'], storage
['zipfile']), "rb"), accept_zip
397 def _remove_yang_prefixes_from_descriptor(self
, descriptor
):
399 for k
, v
in descriptor
.items():
401 if isinstance(v
, dict):
402 new_v
= self
._remove
_yang
_prefixes
_from
_descriptor
(v
)
403 elif isinstance(v
, list):
406 if isinstance(x
, dict):
407 new_v
.append(self
._remove
_yang
_prefixes
_from
_descriptor
(x
))
410 new_descriptor
[k
.split(':')[-1]] = new_v
411 return new_descriptor
413 def pyangbind_validation(self
, item
, data
, force
=False):
414 raise EngineException("Not possible to validate '{}' item".format(item
),
415 http_code
=HTTPStatus
.INTERNAL_SERVER_ERROR
)
417 def _validate_input_edit(self
, indata
, content
, force
=False):
418 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
421 if "_admin" not in indata
:
422 indata
["_admin"] = {}
424 if "operationalState" in indata
:
425 if indata
["operationalState"] in ("ENABLED", "DISABLED"):
426 indata
["_admin"]["operationalState"] = indata
.pop("operationalState")
428 raise EngineException("State '{}' is not a valid operational state"
429 .format(indata
["operationalState"]),
430 http_code
=HTTPStatus
.BAD_REQUEST
)
432 # In the case of user defined data, we need to put the data in the root of the object
433 # to preserve current expected behaviour
434 if "userDefinedData" in indata
:
435 data
= indata
.pop("userDefinedData")
436 if type(data
) == dict:
437 indata
["_admin"]["userDefinedData"] = data
439 raise EngineException("userDefinedData should be an object, but is '{}' instead"
441 http_code
=HTTPStatus
.BAD_REQUEST
)
443 if ("operationalState" in indata
["_admin"] and
444 content
["_admin"]["operationalState"] == indata
["_admin"]["operationalState"]):
445 raise EngineException("operationalState already {}".format(content
["_admin"]["operationalState"]),
446 http_code
=HTTPStatus
.CONFLICT
)
451 class VnfdTopic(DescriptorTopic
):
455 def __init__(self
, db
, fs
, msg
, auth
):
456 DescriptorTopic
.__init
__(self
, db
, fs
, msg
, auth
)
458 def pyangbind_validation(self
, item
, data
, force
=False):
459 if self
._descriptor
_data
_is
_in
_old
_format
(data
):
460 raise EngineException("ERROR: Unsupported descriptor format. Please, use an ETSI SOL006 descriptor.",
461 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
463 virtual_compute_descriptors
= data
.get('virtual-compute-desc')
464 virtual_storage_descriptors
= data
.get('virtual-storage-desc')
465 myvnfd
= etsi_nfv_vnfd
.etsi_nfv_vnfd()
466 pybindJSONDecoder
.load_ietf_json({'etsi-nfv-vnfd:vnfd': data
}, None, None, obj
=myvnfd
,
467 path_helper
=True, skip_unknown
=force
)
468 out
= pybindJSON
.dumps(myvnfd
, mode
="ietf")
469 desc_out
= self
._remove
_envelop
(yaml
.safe_load(out
))
470 desc_out
= self
._remove
_yang
_prefixes
_from
_descriptor
(desc_out
)
471 if virtual_compute_descriptors
:
472 desc_out
['virtual-compute-desc'] = virtual_compute_descriptors
473 if virtual_storage_descriptors
:
474 desc_out
['virtual-storage-desc'] = virtual_storage_descriptors
476 except Exception as e
:
477 raise EngineException("Error in pyangbind validation: {}".format(str(e
)),
478 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
481 def _descriptor_data_is_in_old_format(data
):
482 return ('vnfd-catalog' in data
) or ('vnfd:vnfd-catalog' in data
)
485 def _remove_envelop(indata
=None):
488 clean_indata
= indata
490 if clean_indata
.get('etsi-nfv-vnfd:vnfd'):
491 if not isinstance(clean_indata
['etsi-nfv-vnfd:vnfd'], dict):
492 raise EngineException("'etsi-nfv-vnfd:vnfd' must be a dict")
493 clean_indata
= clean_indata
['etsi-nfv-vnfd:vnfd']
494 elif clean_indata
.get('vnfd'):
495 if not isinstance(clean_indata
['vnfd'], dict):
496 raise EngineException("'vnfd' must be dict")
497 clean_indata
= clean_indata
['vnfd']
501 def check_conflict_on_edit(self
, session
, final_content
, edit_content
, _id
):
502 super().check_conflict_on_edit(session
, final_content
, edit_content
, _id
)
507 for vdu
in get_iterable(final_content
.get("vdu")):
508 if vdu
.get("pdu-type"):
513 final_content
["_admin"]["type"] = "hnfd" if contains_vdu
else "pnfd"
515 final_content
["_admin"]["type"] = "vnfd"
516 # if neither vud nor pdu do not fill type
518 def check_conflict_on_del(self
, session
, _id
, db_content
):
520 Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
521 that VNFD can be public and be used by NSD of other projects. Also check there are not deployments, or vnfr
523 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
524 :param _id: vnfd internal id
525 :param db_content: The database content of the _id.
526 :return: None or raises EngineException with the conflict
530 descriptor
= db_content
531 descriptor_id
= descriptor
.get("id")
532 if not descriptor_id
: # empty vnfd not uploaded
535 _filter
= self
._get
_project
_filter
(session
)
537 # check vnfrs using this vnfd
538 _filter
["vnfd-id"] = _id
539 if self
.db
.get_list("vnfrs", _filter
):
540 raise EngineException("There is at least one VNF using this descriptor", http_code
=HTTPStatus
.CONFLICT
)
542 # check NSD referencing this VNFD
543 del _filter
["vnfd-id"]
544 _filter
["constituent-vnfd.ANYINDEX.vnfd-id-ref"] = descriptor_id
545 if self
.db
.get_list("nsds", _filter
):
546 raise EngineException("There is at least one NSD referencing this descriptor",
547 http_code
=HTTPStatus
.CONFLICT
)
549 def _validate_input_new(self
, indata
, storage_params
, force
=False):
550 indata
.pop("onboardingState", None)
551 indata
.pop("operationalState", None)
552 indata
.pop("usageState", None)
553 indata
.pop("links", None)
555 indata
= self
.pyangbind_validation("vnfds", indata
, force
)
556 # Cross references validation in the descriptor
558 self
.validate_mgmt_interface_connection_point(indata
)
560 for vdu
in get_iterable(indata
.get("vdu")):
561 self
.validate_vdu_internal_connection_points(vdu
)
562 self
._validate
_vdu
_charms
_in
_package
(storage_params
, vdu
, indata
)
563 self
._validate
_vdu
_cloud
_init
_in
_package
(storage_params
, vdu
, indata
)
565 self
._validate
_vnf
_charms
_in
_package
(storage_params
, indata
)
567 self
.validate_external_connection_points(indata
)
568 self
.validate_internal_virtual_links(indata
)
569 self
.validate_monitoring_params(indata
)
570 self
.validate_scaling_group_descriptor(indata
)
575 def validate_mgmt_interface_connection_point(indata
):
576 if not indata
.get("vdu"):
578 if not indata
.get("mgmt-cp"):
579 raise EngineException("'mgmt-cp' is a mandatory field and it is not defined",
580 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
582 for cp
in get_iterable(indata
.get("ext-cpd")):
583 if cp
["id"] == indata
["mgmt-cp"]:
586 raise EngineException("mgmt-cp='{}' must match an existing ext-cpd".format(indata
["mgmt-cp"]),
587 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
590 def validate_vdu_internal_connection_points(vdu
):
592 for cpd
in get_iterable(vdu
.get("int-cpd")):
593 cpd_id
= cpd
.get("id")
594 if cpd_id
and cpd_id
in int_cpds
:
595 raise EngineException("vdu[id='{}']:int-cpd[id='{}'] is already used by other int-cpd"
596 .format(vdu
["id"], cpd_id
),
597 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
601 def validate_external_connection_points(indata
):
602 all_vdus_int_cpds
= set()
603 for vdu
in get_iterable(indata
.get("vdu")):
604 for int_cpd
in get_iterable(vdu
.get("int-cpd")):
605 all_vdus_int_cpds
.add((vdu
.get("id"), int_cpd
.get("id")))
608 for cpd
in get_iterable(indata
.get("ext-cpd")):
609 cpd_id
= cpd
.get("id")
610 if cpd_id
and cpd_id
in ext_cpds
:
611 raise EngineException("ext-cpd[id='{}'] is already used by other ext-cpd".format(cpd_id
),
612 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
615 int_cpd
= cpd
.get("int-cpd")
617 if (int_cpd
.get("vdu-id"), int_cpd
.get("cpd")) not in all_vdus_int_cpds
:
618 raise EngineException("ext-cpd[id='{}']:int-cpd must match an existing vdu int-cpd".format(cpd_id
),
619 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
620 # TODO: Validate k8s-cluster-net points to a valid k8s-cluster:nets ?
622 def _validate_vdu_charms_in_package(self
, storage_params
, vdu
, indata
):
623 if not vdu
.get("vdu-configuration"):
625 for vdu_configuration
in get_iterable(indata
.get("vdu-configuration")):
626 if vdu_configuration
.get("juju"):
627 if not self
._validate
_package
_folders
(storage_params
, 'charms'):
628 raise EngineException("Charm defined in vnf[id={}] but not present in "
629 "package".format(indata
["id"]))
631 def _validate_vdu_cloud_init_in_package(self
, storage_params
, vdu
, indata
):
632 if not vdu
.get("cloud-init-file"):
634 if not self
._validate
_package
_folders
(storage_params
, 'cloud_init', vdu
["cloud-init-file"]):
635 raise EngineException("Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in "
636 "package".format(indata
["id"], vdu
["id"]))
638 def _validate_vnf_charms_in_package(self
, storage_params
, indata
):
639 if not indata
.get("vnf-configuration"):
641 for vnf_configuration
in get_iterable(indata
.get("vnf-configuration")):
642 if vnf_configuration
.get("juju"):
643 if not self
._validate
_package
_folders
(storage_params
, 'charms'):
644 raise EngineException("Charm defined in vnf[id={}] but not present in "
645 "package".format(indata
["id"]))
647 def _validate_package_folders(self
, storage_params
, folder
, file=None):
648 if not storage_params
or not storage_params
.get("pkg-dir"):
651 if self
.fs
.file_exists("{}_".format(storage_params
["folder"]), 'dir'):
652 f
= "{}_/{}/{}".format(storage_params
["folder"], storage_params
["pkg-dir"], folder
)
654 f
= "{}/{}/{}".format(storage_params
["folder"], storage_params
["pkg-dir"], folder
)
656 return self
.fs
.file_exists("{}/{}".format(f
, file), 'file')
658 if self
.fs
.file_exists(f
, 'dir'):
659 if self
.fs
.dir_ls(f
):
664 def validate_internal_virtual_links(indata
):
666 for ivld
in get_iterable(indata
.get("int-virtual-link-desc")):
667 ivld_id
= ivld
.get("id")
668 if ivld_id
and ivld_id
in all_ivld_ids
:
669 raise EngineException("Duplicated VLD id in int-virtual-link-desc[id={}]".format(ivld_id
),
670 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
672 all_ivld_ids
.add(ivld_id
)
674 for vdu
in get_iterable(indata
.get("vdu")):
675 for int_cpd
in get_iterable(vdu
.get("int-cpd")):
676 int_cpd_ivld_id
= int_cpd
.get("int-virtual-link-desc")
677 if int_cpd_ivld_id
and int_cpd_ivld_id
not in all_ivld_ids
:
678 raise EngineException(
679 "vdu[id='{}']:int-cpd[id='{}']:int-virtual-link-desc='{}' must match an existing "
680 "int-virtual-link-desc".format(vdu
["id"], int_cpd
["id"], int_cpd_ivld_id
),
681 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
683 for df
in get_iterable(indata
.get("df")):
684 for vlp
in get_iterable(df
.get("virtual-link-profile")):
685 vlp_ivld_id
= vlp
.get("id")
686 if vlp_ivld_id
and vlp_ivld_id
not in all_ivld_ids
:
687 raise EngineException("df[id='{}']:virtual-link-profile='{}' must match an existing "
688 "int-virtual-link-desc".format(df
["id"], vlp_ivld_id
),
689 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
692 def validate_monitoring_params(indata
):
693 all_monitoring_params
= set()
694 for ivld
in get_iterable(indata
.get("int-virtual-link-desc")):
695 for mp
in get_iterable(ivld
.get("monitoring-parameters")):
697 if mp_id
and mp_id
in all_monitoring_params
:
698 raise EngineException("Duplicated monitoring-parameter id in "
699 "int-virtual-link-desc[id='{}']:monitoring-parameters[id='{}']"
700 .format(ivld
["id"], mp_id
),
701 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
703 all_monitoring_params
.add(mp_id
)
705 for vdu
in get_iterable(indata
.get("vdu")):
706 for mp
in get_iterable(vdu
.get("monitoring-parameter")):
708 if mp_id
and mp_id
in all_monitoring_params
:
709 raise EngineException("Duplicated monitoring-parameter id in "
710 "vdu[id='{}']:monitoring-parameter[id='{}']"
711 .format(vdu
["id"], mp_id
),
712 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
714 all_monitoring_params
.add(mp_id
)
716 for df
in get_iterable(indata
.get("df")):
717 for mp
in get_iterable(df
.get("monitoring-parameter")):
719 if mp_id
and mp_id
in all_monitoring_params
:
720 raise EngineException("Duplicated monitoring-parameter id in "
721 "df[id='{}']:monitoring-parameter[id='{}']"
722 .format(df
["id"], mp_id
),
723 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
725 all_monitoring_params
.add(mp_id
)
728 def validate_scaling_group_descriptor(indata
):
729 all_monitoring_params
= set()
730 for ivld
in get_iterable(indata
.get("int-virtual-link-desc")):
731 for mp
in get_iterable(ivld
.get("monitoring-parameters")):
732 all_monitoring_params
.add(mp
.get("id"))
734 for vdu
in get_iterable(indata
.get("vdu")):
735 for mp
in get_iterable(vdu
.get("monitoring-parameter")):
736 all_monitoring_params
.add(mp
.get("id"))
738 for df
in get_iterable(indata
.get("df")):
739 for mp
in get_iterable(df
.get("monitoring-parameter")):
740 all_monitoring_params
.add(mp
.get("id"))
742 for df
in get_iterable(indata
.get("df")):
743 for sa
in get_iterable(df
.get("scaling-aspect")):
744 for sp
in get_iterable(sa
.get("scaling-policy")):
745 for sc
in get_iterable(sp
.get("scaling-criteria")):
746 sc_monitoring_param
= sc
.get("vnf-monitoring-param-ref")
747 if sc_monitoring_param
and sc_monitoring_param
not in all_monitoring_params
:
748 raise EngineException("df[id='{}']:scaling-aspect[id='{}']:scaling-policy"
749 "[name='{}']:scaling-criteria[name='{}']: "
750 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param"
751 .format(df
["id"], sa
["id"], sp
["name"], sc
["name"],
752 sc_monitoring_param
),
753 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
755 for sca
in get_iterable(sa
.get("scaling-config-action")):
756 if not indata
.get("vnf-configuration"):
757 raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced "
758 "by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action"
759 .format(df
["id"], sa
["id"]),
760 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
761 for configuration
in get_iterable(indata
["vnf-configuration"]):
762 for primitive
in get_iterable(configuration
.get("config-primitive")):
763 if primitive
["name"] == sca
["vnf-config-primitive-name-ref"]:
766 raise EngineException("df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
767 "config-primitive-name-ref='{}' does not match any "
768 "vnf-configuration:config-primitive:name"
769 .format(df
["id"], sa
["id"], sca
["vnf-config-primitive-name-ref"]),
770 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
772 def delete_extra(self
, session
, _id
, db_content
, not_send_msg
=None):
774 Deletes associate file system storage (via super)
775 Deletes associated vnfpkgops from database.
776 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
777 :param _id: server internal id
778 :param db_content: The database content of the descriptor
780 :raises: FsException in case of error while deleting associated storage
782 super().delete_extra(session
, _id
, db_content
, not_send_msg
)
783 self
.db
.del_list("vnfpkgops", {"vnfPkgId": _id
})
785 def sol005_projection(self
, data
):
786 data
["onboardingState"] = data
["_admin"]["onboardingState"]
787 data
["operationalState"] = data
["_admin"]["operationalState"]
788 data
["usageState"] = data
["_admin"]["usageState"]
791 links
["self"] = {"href": "/vnfpkgm/v1/vnf_packages/{}".format(data
["_id"])}
792 links
["vnfd"] = {"href": "/vnfpkgm/v1/vnf_packages/{}/vnfd".format(data
["_id"])}
793 links
["packageContent"] = {"href": "/vnfpkgm/v1/vnf_packages/{}/package_content".format(data
["_id"])}
794 data
["_links"] = links
796 return super().sol005_projection(data
)
799 class NsdTopic(DescriptorTopic
):
803 def __init__(self
, db
, fs
, msg
, auth
):
804 DescriptorTopic
.__init
__(self
, db
, fs
, msg
, auth
)
806 def pyangbind_validation(self
, item
, data
, force
=False):
807 if self
._descriptor
_data
_is
_in
_old
_format
(data
):
808 raise EngineException("ERROR: Unsupported descriptor format. Please, use an ETSI SOL006 descriptor.",
809 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
811 nsd_vnf_profiles
= data
.get('df', [{}])[0].get('vnf-profile', [])
812 mynsd
= etsi_nfv_nsd
.etsi_nfv_nsd()
813 pybindJSONDecoder
.load_ietf_json({'nsd': {'nsd': [data
]}}, None, None, obj
=mynsd
,
814 path_helper
=True, skip_unknown
=force
)
815 out
= pybindJSON
.dumps(mynsd
, mode
="ietf")
816 desc_out
= self
._remove
_envelop
(yaml
.safe_load(out
))
817 desc_out
= self
._remove
_yang
_prefixes
_from
_descriptor
(desc_out
)
819 desc_out
['df'][0]['vnf-profile'] = nsd_vnf_profiles
821 except Exception as e
:
822 raise EngineException("Error in pyangbind validation: {}".format(str(e
)),
823 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
826 def _descriptor_data_is_in_old_format(data
):
827 return ('nsd-catalog' in data
) or ('nsd:nsd-catalog' in data
)
830 def _remove_envelop(indata
=None):
833 clean_indata
= indata
835 if clean_indata
.get('nsd'):
836 clean_indata
= clean_indata
['nsd']
837 elif clean_indata
.get('etsi-nfv-nsd:nsd'):
838 clean_indata
= clean_indata
['etsi-nfv-nsd:nsd']
839 if clean_indata
.get('nsd'):
840 if not isinstance(clean_indata
['nsd'], list) or len(clean_indata
['nsd']) != 1:
841 raise EngineException("'nsd' must be a list of only one element")
842 clean_indata
= clean_indata
['nsd'][0]
845 def _validate_input_new(self
, indata
, storage_params
, force
=False):
846 indata
.pop("nsdOnboardingState", None)
847 indata
.pop("nsdOperationalState", None)
848 indata
.pop("nsdUsageState", None)
850 indata
.pop("links", None)
852 indata
= self
.pyangbind_validation("nsds", indata
, force
)
853 # Cross references validation in the descriptor
854 # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
855 for vld
in get_iterable(indata
.get("virtual-link-desc")):
856 self
.validate_vld_mgmt_network_with_virtual_link_protocol_data(vld
, indata
)
858 self
.validate_vnf_profiles_vnfd_id(indata
)
863 def validate_vld_mgmt_network_with_virtual_link_protocol_data(vld
, indata
):
864 if not vld
.get("mgmt-network"):
866 vld_id
= vld
.get("id")
867 for df
in get_iterable(indata
.get("df")):
868 for vlp
in get_iterable(df
.get("virtual-link-profile")):
869 if vld_id
and vld_id
== vlp
.get("virtual-link-desc-id"):
870 if vlp
.get("virtual-link-protocol-data"):
871 raise EngineException("Error at df[id='{}']:virtual-link-profile[id='{}']:virtual-link-"
872 "protocol-data You cannot set a virtual-link-protocol-data "
873 "when mgmt-network is True"
874 .format(df
["id"], vlp
["id"]), http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
877 def validate_vnf_profiles_vnfd_id(indata
):
878 all_vnfd_ids
= set(get_iterable(indata
.get("vnfd-id")))
879 for df
in get_iterable(indata
.get("df")):
880 for vnf_profile
in get_iterable(df
.get("vnf-profile")):
881 vnfd_id
= vnf_profile
.get("vnfd-id")
882 if vnfd_id
and vnfd_id
not in all_vnfd_ids
:
883 raise EngineException("Error at df[id='{}']:vnf_profile[id='{}']:vnfd-id='{}' "
884 "does not match any vnfd-id".format(df
["id"], vnf_profile
["id"], vnfd_id
),
885 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
887 def _validate_input_edit(self
, indata
, content
, force
=False):
888 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
890 indata looks as follows:
891 - In the new case (conformant)
892 {'nsdOperationalState': 'DISABLED', 'userDefinedData': {'id': 'string23',
893 '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}}
894 - In the old case (backwards-compatible)
895 {'id': 'string23', '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}
897 if "_admin" not in indata
:
898 indata
["_admin"] = {}
900 if "nsdOperationalState" in indata
:
901 if indata
["nsdOperationalState"] in ("ENABLED", "DISABLED"):
902 indata
["_admin"]["operationalState"] = indata
.pop("nsdOperationalState")
904 raise EngineException("State '{}' is not a valid operational state"
905 .format(indata
["nsdOperationalState"]),
906 http_code
=HTTPStatus
.BAD_REQUEST
)
908 # In the case of user defined data, we need to put the data in the root of the object
909 # to preserve current expected behaviour
910 if "userDefinedData" in indata
:
911 data
= indata
.pop("userDefinedData")
912 if type(data
) == dict:
913 indata
["_admin"]["userDefinedData"] = data
915 raise EngineException("userDefinedData should be an object, but is '{}' instead"
917 http_code
=HTTPStatus
.BAD_REQUEST
)
918 if ("operationalState" in indata
["_admin"] and
919 content
["_admin"]["operationalState"] == indata
["_admin"]["operationalState"]):
920 raise EngineException("nsdOperationalState already {}".format(content
["_admin"]["operationalState"]),
921 http_code
=HTTPStatus
.CONFLICT
)
924 def _check_descriptor_dependencies(self
, session
, descriptor
):
926 Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
927 connection points are ok
928 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
929 :param descriptor: descriptor to be inserted or edit
930 :return: None or raises exception
934 vnfds_index
= self
._get
_descriptor
_constituent
_vnfds
_index
(session
, descriptor
)
936 # Cross references validation in the descriptor and vnfd connection point validation
937 for df
in get_iterable(descriptor
.get("df")):
938 self
.validate_df_vnf_profiles_constituent_connection_points(df
, vnfds_index
)
940 def _get_descriptor_constituent_vnfds_index(self
, session
, descriptor
):
942 if descriptor
.get("vnfd-id") and not session
["force"]:
943 for vnfd_id
in get_iterable(descriptor
.get("vnfd-id")):
944 query_filter
= self
._get
_project
_filter
(session
)
945 query_filter
["id"] = vnfd_id
946 vnf_list
= self
.db
.get_list("vnfds", query_filter
)
948 raise EngineException("Descriptor error at 'vnfd-id'='{}' references a non "
949 "existing vnfd".format(vnfd_id
), http_code
=HTTPStatus
.CONFLICT
)
950 vnfds_index
[vnfd_id
] = vnf_list
[0]
954 def validate_df_vnf_profiles_constituent_connection_points(df
, vnfds_index
):
955 for vnf_profile
in get_iterable(df
.get("vnf-profile")):
956 vnfd
= vnfds_index
.get(vnf_profile
["vnfd-id"])
957 all_vnfd_ext_cpds
= set()
958 for ext_cpd
in get_iterable(vnfd
.get("ext-cpd")):
959 if ext_cpd
.get('id'):
960 all_vnfd_ext_cpds
.add(ext_cpd
.get('id'))
962 for virtual_link
in get_iterable(vnf_profile
.get("virtual-link-connectivity")):
963 for vl_cpd
in get_iterable(virtual_link
.get("constituent-cpd-id")):
964 vl_cpd_id
= vl_cpd
.get('constituent-cpd-id')
965 if vl_cpd_id
and vl_cpd_id
not in all_vnfd_ext_cpds
:
966 raise EngineException("Error at df[id='{}']:vnf-profile[id='{}']:virtual-link-connectivity"
967 "[virtual-link-profile-id='{}']:constituent-cpd-id='{}' references a "
968 "non existing ext-cpd:id inside vnfd '{}'"
969 .format(df
["id"], vnf_profile
["id"],
970 virtual_link
["virtual-link-profile-id"], vl_cpd_id
, vnfd
["id"]),
971 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
973 def check_conflict_on_edit(self
, session
, final_content
, edit_content
, _id
):
974 super().check_conflict_on_edit(session
, final_content
, edit_content
, _id
)
976 self
._check
_descriptor
_dependencies
(session
, final_content
)
978 def check_conflict_on_del(self
, session
, _id
, db_content
):
980 Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note
981 that NSD can be public and be used by other projects.
982 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
983 :param _id: nsd internal id
984 :param db_content: The database content of the _id
985 :return: None or raises EngineException with the conflict
989 descriptor
= db_content
990 descriptor_id
= descriptor
.get("id")
991 if not descriptor_id
: # empty nsd not uploaded
994 # check NSD used by NS
995 _filter
= self
._get
_project
_filter
(session
)
996 _filter
["nsd-id"] = _id
997 if self
.db
.get_list("nsrs", _filter
):
998 raise EngineException("There is at least one NS using this descriptor", http_code
=HTTPStatus
.CONFLICT
)
1000 # check NSD referenced by NST
1001 del _filter
["nsd-id"]
1002 _filter
["netslice-subnet.ANYINDEX.nsd-ref"] = descriptor_id
1003 if self
.db
.get_list("nsts", _filter
):
1004 raise EngineException("There is at least one NetSlice Template referencing this descriptor",
1005 http_code
=HTTPStatus
.CONFLICT
)
1007 def sol005_projection(self
, data
):
1008 data
["nsdOnboardingState"] = data
["_admin"]["onboardingState"]
1009 data
["nsdOperationalState"] = data
["_admin"]["operationalState"]
1010 data
["nsdUsageState"] = data
["_admin"]["usageState"]
1013 links
["self"] = {"href": "/nsd/v1/ns_descriptors/{}".format(data
["_id"])}
1014 links
["nsd_content"] = {"href": "/nsd/v1/ns_descriptors/{}/nsd_content".format(data
["_id"])}
1015 data
["_links"] = links
1017 return super().sol005_projection(data
)
1020 class NstTopic(DescriptorTopic
):
1023 quota_name
= "slice_templates"
1025 def __init__(self
, db
, fs
, msg
, auth
):
1026 DescriptorTopic
.__init
__(self
, db
, fs
, msg
, auth
)
1028 def pyangbind_validation(self
, item
, data
, force
=False):
1031 pybindJSONDecoder
.load_ietf_json({'nst': [data
]}, None, None, obj
=mynst
,
1032 path_helper
=True, skip_unknown
=force
)
1033 out
= pybindJSON
.dumps(mynst
, mode
="ietf")
1034 desc_out
= self
._remove
_envelop
(yaml
.safe_load(out
))
1036 except Exception as e
:
1037 raise EngineException("Error in pyangbind validation: {}".format(str(e
)),
1038 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
1041 def _remove_envelop(indata
=None):
1044 clean_indata
= indata
1046 if clean_indata
.get('nst'):
1047 if not isinstance(clean_indata
['nst'], list) or len(clean_indata
['nst']) != 1:
1048 raise EngineException("'nst' must be a list only one element")
1049 clean_indata
= clean_indata
['nst'][0]
1050 elif clean_indata
.get('nst:nst'):
1051 if not isinstance(clean_indata
['nst:nst'], list) or len(clean_indata
['nst:nst']) != 1:
1052 raise EngineException("'nst:nst' must be a list only one element")
1053 clean_indata
= clean_indata
['nst:nst'][0]
1056 def _validate_input_new(self
, indata
, storage_params
, force
=False):
1057 indata
.pop("onboardingState", None)
1058 indata
.pop("operationalState", None)
1059 indata
.pop("usageState", None)
1060 indata
= self
.pyangbind_validation("nsts", indata
, force
)
1061 return indata
.copy()
1063 def _check_descriptor_dependencies(self
, session
, descriptor
):
1065 Check that the dependent descriptors exist on a new descriptor or edition
1066 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1067 :param descriptor: descriptor to be inserted or edit
1068 :return: None or raises exception
1070 if not descriptor
.get("netslice-subnet"):
1072 for nsd
in descriptor
["netslice-subnet"]:
1073 nsd_id
= nsd
["nsd-ref"]
1074 filter_q
= self
._get
_project
_filter
(session
)
1075 filter_q
["id"] = nsd_id
1076 if not self
.db
.get_list("nsds", filter_q
):
1077 raise EngineException("Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non "
1078 "existing nsd".format(nsd_id
), http_code
=HTTPStatus
.CONFLICT
)
1080 def check_conflict_on_edit(self
, session
, final_content
, edit_content
, _id
):
1081 super().check_conflict_on_edit(session
, final_content
, edit_content
, _id
)
1083 self
._check
_descriptor
_dependencies
(session
, final_content
)
1085 def check_conflict_on_del(self
, session
, _id
, db_content
):
1087 Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note
1088 that NST can be public and be used by other projects.
1089 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1090 :param _id: nst internal id
1091 :param db_content: The database content of the _id.
1092 :return: None or raises EngineException with the conflict
1094 # TODO: Check this method
1095 if session
["force"]:
1097 # Get Network Slice Template from Database
1098 _filter
= self
._get
_project
_filter
(session
)
1099 _filter
["_admin.nst-id"] = _id
1100 if self
.db
.get_list("nsis", _filter
):
1101 raise EngineException("there is at least one Netslice Instance using this descriptor",
1102 http_code
=HTTPStatus
.CONFLICT
)
1104 def sol005_projection(self
, data
):
1105 data
["onboardingState"] = data
["_admin"]["onboardingState"]
1106 data
["operationalState"] = data
["_admin"]["operationalState"]
1107 data
["usageState"] = data
["_admin"]["usageState"]
1110 links
["self"] = {"href": "/nst/v1/netslice_templates/{}".format(data
["_id"])}
1111 links
["nst"] = {"href": "/nst/v1/netslice_templates/{}/nst".format(data
["_id"])}
1112 data
["_links"] = links
1114 return super().sol005_projection(data
)
1117 class PduTopic(BaseTopic
):
1120 quota_name
= "pduds"
1121 schema_new
= pdu_new_schema
1122 schema_edit
= pdu_edit_schema
1124 def __init__(self
, db
, fs
, msg
, auth
):
1125 BaseTopic
.__init
__(self
, db
, fs
, msg
, auth
)
1128 def format_on_new(content
, project_id
=None, make_public
=False):
1129 BaseTopic
.format_on_new(content
, project_id
=project_id
, make_public
=make_public
)
1130 content
["_admin"]["onboardingState"] = "CREATED"
1131 content
["_admin"]["operationalState"] = "ENABLED"
1132 content
["_admin"]["usageState"] = "NOT_IN_USE"
1134 def check_conflict_on_del(self
, session
, _id
, db_content
):
1136 Check that there is not any vnfr that uses this PDU
1137 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1138 :param _id: pdu internal id
1139 :param db_content: The database content of the _id.
1140 :return: None or raises EngineException with the conflict
1142 if session
["force"]:
1145 _filter
= self
._get
_project
_filter
(session
)
1146 _filter
["vdur.pdu-id"] = _id
1147 if self
.db
.get_list("vnfrs", _filter
):
1148 raise EngineException("There is at least one VNF using this PDU", http_code
=HTTPStatus
.CONFLICT
)
1151 class VnfPkgOpTopic(BaseTopic
):
1154 schema_new
= vnfpkgop_new_schema
1157 def __init__(self
, db
, fs
, msg
, auth
):
1158 BaseTopic
.__init
__(self
, db
, fs
, msg
, auth
)
1160 def edit(self
, session
, _id
, indata
=None, kwargs
=None, content
=None):
1161 raise EngineException("Method 'edit' not allowed for topic '{}'".format(self
.topic
),
1162 HTTPStatus
.METHOD_NOT_ALLOWED
)
1164 def delete(self
, session
, _id
, dry_run
=False):
1165 raise EngineException("Method 'delete' not allowed for topic '{}'".format(self
.topic
),
1166 HTTPStatus
.METHOD_NOT_ALLOWED
)
1168 def delete_list(self
, session
, filter_q
=None):
1169 raise EngineException("Method 'delete_list' not allowed for topic '{}'".format(self
.topic
),
1170 HTTPStatus
.METHOD_NOT_ALLOWED
)
1172 def new(self
, rollback
, session
, indata
=None, kwargs
=None, headers
=None):
1174 Creates a new entry into database.
1175 :param rollback: list to append created items at database in case a rollback may to be done
1176 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1177 :param indata: data to be inserted
1178 :param kwargs: used to override the indata descriptor
1179 :param headers: http request headers
1180 :return: _id, op_id:
1181 _id: identity of the inserted data.
1184 self
._update
_input
_with
_kwargs
(indata
, kwargs
)
1185 validate_input(indata
, self
.schema_new
)
1186 vnfpkg_id
= indata
["vnfPkgId"]
1187 filter_q
= BaseTopic
._get
_project
_filter
(session
)
1188 filter_q
["_id"] = vnfpkg_id
1189 vnfd
= self
.db
.get_one("vnfds", filter_q
)
1190 operation
= indata
["lcmOperationType"]
1191 kdu_name
= indata
["kdu_name"]
1192 for kdu
in vnfd
.get("kdu", []):
1193 if kdu
["name"] == kdu_name
:
1194 helm_chart
= kdu
.get("helm-chart")
1195 juju_bundle
= kdu
.get("juju-bundle")
1198 raise EngineException("Not found vnfd[id='{}']:kdu[name='{}']".format(vnfpkg_id
, kdu_name
))
1200 indata
["helm-chart"] = helm_chart
1201 match
= fullmatch(r
"([^/]*)/([^/]*)", helm_chart
)
1202 repo_name
= match
.group(1) if match
else None
1204 indata
["juju-bundle"] = juju_bundle
1205 match
= fullmatch(r
"([^/]*)/([^/]*)", juju_bundle
)
1206 repo_name
= match
.group(1) if match
else None
1208 raise EngineException("Found neither 'helm-chart' nor 'juju-bundle' in vnfd[id='{}']:kdu[name='{}']"
1209 .format(vnfpkg_id
, kdu_name
))
1212 filter_q
["name"] = repo_name
1213 repo
= self
.db
.get_one("k8srepos", filter_q
)
1214 k8srepo_id
= repo
.get("_id")
1215 k8srepo_url
= repo
.get("url")
1219 indata
["k8srepoId"] = k8srepo_id
1220 indata
["k8srepo_url"] = k8srepo_url
1221 vnfpkgop_id
= str(uuid4())
1224 "operationState": "PROCESSING",
1225 "vnfPkgId": vnfpkg_id
,
1226 "lcmOperationType": operation
,
1227 "isAutomaticInvocation": False,
1228 "isCancelPending": False,
1229 "operationParams": indata
,
1231 "self": "/osm/vnfpkgm/v1/vnfpkg_op_occs/" + vnfpkgop_id
,
1232 "vnfpkg": "/osm/vnfpkgm/v1/vnf_packages/" + vnfpkg_id
,
1235 self
.format_on_new(vnfpkgop_desc
, session
["project_id"], make_public
=session
["public"])
1236 ctime
= vnfpkgop_desc
["_admin"]["created"]
1237 vnfpkgop_desc
["statusEnteredTime"] = ctime
1238 vnfpkgop_desc
["startTime"] = ctime
1239 self
.db
.create(self
.topic
, vnfpkgop_desc
)
1240 rollback
.append({"topic": self
.topic
, "_id": vnfpkgop_id
})
1241 self
.msg
.write(self
.topic_msg
, operation
, vnfpkgop_desc
)
1242 return vnfpkgop_id
, None