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.
20 from hashlib
import md5
21 from osm_common
.dbbase
import DbException
, deep_update_rfc7396
22 from http
import HTTPStatus
24 from uuid
import uuid4
25 from re
import fullmatch
26 from osm_nbi
.validation
import ValidationError
, pdu_new_schema
, pdu_edit_schema
, \
27 validate_input
, vnfpkgop_new_schema
28 from osm_nbi
.base_topic
import BaseTopic
, EngineException
, get_iterable
29 from osm_im
.vnfd
import vnfd
as vnfd_im
30 from osm_im
.nsd
import nsd
as nsd_im
31 from osm_im
.nst
import nst
as nst_im
32 from pyangbind
.lib
.serialise
import pybindJSONDecoder
33 import pyangbind
.lib
.pybindJSON
as pybindJSON
35 __author__
= "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
38 class DescriptorTopic(BaseTopic
):
40 def __init__(self
, db
, fs
, msg
, auth
):
41 BaseTopic
.__init
__(self
, db
, fs
, msg
, auth
)
43 def check_conflict_on_edit(self
, session
, final_content
, edit_content
, _id
):
44 super().check_conflict_on_edit(session
, final_content
, edit_content
, _id
)
46 def _check_unique_id_name(descriptor
, position
=""):
47 for desc_key
, desc_item
in descriptor
.items():
48 if isinstance(desc_item
, list) and desc_item
:
51 for index
, list_item
in enumerate(desc_item
):
52 if isinstance(list_item
, dict):
53 _check_unique_id_name(list_item
, "{}.{}[{}]"
54 .format(position
, desc_key
, index
))
56 if index
== 0 and (list_item
.get("id") or list_item
.get("name")):
57 desc_item_id
= "id" if list_item
.get("id") else "name"
58 if desc_item_id
and list_item
.get(desc_item_id
):
59 if list_item
[desc_item_id
] in used_ids
:
60 position
= "{}.{}[{}]".format(position
, desc_key
, index
)
61 raise EngineException("Error: identifier {} '{}' is not unique and repeats at '{}'"
62 .format(desc_item_id
, list_item
[desc_item_id
],
63 position
), HTTPStatus
.UNPROCESSABLE_ENTITY
)
64 used_ids
.append(list_item
[desc_item_id
])
65 _check_unique_id_name(final_content
)
66 # 1. validate again with pyangbind
67 # 1.1. remove internal keys
69 for k
in ("_id", "_admin"):
70 if k
in final_content
:
71 internal_keys
[k
] = final_content
.pop(k
)
72 storage_params
= internal_keys
["_admin"].get("storage")
73 serialized
= self
._validate
_input
_new
(final_content
, storage_params
, session
["force"])
74 # 1.2. modify final_content with a serialized version
76 final_content
.update(serialized
)
77 # 1.3. restore internal keys
78 for k
, v
in internal_keys
.items():
83 # 2. check that this id is not present
84 if "id" in edit_content
:
85 _filter
= self
._get
_project
_filter
(session
)
86 _filter
["id"] = final_content
["id"]
87 _filter
["_id.neq"] = _id
88 if self
.db
.get_one(self
.topic
, _filter
, fail_on_empty
=False):
89 raise EngineException("{} with id '{}' already exists for this project".format(self
.topic
[:-1],
94 def format_on_new(content
, project_id
=None, make_public
=False):
95 BaseTopic
.format_on_new(content
, project_id
=project_id
, make_public
=make_public
)
96 content
["_admin"]["onboardingState"] = "CREATED"
97 content
["_admin"]["operationalState"] = "DISABLED"
98 content
["_admin"]["usageState"] = "NOT_IN_USE"
100 def delete_extra(self
, session
, _id
, db_content
, not_send_msg
=None):
102 Deletes file system storage associated with the descriptor
103 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
104 :param _id: server internal id
105 :param db_content: The database content of the descriptor
106 :param not_send_msg: To not send message (False) or store content (list) instead
107 :return: None if ok or raises EngineException with the problem
109 self
.fs
.file_delete(_id
, ignore_non_exist
=True)
110 self
.fs
.file_delete(_id
+ "_", ignore_non_exist
=True) # remove temp folder
113 def get_one_by_id(db
, session
, topic
, id):
114 # find owned by this project
115 _filter
= BaseTopic
._get
_project
_filter
(session
)
117 desc_list
= db
.get_list(topic
, _filter
)
118 if len(desc_list
) == 1:
120 elif len(desc_list
) > 1:
121 raise DbException("Found more than one {} with id='{}' belonging to this project".format(topic
[:-1], id),
124 # not found any: try to find public
125 _filter
= BaseTopic
._get
_project
_filter
(session
)
127 desc_list
= db
.get_list(topic
, _filter
)
129 raise DbException("Not found any {} with id='{}'".format(topic
[:-1], id), HTTPStatus
.NOT_FOUND
)
130 elif len(desc_list
) == 1:
133 raise DbException("Found more than one public {} with id='{}'; and no one belonging to this project".format(
134 topic
[:-1], id), HTTPStatus
.CONFLICT
)
136 def new(self
, rollback
, session
, indata
=None, kwargs
=None, headers
=None):
138 Creates a new almost empty DISABLED entry into database. Due to SOL005, it does not follow normal procedure.
139 Creating a VNFD or NSD is done in two steps: 1. Creates an empty descriptor (this step) and 2) upload content
140 (self.upload_content)
141 :param rollback: list to append created items at database in case a rollback may to be done
142 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
143 :param indata: data to be inserted
144 :param kwargs: used to override the indata descriptor
145 :param headers: http request headers
146 :return: _id, None: identity of the inserted data; and None as there is not any operation
149 # No needed to capture exceptions
151 self
.check_quota(session
)
155 if "userDefinedData" in indata
:
156 indata
= indata
['userDefinedData']
158 # Override descriptor with query string kwargs
159 self
._update
_input
_with
_kwargs
(indata
, kwargs
)
160 # uncomment when this method is implemented.
161 # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors
162 # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"])
164 content
= {"_admin": {"userDefinedData": indata
}}
165 self
.format_on_new(content
, session
["project_id"], make_public
=session
["public"])
166 _id
= self
.db
.create(self
.topic
, content
)
167 rollback
.append({"topic": self
.topic
, "_id": _id
})
168 self
._send
_msg
("created", {"_id": _id
})
171 def upload_content(self
, session
, _id
, indata
, kwargs
, headers
):
173 Used for receiving content by chunks (with a transaction_id header and/or gzip file. It will store and extract)
174 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
175 :param _id : the nsd,vnfd is already created, this is the id
176 :param indata: http body request
177 :param kwargs: user query string to override parameters. NOT USED
178 :param headers: http request headers
179 :return: True if package is completely uploaded or False if partial content has been uploded
180 Raise exception on error
182 # Check that _id exists and it is valid
183 current_desc
= self
.show(session
, _id
)
185 content_range_text
= headers
.get("Content-Range")
186 expected_md5
= headers
.get("Content-File-MD5")
188 content_type
= headers
.get("Content-Type")
189 if content_type
and "application/gzip" in content_type
or "application/x-gzip" in content_type
or \
190 "application/zip" in content_type
:
192 filename
= headers
.get("Content-Filename")
194 filename
= "package.tar.gz" if compressed
else "package"
195 # TODO change to Content-Disposition filename https://tools.ietf.org/html/rfc6266
199 if content_range_text
:
200 content_range
= content_range_text
.replace("-", " ").replace("/", " ").split()
201 if content_range
[0] != "bytes": # TODO check x<y not negative < total....
203 start
= int(content_range
[1])
204 end
= int(content_range
[2]) + 1
205 total
= int(content_range
[3])
208 temp_folder
= _id
+ "_" # all the content is upload here and if ok, it is rename from id_ to is folder
211 if not self
.fs
.file_exists(temp_folder
, 'dir'):
212 raise EngineException("invalid Transaction-Id header", HTTPStatus
.NOT_FOUND
)
214 self
.fs
.file_delete(temp_folder
, ignore_non_exist
=True)
215 self
.fs
.mkdir(temp_folder
)
217 storage
= self
.fs
.get_params()
218 storage
["folder"] = _id
220 file_path
= (temp_folder
, filename
)
221 if self
.fs
.file_exists(file_path
, 'file'):
222 file_size
= self
.fs
.file_size(file_path
)
225 if file_size
!= start
:
226 raise EngineException("invalid Content-Range start sequence, expected '{}' but received '{}'".format(
227 file_size
, start
), HTTPStatus
.REQUESTED_RANGE_NOT_SATISFIABLE
)
228 file_pkg
= self
.fs
.file_open(file_path
, 'a+b')
229 if isinstance(indata
, dict):
230 indata_text
= yaml
.safe_dump(indata
, indent
=4, default_flow_style
=False)
231 file_pkg
.write(indata_text
.encode(encoding
="utf-8"))
235 indata_text
= indata
.read(4096)
236 indata_len
+= len(indata_text
)
239 file_pkg
.write(indata_text
)
240 if content_range_text
:
241 if indata_len
!= end
-start
:
242 raise EngineException("Mismatch between Content-Range header {}-{} and body length of {}".format(
243 start
, end
-1, indata_len
), HTTPStatus
.REQUESTED_RANGE_NOT_SATISFIABLE
)
245 # TODO update to UPLOADING
252 chunk_data
= file_pkg
.read(1024)
254 file_md5
.update(chunk_data
)
255 chunk_data
= file_pkg
.read(1024)
256 if expected_md5
!= file_md5
.hexdigest():
257 raise EngineException("Error, MD5 mismatch", HTTPStatus
.CONFLICT
)
259 if compressed
== "gzip":
260 tar
= tarfile
.open(mode
='r', fileobj
=file_pkg
)
261 descriptor_file_name
= None
263 tarname
= tarinfo
.name
264 tarname_path
= tarname
.split("/")
265 if not tarname_path
[0] or ".." in tarname_path
: # if start with "/" means absolute path
266 raise EngineException("Absolute path or '..' are not allowed for package descriptor tar.gz")
267 if len(tarname_path
) == 1 and not tarinfo
.isdir():
268 raise EngineException("All files must be inside a dir for package descriptor tar.gz")
269 if tarname
.endswith(".yaml") or tarname
.endswith(".json") or tarname
.endswith(".yml"):
270 storage
["pkg-dir"] = tarname_path
[0]
271 if len(tarname_path
) == 2:
272 if descriptor_file_name
:
273 raise EngineException(
274 "Found more than one descriptor file at package descriptor tar.gz")
275 descriptor_file_name
= tarname
276 if not descriptor_file_name
:
277 raise EngineException("Not found any descriptor file at package descriptor tar.gz")
278 storage
["descriptor"] = descriptor_file_name
279 storage
["zipfile"] = filename
280 self
.fs
.file_extract(tar
, temp_folder
)
281 with self
.fs
.file_open((temp_folder
, descriptor_file_name
), "r") as descriptor_file
:
282 content
= descriptor_file
.read()
284 content
= file_pkg
.read()
285 storage
["descriptor"] = descriptor_file_name
= filename
287 if descriptor_file_name
.endswith(".json"):
288 error_text
= "Invalid json format "
289 indata
= json
.load(content
)
291 error_text
= "Invalid yaml format "
292 indata
= yaml
.load(content
, Loader
=yaml
.SafeLoader
)
294 current_desc
["_admin"]["storage"] = storage
295 current_desc
["_admin"]["onboardingState"] = "ONBOARDED"
296 current_desc
["_admin"]["operationalState"] = "ENABLED"
298 indata
= self
._remove
_envelop
(indata
)
300 # Override descriptor with query string kwargs
302 self
._update
_input
_with
_kwargs
(indata
, kwargs
)
303 # it will call overrides method at VnfdTopic or NsdTopic
304 # indata = self._validate_input_edit(indata, force=session["force"])
306 deep_update_rfc7396(current_desc
, indata
)
307 self
.check_conflict_on_edit(session
, current_desc
, indata
, _id
=_id
)
308 current_desc
["_admin"]["modified"] = time()
309 self
.db
.replace(self
.topic
, _id
, current_desc
)
310 self
.fs
.dir_rename(temp_folder
, _id
)
313 self
._send
_msg
("edited", indata
)
315 # TODO if descriptor has changed because kwargs update content and remove cached zip
316 # TODO if zip is not present creates one
319 except EngineException
:
322 raise EngineException("invalid Content-Range header format. Expected 'bytes start-end/total'",
323 HTTPStatus
.REQUESTED_RANGE_NOT_SATISFIABLE
)
325 raise EngineException("invalid upload transaction sequence: '{}'".format(e
), HTTPStatus
.BAD_REQUEST
)
326 except tarfile
.ReadError
as e
:
327 raise EngineException("invalid file content {}".format(e
), HTTPStatus
.BAD_REQUEST
)
328 except (ValueError, yaml
.YAMLError
) as e
:
329 raise EngineException(error_text
+ str(e
))
330 except ValidationError
as e
:
331 raise EngineException(e
, HTTPStatus
.UNPROCESSABLE_ENTITY
)
336 def get_file(self
, session
, _id
, path
=None, accept_header
=None):
338 Return the file content of a vnfd or nsd
339 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
340 :param _id: Identity of the vnfd, nsd
341 :param path: artifact path or "$DESCRIPTOR" or None
342 :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
343 :return: opened file plus Accept format or raises an exception
345 accept_text
= accept_zip
= False
347 if 'text/plain' in accept_header
or '*/*' in accept_header
:
349 if 'application/zip' in accept_header
or '*/*' in accept_header
:
350 accept_zip
= 'application/zip'
351 elif 'application/gzip' in accept_header
:
352 accept_zip
= 'application/gzip'
354 if not accept_text
and not accept_zip
:
355 raise EngineException("provide request header 'Accept' with 'application/zip' or 'text/plain'",
356 http_code
=HTTPStatus
.NOT_ACCEPTABLE
)
358 content
= self
.show(session
, _id
)
359 if content
["_admin"]["onboardingState"] != "ONBOARDED":
360 raise EngineException("Cannot get content because this resource is not at 'ONBOARDED' state. "
361 "onboardingState is {}".format(content
["_admin"]["onboardingState"]),
362 http_code
=HTTPStatus
.CONFLICT
)
363 storage
= content
["_admin"]["storage"]
364 if path
is not None and path
!= "$DESCRIPTOR": # artifacts
365 if not storage
.get('pkg-dir'):
366 raise EngineException("Packages does not contains artifacts", http_code
=HTTPStatus
.BAD_REQUEST
)
367 if self
.fs
.file_exists((storage
['folder'], storage
['pkg-dir'], *path
), 'dir'):
368 folder_content
= self
.fs
.dir_ls((storage
['folder'], storage
['pkg-dir'], *path
))
369 return folder_content
, "text/plain"
370 # TODO manage folders in http
372 return self
.fs
.file_open((storage
['folder'], storage
['pkg-dir'], *path
), "rb"),\
373 "application/octet-stream"
375 # pkgtype accept ZIP TEXT -> result
376 # manyfiles yes X -> zip
378 # onefile yes no -> zip
381 if accept_text
and (not storage
.get('pkg-dir') or path
== "$DESCRIPTOR"):
382 return self
.fs
.file_open((storage
['folder'], storage
['descriptor']), "r"), "text/plain"
383 elif storage
.get('pkg-dir') and not accept_zip
:
384 raise EngineException("Packages that contains several files need to be retrieved with 'application/zip'"
385 "Accept header", http_code
=HTTPStatus
.NOT_ACCEPTABLE
)
387 if not storage
.get('zipfile'):
388 # TODO generate zipfile if not present
389 raise EngineException("Only allowed 'text/plain' Accept header for this descriptor. To be solved in "
390 "future versions", http_code
=HTTPStatus
.NOT_ACCEPTABLE
)
391 return self
.fs
.file_open((storage
['folder'], storage
['zipfile']), "rb"), accept_zip
393 def pyangbind_validation(self
, item
, data
, force
=False):
397 pybindJSONDecoder
.load_ietf_json({'vnfd:vnfd-catalog': {'vnfd': [data
]}}, None, None, obj
=myvnfd
,
398 path_helper
=True, skip_unknown
=force
)
399 out
= pybindJSON
.dumps(myvnfd
, mode
="ietf")
402 pybindJSONDecoder
.load_ietf_json({'nsd:nsd-catalog': {'nsd': [data
]}}, None, None, obj
=mynsd
,
403 path_helper
=True, skip_unknown
=force
)
404 out
= pybindJSON
.dumps(mynsd
, mode
="ietf")
407 pybindJSONDecoder
.load_ietf_json({'nst': [data
]}, None, None, obj
=mynst
,
408 path_helper
=True, skip_unknown
=force
)
409 out
= pybindJSON
.dumps(mynst
, mode
="ietf")
411 raise EngineException("Not possible to validate '{}' item".format(item
),
412 http_code
=HTTPStatus
.INTERNAL_SERVER_ERROR
)
414 desc_out
= self
._remove
_envelop
(yaml
.safe_load(out
))
417 except Exception as e
:
418 raise EngineException("Error in pyangbind validation: {}".format(str(e
)),
419 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
421 def _validate_input_edit(self
, indata
, content
, force
=False):
422 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
425 if "_admin" not in indata
:
426 indata
["_admin"] = {}
428 if "operationalState" in indata
:
429 if indata
["operationalState"] in ("ENABLED", "DISABLED"):
430 indata
["_admin"]["operationalState"] = indata
.pop("operationalState")
432 raise EngineException("State '{}' is not a valid operational state"
433 .format(indata
["operationalState"]),
434 http_code
=HTTPStatus
.BAD_REQUEST
)
436 # In the case of user defined data, we need to put the data in the root of the object
437 # to preserve current expected behaviour
438 if "userDefinedData" in indata
:
439 data
= indata
.pop("userDefinedData")
440 if type(data
) == dict:
441 indata
["_admin"]["userDefinedData"] = data
443 raise EngineException("userDefinedData should be an object, but is '{}' instead"
445 http_code
=HTTPStatus
.BAD_REQUEST
)
447 if ("operationalState" in indata
["_admin"] and
448 content
["_admin"]["operationalState"] == indata
["_admin"]["operationalState"]):
449 raise EngineException("operationalState already {}".format(content
["_admin"]["operationalState"]),
450 http_code
=HTTPStatus
.CONFLICT
)
455 class VnfdTopic(DescriptorTopic
):
459 def __init__(self
, db
, fs
, msg
, auth
):
460 DescriptorTopic
.__init
__(self
, db
, fs
, msg
, auth
)
463 def _remove_envelop(indata
=None):
466 clean_indata
= indata
467 if clean_indata
.get('vnfd:vnfd-catalog'):
468 clean_indata
= clean_indata
['vnfd:vnfd-catalog']
469 elif clean_indata
.get('vnfd-catalog'):
470 clean_indata
= clean_indata
['vnfd-catalog']
471 if clean_indata
.get('vnfd'):
472 if not isinstance(clean_indata
['vnfd'], list) or len(clean_indata
['vnfd']) != 1:
473 raise EngineException("'vnfd' must be a list of only one element")
474 clean_indata
= clean_indata
['vnfd'][0]
475 elif clean_indata
.get('vnfd:vnfd'):
476 if not isinstance(clean_indata
['vnfd:vnfd'], list) or len(clean_indata
['vnfd:vnfd']) != 1:
477 raise EngineException("'vnfd:vnfd' must be a list of only one element")
478 clean_indata
= clean_indata
['vnfd:vnfd'][0]
481 def check_conflict_on_edit(self
, session
, final_content
, edit_content
, _id
):
482 super().check_conflict_on_edit(session
, final_content
, edit_content
, _id
)
487 for vdu
in get_iterable(final_content
.get("vdu")):
488 if vdu
.get("pdu-type"):
493 final_content
["_admin"]["type"] = "hnfd" if contains_vdu
else "pnfd"
495 final_content
["_admin"]["type"] = "vnfd"
496 # if neither vud nor pdu do not fill type
498 def check_conflict_on_del(self
, session
, _id
, db_content
):
500 Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
501 that VNFD can be public and be used by NSD of other projects. Also check there are not deployments, or vnfr
503 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
504 :param _id: vnfd internal id
505 :param db_content: The database content of the _id.
506 :return: None or raises EngineException with the conflict
510 descriptor
= db_content
511 descriptor_id
= descriptor
.get("id")
512 if not descriptor_id
: # empty vnfd not uploaded
515 _filter
= self
._get
_project
_filter
(session
)
517 # check vnfrs using this vnfd
518 _filter
["vnfd-id"] = _id
519 if self
.db
.get_list("vnfrs", _filter
):
520 raise EngineException("There is at least one VNF using this descriptor", http_code
=HTTPStatus
.CONFLICT
)
522 # check NSD referencing this VNFD
523 del _filter
["vnfd-id"]
524 _filter
["constituent-vnfd.ANYINDEX.vnfd-id-ref"] = descriptor_id
525 if self
.db
.get_list("nsds", _filter
):
526 raise EngineException("There is at least one NSD referencing this descriptor",
527 http_code
=HTTPStatus
.CONFLICT
)
529 def _validate_input_new(self
, indata
, storage_params
, force
=False):
530 indata
= self
.pyangbind_validation("vnfds", indata
, force
)
531 # Cross references validation in the descriptor
532 if indata
.get("vdu"):
533 if not indata
.get("mgmt-interface"):
534 raise EngineException("'mgmt-interface' is a mandatory field and it is not defined",
535 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
536 if indata
["mgmt-interface"].get("cp"):
537 for cp
in get_iterable(indata
.get("connection-point")):
538 if cp
["name"] == indata
["mgmt-interface"]["cp"]:
541 raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point"
542 .format(indata
["mgmt-interface"]["cp"]),
543 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
545 for vdu
in get_iterable(indata
.get("vdu")):
548 for interface
in get_iterable(vdu
.get("interface")):
549 if interface
.get("external-connection-point-ref"):
550 if interface
.get("external-connection-point-ref") in ecp_refs
:
551 raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' "
552 "is referenced by other interface"
553 .format(vdu
["id"], interface
["name"],
554 interface
["external-connection-point-ref"]),
555 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
556 ecp_refs
.append(interface
.get("external-connection-point-ref"))
557 for cp
in get_iterable(indata
.get("connection-point")):
558 if cp
["name"] == interface
["external-connection-point-ref"]:
561 raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' "
562 "must match an existing connection-point"
563 .format(vdu
["id"], interface
["name"],
564 interface
["external-connection-point-ref"]),
565 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
566 elif interface
.get("internal-connection-point-ref"):
567 if interface
.get("internal-connection-point-ref") in icp_refs
:
568 raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' "
569 "is referenced by other interface"
570 .format(vdu
["id"], interface
["name"],
571 interface
["internal-connection-point-ref"]),
572 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
573 icp_refs
.append(interface
.get("internal-connection-point-ref"))
574 for internal_cp
in get_iterable(vdu
.get("internal-connection-point")):
575 if interface
["internal-connection-point-ref"] == internal_cp
.get("id"):
578 raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' "
579 "must match an existing vdu:internal-connection-point"
580 .format(vdu
["id"], interface
["name"],
581 interface
["internal-connection-point-ref"]),
582 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
583 # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
584 if vdu
.get("vdu-configuration"):
585 if vdu
["vdu-configuration"].get("juju"):
586 if not self
._validate
_package
_folders
(storage_params
, 'charms'):
587 raise EngineException("Charm defined in vnf[id={}]:vdu[id={}] but not present in "
588 "package".format(indata
["id"], vdu
["id"]))
589 # Validate that if descriptor contains cloud-init, artifacts _admin.storage."pkg-dir" is not none
590 if vdu
.get("cloud-init-file"):
591 if not self
._validate
_package
_folders
(storage_params
, 'cloud_init', vdu
["cloud-init-file"]):
592 raise EngineException("Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in "
593 "package".format(indata
["id"], vdu
["id"]))
594 # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none
595 if indata
.get("vnf-configuration"):
596 if indata
["vnf-configuration"].get("juju"):
597 if not self
._validate
_package
_folders
(storage_params
, 'charms'):
598 raise EngineException("Charm defined in vnf[id={}] but not present in "
599 "package".format(indata
["id"]))
600 vld_names
= [] # For detection of duplicated VLD names
601 for ivld
in get_iterable(indata
.get("internal-vld")):
602 # BEGIN Detection of duplicated VLD names
603 ivld_name
= ivld
.get("name")
605 if ivld_name
in vld_names
:
606 raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]"
607 .format(ivld
["name"], indata
["id"], ivld
["id"]),
608 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
610 vld_names
.append(ivld_name
)
611 # END Detection of duplicated VLD names
612 for icp
in get_iterable(ivld
.get("internal-connection-point")):
614 for vdu
in get_iterable(indata
.get("vdu")):
615 for internal_cp
in get_iterable(vdu
.get("internal-connection-point")):
616 if icp
["id-ref"] == internal_cp
["id"]:
622 raise EngineException("internal-vld[id='{}']:internal-connection-point='{}' must match an existing "
623 "vdu:internal-connection-point".format(ivld
["id"], icp
["id-ref"]),
624 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
625 if ivld
.get("ip-profile-ref"):
626 for ip_prof
in get_iterable(indata
.get("ip-profiles")):
627 if ip_prof
["name"] == get_iterable(ivld
.get("ip-profile-ref")):
630 raise EngineException("internal-vld[id='{}']:ip-profile-ref='{}' does not exist".format(
631 ivld
["id"], ivld
["ip-profile-ref"]),
632 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
633 for mp
in get_iterable(indata
.get("monitoring-param")):
634 if mp
.get("vdu-monitoring-param"):
636 for vdu
in get_iterable(indata
.get("vdu")):
637 for vmp
in get_iterable(vdu
.get("monitoring-param")):
638 if vmp
["id"] == mp
["vdu-monitoring-param"].get("vdu-monitoring-param-ref") and vdu
["id"] ==\
639 mp
["vdu-monitoring-param"]["vdu-ref"]:
645 raise EngineException("monitoring-param:vdu-monitoring-param:vdu-monitoring-param-ref='{}' not "
646 "defined at vdu[id='{}'] or vdu does not exist"
647 .format(mp
["vdu-monitoring-param"]["vdu-monitoring-param-ref"],
648 mp
["vdu-monitoring-param"]["vdu-ref"]),
649 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
650 elif mp
.get("vdu-metric"):
652 for vdu
in get_iterable(indata
.get("vdu")):
653 if vdu
.get("vdu-configuration"):
654 for metric
in get_iterable(vdu
["vdu-configuration"].get("metrics")):
655 if metric
["name"] == mp
["vdu-metric"]["vdu-metric-name-ref"] and vdu
["id"] == \
656 mp
["vdu-metric"]["vdu-ref"]:
662 raise EngineException("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined at "
663 "vdu[id='{}'] or vdu does not exist"
664 .format(mp
["vdu-metric"]["vdu-metric-name-ref"],
665 mp
["vdu-metric"]["vdu-ref"]),
666 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
668 for sgd
in get_iterable(indata
.get("scaling-group-descriptor")):
669 for sp
in get_iterable(sgd
.get("scaling-policy")):
670 for sc
in get_iterable(sp
.get("scaling-criteria")):
671 for mp
in get_iterable(indata
.get("monitoring-param")):
672 if mp
["id"] == get_iterable(sc
.get("vnf-monitoring-param-ref")):
675 raise EngineException("scaling-group-descriptor[name='{}']:scaling-criteria[name='{}']:"
676 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param"
677 .format(sgd
["name"], sc
["name"], sc
["vnf-monitoring-param-ref"]),
678 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
679 for sgd_vdu
in get_iterable(sgd
.get("vdu")):
681 for vdu
in get_iterable(indata
.get("vdu")):
682 if vdu
["id"] == sgd_vdu
["vdu-id-ref"]:
688 raise EngineException("scaling-group-descriptor[name='{}']:vdu-id-ref={} does not match any vdu"
689 .format(sgd
["name"], sgd_vdu
["vdu-id-ref"]),
690 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
691 for sca
in get_iterable(sgd
.get("scaling-config-action")):
692 if not indata
.get("vnf-configuration"):
693 raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced by "
694 "scaling-group-descriptor[name='{}']:scaling-config-action"
695 .format(sgd
["name"]),
696 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
697 for primitive
in get_iterable(indata
["vnf-configuration"].get("config-primitive")):
698 if primitive
["name"] == sca
["vnf-config-primitive-name-ref"]:
701 raise EngineException("scaling-group-descriptor[name='{}']:scaling-config-action:vnf-config-"
702 "primitive-name-ref='{}' does not match any "
703 "vnf-configuration:config-primitive:name"
704 .format(sgd
["name"], sca
["vnf-config-primitive-name-ref"]),
705 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
708 def _validate_package_folders(self
, storage_params
, folder
, file=None):
709 if not storage_params
or not storage_params
.get("pkg-dir"):
712 if self
.fs
.file_exists("{}_".format(storage_params
["folder"]), 'dir'):
713 f
= "{}_/{}/{}".format(storage_params
["folder"], storage_params
["pkg-dir"], folder
)
715 f
= "{}/{}/{}".format(storage_params
["folder"], storage_params
["pkg-dir"], folder
)
717 return self
.fs
.file_exists("{}/{}".format(f
, file), 'file')
719 if self
.fs
.file_exists(f
, 'dir'):
720 if self
.fs
.dir_ls(f
):
724 def delete_extra(self
, session
, _id
, db_content
, not_send_msg
=None):
726 Deletes associate file system storage (via super)
727 Deletes associated vnfpkgops from database.
728 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
729 :param _id: server internal id
730 :param db_content: The database content of the descriptor
732 :raises: FsException in case of error while deleting associated storage
734 super().delete_extra(session
, _id
, db_content
, not_send_msg
)
735 self
.db
.del_list("vnfpkgops", {"vnfPkgId": _id
})
738 class NsdTopic(DescriptorTopic
):
742 def __init__(self
, db
, fs
, msg
, auth
):
743 DescriptorTopic
.__init
__(self
, db
, fs
, msg
, auth
)
746 def _remove_envelop(indata
=None):
749 clean_indata
= indata
751 if clean_indata
.get('nsd:nsd-catalog'):
752 clean_indata
= clean_indata
['nsd:nsd-catalog']
753 elif clean_indata
.get('nsd-catalog'):
754 clean_indata
= clean_indata
['nsd-catalog']
755 if clean_indata
.get('nsd'):
756 if not isinstance(clean_indata
['nsd'], list) or len(clean_indata
['nsd']) != 1:
757 raise EngineException("'nsd' must be a list of only one element")
758 clean_indata
= clean_indata
['nsd'][0]
759 elif clean_indata
.get('nsd:nsd'):
760 if not isinstance(clean_indata
['nsd:nsd'], list) or len(clean_indata
['nsd:nsd']) != 1:
761 raise EngineException("'nsd:nsd' must be a list of only one element")
762 clean_indata
= clean_indata
['nsd:nsd'][0]
765 def _validate_input_new(self
, indata
, storage_params
, force
=False):
766 indata
= self
.pyangbind_validation("nsds", indata
, force
)
767 # Cross references validation in the descriptor
768 # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
769 for vld
in get_iterable(indata
.get("vld")):
770 if vld
.get("mgmt-network") and vld
.get("ip-profile-ref"):
771 raise EngineException("Error at vld[id='{}']:ip-profile-ref"
772 " You cannot set an ip-profile when mgmt-network is True"
773 .format(vld
["id"]), http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
774 for vnfd_cp
in get_iterable(vld
.get("vnfd-connection-point-ref")):
775 for constituent_vnfd
in get_iterable(indata
.get("constituent-vnfd")):
776 if vnfd_cp
["member-vnf-index-ref"] == constituent_vnfd
["member-vnf-index"]:
777 if vnfd_cp
.get("vnfd-id-ref") and vnfd_cp
["vnfd-id-ref"] != constituent_vnfd
["vnfd-id-ref"]:
778 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[vnfd-id-ref='{}'] "
779 "does not match constituent-vnfd[member-vnf-index='{}']:vnfd-id-ref"
780 " '{}'".format(vld
["id"], vnfd_cp
["vnfd-id-ref"],
781 constituent_vnfd
["member-vnf-index"],
782 constituent_vnfd
["vnfd-id-ref"]),
783 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
786 raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] "
787 "does not match any constituent-vnfd:member-vnf-index"
788 .format(vld
["id"], vnfd_cp
["member-vnf-index-ref"]),
789 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
791 for fgd
in get_iterable(indata
.get("vnffgd")):
792 for cls
in get_iterable(fgd
.get("classifier")):
793 rspref
= cls
.get("rsp-id-ref")
794 for rsp
in get_iterable(fgd
.get("rsp")):
795 rspid
= rsp
.get("id")
796 if rspid
and rspref
and rspid
== rspref
:
799 raise EngineException(
800 "Error at vnffgd[id='{}']:classifier[id='{}']:rsp-id-ref '{}' does not match any rsp:id"
801 .format(fgd
["id"], cls
["id"], rspref
),
802 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
805 def _validate_input_edit(self
, indata
, content
, force
=False):
806 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
808 indata looks as follows:
809 - In the new case (conformant)
810 {'nsdOperationalState': 'DISABLED', 'userDefinedData': {'id': 'string23',
811 '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}}
812 - In the old case (backwards-compatible)
813 {'id': 'string23', '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}
815 if "_admin" not in indata
:
816 indata
["_admin"] = {}
818 if "nsdOperationalState" in indata
:
819 if indata
["nsdOperationalState"] in ("ENABLED", "DISABLED"):
820 indata
["_admin"]["operationalState"] = indata
.pop("nsdOperationalState")
822 raise EngineException("State '{}' is not a valid operational state"
823 .format(indata
["nsdOperationalState"]),
824 http_code
=HTTPStatus
.BAD_REQUEST
)
826 # In the case of user defined data, we need to put the data in the root of the object
827 # to preserve current expected behaviour
828 if "userDefinedData" in indata
:
829 data
= indata
.pop("userDefinedData")
830 if type(data
) == dict:
831 indata
["_admin"]["userDefinedData"] = data
833 raise EngineException("userDefinedData should be an object, but is '{}' instead"
835 http_code
=HTTPStatus
.BAD_REQUEST
)
836 if ("operationalState" in indata
["_admin"] and
837 content
["_admin"]["operationalState"] == indata
["_admin"]["operationalState"]):
838 raise EngineException("nsdOperationalState already {}".format(content
["_admin"]["operationalState"]),
839 http_code
=HTTPStatus
.CONFLICT
)
842 def _check_descriptor_dependencies(self
, session
, descriptor
):
844 Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
845 connection points are ok
846 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
847 :param descriptor: descriptor to be inserted or edit
848 :return: None or raises exception
852 member_vnfd_index
= {}
853 if descriptor
.get("constituent-vnfd") and not session
["force"]:
854 for vnf
in descriptor
["constituent-vnfd"]:
855 vnfd_id
= vnf
["vnfd-id-ref"]
856 filter_q
= self
._get
_project
_filter
(session
)
857 filter_q
["id"] = vnfd_id
858 vnf_list
= self
.db
.get_list("vnfds", filter_q
)
860 raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non "
861 "existing vnfd".format(vnfd_id
), http_code
=HTTPStatus
.CONFLICT
)
862 # elif len(vnf_list) > 1:
863 # raise EngineException("More than one vnfd found for id='{}'".format(vnfd_id),
864 # http_code=HTTPStatus.CONFLICT)
865 member_vnfd_index
[vnf
["member-vnf-index"]] = vnf_list
[0]
867 # Cross references validation in the descriptor and vnfd connection point validation
868 for vld
in get_iterable(descriptor
.get("vld")):
869 for referenced_vnfd_cp
in get_iterable(vld
.get("vnfd-connection-point-ref")):
870 # look if this vnfd contains this connection point
871 vnfd
= member_vnfd_index
.get(referenced_vnfd_cp
["member-vnf-index-ref"])
872 for vnfd_cp
in get_iterable(vnfd
.get("connection-point")):
873 if referenced_vnfd_cp
.get("vnfd-connection-point-ref") == vnfd_cp
["name"]:
876 raise EngineException(
877 "Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}']:vnfd-"
878 "connection-point-ref='{}' references a non existing conection-point:name inside vnfd '{}'"
879 .format(vld
["id"], referenced_vnfd_cp
["member-vnf-index-ref"],
880 referenced_vnfd_cp
["vnfd-connection-point-ref"], vnfd
["id"]),
881 http_code
=HTTPStatus
.UNPROCESSABLE_ENTITY
)
883 def check_conflict_on_edit(self
, session
, final_content
, edit_content
, _id
):
884 super().check_conflict_on_edit(session
, final_content
, edit_content
, _id
)
886 self
._check
_descriptor
_dependencies
(session
, final_content
)
888 def check_conflict_on_del(self
, session
, _id
, db_content
):
890 Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note
891 that NSD can be public and be used by other projects.
892 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
893 :param _id: nsd internal id
894 :param db_content: The database content of the _id
895 :return: None or raises EngineException with the conflict
899 descriptor
= db_content
900 descriptor_id
= descriptor
.get("id")
901 if not descriptor_id
: # empty nsd not uploaded
904 # check NSD used by NS
905 _filter
= self
._get
_project
_filter
(session
)
906 _filter
["nsd-id"] = _id
907 if self
.db
.get_list("nsrs", _filter
):
908 raise EngineException("There is at least one NS using this descriptor", http_code
=HTTPStatus
.CONFLICT
)
910 # check NSD referenced by NST
911 del _filter
["nsd-id"]
912 _filter
["netslice-subnet.ANYINDEX.nsd-ref"] = descriptor_id
913 if self
.db
.get_list("nsts", _filter
):
914 raise EngineException("There is at least one NetSlice Template referencing this descriptor",
915 http_code
=HTTPStatus
.CONFLICT
)
918 class NstTopic(DescriptorTopic
):
921 quota_name
= "slice_templates"
923 def __init__(self
, db
, fs
, msg
, auth
):
924 DescriptorTopic
.__init
__(self
, db
, fs
, msg
, auth
)
927 def _remove_envelop(indata
=None):
930 clean_indata
= indata
932 if clean_indata
.get('nst'):
933 if not isinstance(clean_indata
['nst'], list) or len(clean_indata
['nst']) != 1:
934 raise EngineException("'nst' must be a list only one element")
935 clean_indata
= clean_indata
['nst'][0]
936 elif clean_indata
.get('nst:nst'):
937 if not isinstance(clean_indata
['nst:nst'], list) or len(clean_indata
['nst:nst']) != 1:
938 raise EngineException("'nst:nst' must be a list only one element")
939 clean_indata
= clean_indata
['nst:nst'][0]
942 def _validate_input_new(self
, indata
, storage_params
, force
=False):
943 indata
= self
.pyangbind_validation("nsts", indata
, force
)
946 def _check_descriptor_dependencies(self
, session
, descriptor
):
948 Check that the dependent descriptors exist on a new descriptor or edition
949 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
950 :param descriptor: descriptor to be inserted or edit
951 :return: None or raises exception
953 if not descriptor
.get("netslice-subnet"):
955 for nsd
in descriptor
["netslice-subnet"]:
956 nsd_id
= nsd
["nsd-ref"]
957 filter_q
= self
._get
_project
_filter
(session
)
958 filter_q
["id"] = nsd_id
959 if not self
.db
.get_list("nsds", filter_q
):
960 raise EngineException("Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non "
961 "existing nsd".format(nsd_id
), http_code
=HTTPStatus
.CONFLICT
)
963 def check_conflict_on_edit(self
, session
, final_content
, edit_content
, _id
):
964 super().check_conflict_on_edit(session
, final_content
, edit_content
, _id
)
966 self
._check
_descriptor
_dependencies
(session
, final_content
)
968 def check_conflict_on_del(self
, session
, _id
, db_content
):
970 Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note
971 that NST can be public and be used by other projects.
972 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
973 :param _id: nst internal id
974 :param db_content: The database content of the _id.
975 :return: None or raises EngineException with the conflict
977 # TODO: Check this method
980 # Get Network Slice Template from Database
981 _filter
= self
._get
_project
_filter
(session
)
982 _filter
["_admin.nst-id"] = _id
983 if self
.db
.get_list("nsis", _filter
):
984 raise EngineException("there is at least one Netslice Instance using this descriptor",
985 http_code
=HTTPStatus
.CONFLICT
)
988 class PduTopic(BaseTopic
):
992 schema_new
= pdu_new_schema
993 schema_edit
= pdu_edit_schema
995 def __init__(self
, db
, fs
, msg
, auth
):
996 BaseTopic
.__init
__(self
, db
, fs
, msg
, auth
)
999 def format_on_new(content
, project_id
=None, make_public
=False):
1000 BaseTopic
.format_on_new(content
, project_id
=project_id
, make_public
=make_public
)
1001 content
["_admin"]["onboardingState"] = "CREATED"
1002 content
["_admin"]["operationalState"] = "ENABLED"
1003 content
["_admin"]["usageState"] = "NOT_IN_USE"
1005 def check_conflict_on_del(self
, session
, _id
, db_content
):
1007 Check that there is not any vnfr that uses this PDU
1008 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1009 :param _id: pdu internal id
1010 :param db_content: The database content of the _id.
1011 :return: None or raises EngineException with the conflict
1013 if session
["force"]:
1016 _filter
= self
._get
_project
_filter
(session
)
1017 _filter
["vdur.pdu-id"] = _id
1018 if self
.db
.get_list("vnfrs", _filter
):
1019 raise EngineException("There is at least one VNF using this PDU", http_code
=HTTPStatus
.CONFLICT
)
1022 class VnfPkgOpTopic(BaseTopic
):
1025 schema_new
= vnfpkgop_new_schema
1028 def __init__(self
, db
, fs
, msg
, auth
):
1029 BaseTopic
.__init
__(self
, db
, fs
, msg
, auth
)
1031 def edit(self
, session
, _id
, indata
=None, kwargs
=None, content
=None):
1032 raise EngineException("Method 'edit' not allowed for topic '{}'".format(self
.topic
),
1033 HTTPStatus
.METHOD_NOT_ALLOWED
)
1035 def delete(self
, session
, _id
, dry_run
=False):
1036 raise EngineException("Method 'delete' not allowed for topic '{}'".format(self
.topic
),
1037 HTTPStatus
.METHOD_NOT_ALLOWED
)
1039 def delete_list(self
, session
, filter_q
=None):
1040 raise EngineException("Method 'delete_list' not allowed for topic '{}'".format(self
.topic
),
1041 HTTPStatus
.METHOD_NOT_ALLOWED
)
1043 def new(self
, rollback
, session
, indata
=None, kwargs
=None, headers
=None):
1045 Creates a new entry into database.
1046 :param rollback: list to append created items at database in case a rollback may to be done
1047 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1048 :param indata: data to be inserted
1049 :param kwargs: used to override the indata descriptor
1050 :param headers: http request headers
1051 :return: _id, op_id:
1052 _id: identity of the inserted data.
1055 self
._update
_input
_with
_kwargs
(indata
, kwargs
)
1056 validate_input(indata
, self
.schema_new
)
1057 vnfpkg_id
= indata
["vnfPkgId"]
1058 filter_q
= BaseTopic
._get
_project
_filter
(session
)
1059 filter_q
["_id"] = vnfpkg_id
1060 vnfd
= self
.db
.get_one("vnfds", filter_q
)
1061 operation
= indata
["lcmOperationType"]
1062 kdu_name
= indata
["kdu_name"]
1063 for kdu
in vnfd
.get("kdu", []):
1064 if kdu
["name"] == kdu_name
:
1065 helm_chart
= kdu
.get("helm-chart")
1066 juju_bundle
= kdu
.get("juju-bundle")
1069 raise EngineException("Not found vnfd[id='{}']:kdu[name='{}']".format(vnfpkg_id
, kdu_name
))
1071 indata
["helm-chart"] = helm_chart
1072 match
= fullmatch(r
"([^/]*)/([^/]*)", helm_chart
)
1073 repo_name
= match
.group(1) if match
else None
1075 indata
["juju-bundle"] = juju_bundle
1076 match
= fullmatch(r
"([^/]*)/([^/]*)", juju_bundle
)
1077 repo_name
= match
.group(1) if match
else None
1079 raise EngineException("Found neither 'helm-chart' nor 'juju-bundle' in vnfd[id='{}']:kdu[name='{}']"
1080 .format(vnfpkg_id
, kdu_name
))
1083 filter_q
["name"] = repo_name
1084 repo
= self
.db
.get_one("k8srepos", filter_q
)
1085 k8srepo_id
= repo
.get("_id")
1086 k8srepo_url
= repo
.get("url")
1090 indata
["k8srepoId"] = k8srepo_id
1091 indata
["k8srepo_url"] = k8srepo_url
1092 vnfpkgop_id
= str(uuid4())
1095 "operationState": "PROCESSING",
1096 "vnfPkgId": vnfpkg_id
,
1097 "lcmOperationType": operation
,
1098 "isAutomaticInvocation": False,
1099 "isCancelPending": False,
1100 "operationParams": indata
,
1102 "self": "/osm/vnfpkgm/v1/vnfpkg_op_occs/" + vnfpkgop_id
,
1103 "vnfpkg": "/osm/vnfpkgm/v1/vnf_packages/" + vnfpkg_id
,
1106 self
.format_on_new(vnfpkgop_desc
, session
["project_id"], make_public
=session
["public"])
1107 ctime
= vnfpkgop_desc
["_admin"]["created"]
1108 vnfpkgop_desc
["statusEnteredTime"] = ctime
1109 vnfpkgop_desc
["startTime"] = ctime
1110 self
.db
.create(self
.topic
, vnfpkgop_desc
)
1111 rollback
.append({"topic": self
.topic
, "_id": vnfpkgop_id
})
1112 self
.msg
.write(self
.topic_msg
, operation
, vnfpkgop_desc
)
1113 return vnfpkgop_id
, None