blob: 28c923e9bbf4771f6c931514289abb66b941c016 [file] [log] [blame]
tiernob24258a2018-10-04 18:39:49 +02001# -*- coding: utf-8 -*-
2
tiernod125caf2018-11-22 16:05:54 +00003# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
tiernob24258a2018-10-04 18:39:49 +020016import tarfile
17import yaml
18import json
bravofb995ea22021-02-10 10:57:52 -030019import copy
beierlmcee2ebf2022-03-29 17:42:48 -040020import os
21import shutil
aticig9cfa8162022-04-07 11:57:18 +030022import functools
Daniel Arndt00f83aa2023-06-15 16:43:33 +020023import re
garciadeblas4568a372021-03-24 09:19:48 +010024
tiernob24258a2018-10-04 18:39:49 +020025# import logging
aticig9cfa8162022-04-07 11:57:18 +030026from deepdiff import DeepDiff
tiernob24258a2018-10-04 18:39:49 +020027from hashlib import md5
28from osm_common.dbbase import DbException, deep_update_rfc7396
29from http import HTTPStatus
delacruzramo26301bb2019-11-15 14:45:32 +010030from time import time
delacruzramo271d2002019-12-02 21:00:37 +010031from uuid import uuid4
32from re import fullmatch
bravofc26740a2021-11-08 09:44:54 -030033from zipfile import ZipFile
Gabriel Cuba646773d2023-11-20 01:43:05 -050034from urllib.parse import urlparse
garciadeblas4568a372021-03-24 09:19:48 +010035from osm_nbi.validation import (
36 ValidationError,
37 pdu_new_schema,
38 pdu_edit_schema,
39 validate_input,
40 vnfpkgop_new_schema,
kayal2001f71c2e82024-06-25 15:26:24 +053041 ns_config_template,
42 vnf_schema,
43 vld_schema,
44 additional_params_for_vnf,
garciadeblas4568a372021-03-24 09:19:48 +010045)
aticig2b5e1232022-08-10 17:30:12 +030046from osm_nbi.base_topic import (
47 BaseTopic,
48 EngineException,
49 get_iterable,
50 detect_descriptor_usage,
51)
sousaedu317b9fd2021-07-29 17:40:16 +020052from osm_im import etsi_nfv_vnfd, etsi_nfv_nsd
gcalvino70434c12018-11-27 15:17:04 +010053from osm_im.nst import nst as nst_im
gcalvino46e4cb82018-10-26 13:10:22 +020054from pyangbind.lib.serialise import pybindJSONDecoder
55import pyangbind.lib.pybindJSON as pybindJSON
bravof41a52052021-02-17 18:08:01 -030056from osm_nbi import utils
tiernob24258a2018-10-04 18:39:49 +020057
58__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
59
Daniel Arndt00f83aa2023-06-15 16:43:33 +020060valid_helm_chart_re = re.compile(
61 r"^[a-z0-9]([-a-z0-9]*[a-z0-9]/)?([a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"
62)
63
tiernob24258a2018-10-04 18:39:49 +020064
65class DescriptorTopic(BaseTopic):
delacruzramo32bab472019-09-13 12:24:22 +020066 def __init__(self, db, fs, msg, auth):
Daniel Arndt00f83aa2023-06-15 16:43:33 +020067 super().__init__(db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +020068
garciadeblasf2af4a12023-01-24 16:56:54 +010069 def _validate_input_new(self, indata, storage_params, force=False):
70 return indata
71
tierno65ca36d2019-02-12 19:27:52 +010072 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
garciadeblas4568a372021-03-24 09:19:48 +010073 final_content = super().check_conflict_on_edit(
74 session, final_content, edit_content, _id
75 )
K Sai Kiran45bd94c2019-11-25 17:30:37 +053076
77 def _check_unique_id_name(descriptor, position=""):
78 for desc_key, desc_item in descriptor.items():
79 if isinstance(desc_item, list) and desc_item:
80 used_ids = []
81 desc_item_id = None
82 for index, list_item in enumerate(desc_item):
83 if isinstance(list_item, dict):
garciadeblas4568a372021-03-24 09:19:48 +010084 _check_unique_id_name(
85 list_item, "{}.{}[{}]".format(position, desc_key, index)
86 )
K Sai Kiran45bd94c2019-11-25 17:30:37 +053087 # Base case
garciadeblas4568a372021-03-24 09:19:48 +010088 if index == 0 and (
89 list_item.get("id") or list_item.get("name")
90 ):
K Sai Kiran45bd94c2019-11-25 17:30:37 +053091 desc_item_id = "id" if list_item.get("id") else "name"
92 if desc_item_id and list_item.get(desc_item_id):
93 if list_item[desc_item_id] in used_ids:
garciadeblas4568a372021-03-24 09:19:48 +010094 position = "{}.{}[{}]".format(
95 position, desc_key, index
96 )
97 raise EngineException(
98 "Error: identifier {} '{}' is not unique and repeats at '{}'".format(
99 desc_item_id,
100 list_item[desc_item_id],
101 position,
102 ),
103 HTTPStatus.UNPROCESSABLE_ENTITY,
104 )
K Sai Kiran45bd94c2019-11-25 17:30:37 +0530105 used_ids.append(list_item[desc_item_id])
garciaale960531a2020-10-20 18:29:45 -0300106
K Sai Kiran45bd94c2019-11-25 17:30:37 +0530107 _check_unique_id_name(final_content)
tiernoaa1ca7b2018-11-08 19:00:20 +0100108 # 1. validate again with pyangbind
109 # 1.1. remove internal keys
110 internal_keys = {}
111 for k in ("_id", "_admin"):
112 if k in final_content:
113 internal_keys[k] = final_content.pop(k)
gcalvinoa6fe0002019-01-09 13:27:11 +0100114 storage_params = internal_keys["_admin"].get("storage")
garciadeblas4568a372021-03-24 09:19:48 +0100115 serialized = self._validate_input_new(
116 final_content, storage_params, session["force"]
117 )
bravofb995ea22021-02-10 10:57:52 -0300118
tiernoaa1ca7b2018-11-08 19:00:20 +0100119 # 1.2. modify final_content with a serialized version
bravofb995ea22021-02-10 10:57:52 -0300120 final_content = copy.deepcopy(serialized)
tiernoaa1ca7b2018-11-08 19:00:20 +0100121 # 1.3. restore internal keys
122 for k, v in internal_keys.items():
123 final_content[k] = v
tierno65ca36d2019-02-12 19:27:52 +0100124 if session["force"]:
bravofb995ea22021-02-10 10:57:52 -0300125 return final_content
126
tiernoaa1ca7b2018-11-08 19:00:20 +0100127 # 2. check that this id is not present
128 if "id" in edit_content:
tierno65ca36d2019-02-12 19:27:52 +0100129 _filter = self._get_project_filter(session)
bravofb995ea22021-02-10 10:57:52 -0300130
tiernoaa1ca7b2018-11-08 19:00:20 +0100131 _filter["id"] = final_content["id"]
132 _filter["_id.neq"] = _id
bravofb995ea22021-02-10 10:57:52 -0300133
tiernoaa1ca7b2018-11-08 19:00:20 +0100134 if self.db.get_one(self.topic, _filter, fail_on_empty=False):
garciadeblas4568a372021-03-24 09:19:48 +0100135 raise EngineException(
136 "{} with id '{}' already exists for this project".format(
garciadeblasf2af4a12023-01-24 16:56:54 +0100137 (str(self.topic))[:-1], final_content["id"]
garciadeblas4568a372021-03-24 09:19:48 +0100138 ),
139 HTTPStatus.CONFLICT,
140 )
tiernob24258a2018-10-04 18:39:49 +0200141
bravofb995ea22021-02-10 10:57:52 -0300142 return final_content
143
tiernob24258a2018-10-04 18:39:49 +0200144 @staticmethod
145 def format_on_new(content, project_id=None, make_public=False):
146 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
147 content["_admin"]["onboardingState"] = "CREATED"
148 content["_admin"]["operationalState"] = "DISABLED"
tierno36ec8602018-11-02 17:27:11 +0100149 content["_admin"]["usageState"] = "NOT_IN_USE"
tiernob24258a2018-10-04 18:39:49 +0200150
tiernobee3bad2019-12-05 12:26:01 +0000151 def delete_extra(self, session, _id, db_content, not_send_msg=None):
tiernob4844ab2019-05-23 08:42:12 +0000152 """
153 Deletes file system storage associated with the descriptor
154 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
155 :param _id: server internal id
156 :param db_content: The database content of the descriptor
tiernobee3bad2019-12-05 12:26:01 +0000157 :param not_send_msg: To not send message (False) or store content (list) instead
tiernob4844ab2019-05-23 08:42:12 +0000158 :return: None if ok or raises EngineException with the problem
159 """
tiernob24258a2018-10-04 18:39:49 +0200160 self.fs.file_delete(_id, ignore_non_exist=True)
tiernof717cbe2018-12-03 16:35:42 +0000161 self.fs.file_delete(_id + "_", ignore_non_exist=True) # remove temp folder
beierlmcee2ebf2022-03-29 17:42:48 -0400162 # Remove file revisions
163 if "revision" in db_content["_admin"]:
164 revision = db_content["_admin"]["revision"]
165 while revision > 0:
166 self.fs.file_delete(_id + ":" + str(revision), ignore_non_exist=True)
167 revision = revision - 1
168
tiernob24258a2018-10-04 18:39:49 +0200169 @staticmethod
170 def get_one_by_id(db, session, topic, id):
171 # find owned by this project
tierno65ca36d2019-02-12 19:27:52 +0100172 _filter = BaseTopic._get_project_filter(session)
tiernob24258a2018-10-04 18:39:49 +0200173 _filter["id"] = id
174 desc_list = db.get_list(topic, _filter)
175 if len(desc_list) == 1:
176 return desc_list[0]
177 elif len(desc_list) > 1:
garciadeblas4568a372021-03-24 09:19:48 +0100178 raise DbException(
179 "Found more than one {} with id='{}' belonging to this project".format(
180 topic[:-1], id
181 ),
182 HTTPStatus.CONFLICT,
183 )
tiernob24258a2018-10-04 18:39:49 +0200184
185 # not found any: try to find public
tierno65ca36d2019-02-12 19:27:52 +0100186 _filter = BaseTopic._get_project_filter(session)
tiernob24258a2018-10-04 18:39:49 +0200187 _filter["id"] = id
188 desc_list = db.get_list(topic, _filter)
189 if not desc_list:
garciadeblas4568a372021-03-24 09:19:48 +0100190 raise DbException(
191 "Not found any {} with id='{}'".format(topic[:-1], id),
192 HTTPStatus.NOT_FOUND,
193 )
tiernob24258a2018-10-04 18:39:49 +0200194 elif len(desc_list) == 1:
195 return desc_list[0]
196 else:
garciadeblas4568a372021-03-24 09:19:48 +0100197 raise DbException(
198 "Found more than one public {} with id='{}'; and no one belonging to this project".format(
199 topic[:-1], id
200 ),
201 HTTPStatus.CONFLICT,
202 )
tiernob24258a2018-10-04 18:39:49 +0200203
tierno65ca36d2019-02-12 19:27:52 +0100204 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
tiernob24258a2018-10-04 18:39:49 +0200205 """
206 Creates a new almost empty DISABLED entry into database. Due to SOL005, it does not follow normal procedure.
207 Creating a VNFD or NSD is done in two steps: 1. Creates an empty descriptor (this step) and 2) upload content
208 (self.upload_content)
209 :param rollback: list to append created items at database in case a rollback may to be done
tierno65ca36d2019-02-12 19:27:52 +0100210 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200211 :param indata: data to be inserted
212 :param kwargs: used to override the indata descriptor
213 :param headers: http request headers
tiernobdebce92019-07-01 15:36:49 +0000214 :return: _id, None: identity of the inserted data; and None as there is not any operation
tiernob24258a2018-10-04 18:39:49 +0200215 """
216
tiernod7749582020-05-28 10:41:10 +0000217 # No needed to capture exceptions
218 # Check Quota
219 self.check_quota(session)
delacruzramo32bab472019-09-13 12:24:22 +0200220
tiernod7749582020-05-28 10:41:10 +0000221 # _remove_envelop
222 if indata:
223 if "userDefinedData" in indata:
garciadeblas4568a372021-03-24 09:19:48 +0100224 indata = indata["userDefinedData"]
tiernob24258a2018-10-04 18:39:49 +0200225
tiernod7749582020-05-28 10:41:10 +0000226 # Override descriptor with query string kwargs
227 self._update_input_with_kwargs(indata, kwargs)
228 # uncomment when this method is implemented.
229 # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors
230 # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"])
tiernob24258a2018-10-04 18:39:49 +0200231
garciadeblasf2af4a12023-01-24 16:56:54 +0100232 content = {"_admin": {"userDefinedData": indata, "revision": 0}}
beierlmcee2ebf2022-03-29 17:42:48 -0400233
garciadeblas4568a372021-03-24 09:19:48 +0100234 self.format_on_new(
235 content, session["project_id"], make_public=session["public"]
236 )
tiernod7749582020-05-28 10:41:10 +0000237 _id = self.db.create(self.topic, content)
238 rollback.append({"topic": self.topic, "_id": _id})
239 self._send_msg("created", {"_id": _id})
240 return _id, None
tiernob24258a2018-10-04 18:39:49 +0200241
tierno65ca36d2019-02-12 19:27:52 +0100242 def upload_content(self, session, _id, indata, kwargs, headers):
tiernob24258a2018-10-04 18:39:49 +0200243 """
244 Used for receiving content by chunks (with a transaction_id header and/or gzip file. It will store and extract)
tierno65ca36d2019-02-12 19:27:52 +0100245 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200246 :param _id : the nsd,vnfd is already created, this is the id
247 :param indata: http body request
248 :param kwargs: user query string to override parameters. NOT USED
249 :param headers: http request headers
tierno5a5c2182018-11-20 12:27:42 +0000250 :return: True if package is completely uploaded or False if partial content has been uploded
tiernob24258a2018-10-04 18:39:49 +0200251 Raise exception on error
252 """
253 # Check that _id exists and it is valid
254 current_desc = self.show(session, _id)
255
256 content_range_text = headers.get("Content-Range")
257 expected_md5 = headers.get("Content-File-MD5")
258 compressed = None
259 content_type = headers.get("Content-Type")
garciadeblas4568a372021-03-24 09:19:48 +0100260 if (
261 content_type
262 and "application/gzip" in content_type
263 or "application/x-gzip" in content_type
garciadeblas4568a372021-03-24 09:19:48 +0100264 ):
tiernob24258a2018-10-04 18:39:49 +0200265 compressed = "gzip"
garciadeblasf2af4a12023-01-24 16:56:54 +0100266 if content_type and "application/zip" in content_type:
bravofc26740a2021-11-08 09:44:54 -0300267 compressed = "zip"
tiernob24258a2018-10-04 18:39:49 +0200268 filename = headers.get("Content-Filename")
bravofc26740a2021-11-08 09:44:54 -0300269 if not filename and compressed:
270 filename = "package.tar.gz" if compressed == "gzip" else "package.zip"
271 elif not filename:
272 filename = "package"
273
beierlmcee2ebf2022-03-29 17:42:48 -0400274 revision = 1
275 if "revision" in current_desc["_admin"]:
276 revision = current_desc["_admin"]["revision"] + 1
277
tiernob24258a2018-10-04 18:39:49 +0200278 # TODO change to Content-Disposition filename https://tools.ietf.org/html/rfc6266
279 file_pkg = None
280 error_text = ""
beierlmbc5a5242022-05-17 21:25:29 -0400281 fs_rollback = []
282
tiernob24258a2018-10-04 18:39:49 +0200283 try:
284 if content_range_text:
garciadeblas4568a372021-03-24 09:19:48 +0100285 content_range = (
286 content_range_text.replace("-", " ").replace("/", " ").split()
287 )
288 if (
289 content_range[0] != "bytes"
290 ): # TODO check x<y not negative < total....
tiernob24258a2018-10-04 18:39:49 +0200291 raise IndexError()
292 start = int(content_range[1])
293 end = int(content_range[2]) + 1
294 total = int(content_range[3])
295 else:
296 start = 0
beierlmcee2ebf2022-03-29 17:42:48 -0400297 # Rather than using a temp folder, we will store the package in a folder based on
298 # the current revision.
299 proposed_revision_path = (
300 _id + ":" + str(revision)
garciadeblas4568a372021-03-24 09:19:48 +0100301 ) # all the content is upload here and if ok, it is rename from id_ to is folder
tiernob24258a2018-10-04 18:39:49 +0200302
303 if start:
beierlmcee2ebf2022-03-29 17:42:48 -0400304 if not self.fs.file_exists(proposed_revision_path, "dir"):
garciadeblas4568a372021-03-24 09:19:48 +0100305 raise EngineException(
306 "invalid Transaction-Id header", HTTPStatus.NOT_FOUND
307 )
tiernob24258a2018-10-04 18:39:49 +0200308 else:
beierlmcee2ebf2022-03-29 17:42:48 -0400309 self.fs.file_delete(proposed_revision_path, ignore_non_exist=True)
310 self.fs.mkdir(proposed_revision_path)
beierlmbc5a5242022-05-17 21:25:29 -0400311 fs_rollback.append(proposed_revision_path)
tiernob24258a2018-10-04 18:39:49 +0200312
313 storage = self.fs.get_params()
beierlmbc5a5242022-05-17 21:25:29 -0400314 storage["folder"] = proposed_revision_path
tiernob24258a2018-10-04 18:39:49 +0200315
beierlmcee2ebf2022-03-29 17:42:48 -0400316 file_path = (proposed_revision_path, filename)
garciadeblas4568a372021-03-24 09:19:48 +0100317 if self.fs.file_exists(file_path, "file"):
tiernob24258a2018-10-04 18:39:49 +0200318 file_size = self.fs.file_size(file_path)
319 else:
320 file_size = 0
321 if file_size != start:
garciadeblas4568a372021-03-24 09:19:48 +0100322 raise EngineException(
323 "invalid Content-Range start sequence, expected '{}' but received '{}'".format(
324 file_size, start
325 ),
326 HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
327 )
328 file_pkg = self.fs.file_open(file_path, "a+b")
tiernob24258a2018-10-04 18:39:49 +0200329 if isinstance(indata, dict):
330 indata_text = yaml.safe_dump(indata, indent=4, default_flow_style=False)
331 file_pkg.write(indata_text.encode(encoding="utf-8"))
332 else:
333 indata_len = 0
334 while True:
335 indata_text = indata.read(4096)
336 indata_len += len(indata_text)
337 if not indata_text:
338 break
339 file_pkg.write(indata_text)
340 if content_range_text:
garciaale960531a2020-10-20 18:29:45 -0300341 if indata_len != end - start:
garciadeblas4568a372021-03-24 09:19:48 +0100342 raise EngineException(
343 "Mismatch between Content-Range header {}-{} and body length of {}".format(
344 start, end - 1, indata_len
345 ),
346 HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
347 )
tiernob24258a2018-10-04 18:39:49 +0200348 if end != total:
349 # TODO update to UPLOADING
350 return False
351
352 # PACKAGE UPLOADED
353 if expected_md5:
354 file_pkg.seek(0, 0)
355 file_md5 = md5()
356 chunk_data = file_pkg.read(1024)
357 while chunk_data:
358 file_md5.update(chunk_data)
359 chunk_data = file_pkg.read(1024)
360 if expected_md5 != file_md5.hexdigest():
361 raise EngineException("Error, MD5 mismatch", HTTPStatus.CONFLICT)
362 file_pkg.seek(0, 0)
363 if compressed == "gzip":
garciadeblas4568a372021-03-24 09:19:48 +0100364 tar = tarfile.open(mode="r", fileobj=file_pkg)
tiernob24258a2018-10-04 18:39:49 +0200365 descriptor_file_name = None
366 for tarinfo in tar:
367 tarname = tarinfo.name
368 tarname_path = tarname.split("/")
garciadeblas4568a372021-03-24 09:19:48 +0100369 if (
370 not tarname_path[0] or ".." in tarname_path
371 ): # if start with "/" means absolute path
372 raise EngineException(
373 "Absolute path or '..' are not allowed for package descriptor tar.gz"
374 )
tiernob24258a2018-10-04 18:39:49 +0200375 if len(tarname_path) == 1 and not tarinfo.isdir():
garciadeblas4568a372021-03-24 09:19:48 +0100376 raise EngineException(
377 "All files must be inside a dir for package descriptor tar.gz"
378 )
379 if (
380 tarname.endswith(".yaml")
381 or tarname.endswith(".json")
382 or tarname.endswith(".yml")
383 ):
tiernob24258a2018-10-04 18:39:49 +0200384 storage["pkg-dir"] = tarname_path[0]
385 if len(tarname_path) == 2:
386 if descriptor_file_name:
387 raise EngineException(
garciadeblas4568a372021-03-24 09:19:48 +0100388 "Found more than one descriptor file at package descriptor tar.gz"
389 )
tiernob24258a2018-10-04 18:39:49 +0200390 descriptor_file_name = tarname
391 if not descriptor_file_name:
garciadeblas4568a372021-03-24 09:19:48 +0100392 raise EngineException(
393 "Not found any descriptor file at package descriptor tar.gz"
394 )
tiernob24258a2018-10-04 18:39:49 +0200395 storage["descriptor"] = descriptor_file_name
396 storage["zipfile"] = filename
beierlmcee2ebf2022-03-29 17:42:48 -0400397 self.fs.file_extract(tar, proposed_revision_path)
garciadeblas4568a372021-03-24 09:19:48 +0100398 with self.fs.file_open(
beierlmcee2ebf2022-03-29 17:42:48 -0400399 (proposed_revision_path, descriptor_file_name), "r"
garciadeblas4568a372021-03-24 09:19:48 +0100400 ) as descriptor_file:
tiernob24258a2018-10-04 18:39:49 +0200401 content = descriptor_file.read()
bravofc26740a2021-11-08 09:44:54 -0300402 elif compressed == "zip":
403 zipfile = ZipFile(file_pkg)
404 descriptor_file_name = None
405 for package_file in zipfile.infolist():
406 zipfilename = package_file.filename
407 file_path = zipfilename.split("/")
408 if (
409 not file_path[0] or ".." in zipfilename
410 ): # if start with "/" means absolute path
411 raise EngineException(
412 "Absolute path or '..' are not allowed for package descriptor zip"
413 )
414
415 if (
garciadeblasf2af4a12023-01-24 16:56:54 +0100416 zipfilename.endswith(".yaml")
417 or zipfilename.endswith(".json")
418 or zipfilename.endswith(".yml")
419 ) and (
420 zipfilename.find("/") < 0
421 or zipfilename.find("Definitions") >= 0
bravofc26740a2021-11-08 09:44:54 -0300422 ):
423 storage["pkg-dir"] = ""
424 if descriptor_file_name:
425 raise EngineException(
426 "Found more than one descriptor file at package descriptor zip"
427 )
428 descriptor_file_name = zipfilename
429 if not descriptor_file_name:
430 raise EngineException(
431 "Not found any descriptor file at package descriptor zip"
432 )
433 storage["descriptor"] = descriptor_file_name
434 storage["zipfile"] = filename
beierlmcee2ebf2022-03-29 17:42:48 -0400435 self.fs.file_extract(zipfile, proposed_revision_path)
bravofc26740a2021-11-08 09:44:54 -0300436
437 with self.fs.file_open(
beierlmcee2ebf2022-03-29 17:42:48 -0400438 (proposed_revision_path, descriptor_file_name), "r"
bravofc26740a2021-11-08 09:44:54 -0300439 ) as descriptor_file:
440 content = descriptor_file.read()
tiernob24258a2018-10-04 18:39:49 +0200441 else:
442 content = file_pkg.read()
443 storage["descriptor"] = descriptor_file_name = filename
444
445 if descriptor_file_name.endswith(".json"):
446 error_text = "Invalid json format "
447 indata = json.load(content)
448 else:
449 error_text = "Invalid yaml format "
garciadeblas4cd875d2023-02-14 19:05:34 +0100450 indata = yaml.safe_load(content)
tiernob24258a2018-10-04 18:39:49 +0200451
beierlmcee2ebf2022-03-29 17:42:48 -0400452 # Need to close the file package here so it can be copied from the
453 # revision to the current, unrevisioned record
454 if file_pkg:
455 file_pkg.close()
456 file_pkg = None
457
458 # Fetch both the incoming, proposed revision and the original revision so we
459 # can call a validate method to compare them
460 current_revision_path = _id + "/"
461 self.fs.sync(from_path=current_revision_path)
462 self.fs.sync(from_path=proposed_revision_path)
463
464 if revision > 1:
465 try:
466 self._validate_descriptor_changes(
aticig2b5e1232022-08-10 17:30:12 +0300467 _id,
beierlmcee2ebf2022-03-29 17:42:48 -0400468 descriptor_file_name,
469 current_revision_path,
aticig2b5e1232022-08-10 17:30:12 +0300470 proposed_revision_path,
471 )
beierlmcee2ebf2022-03-29 17:42:48 -0400472 except Exception as e:
garciadeblasf2af4a12023-01-24 16:56:54 +0100473 shutil.rmtree(
474 self.fs.path + current_revision_path, ignore_errors=True
475 )
476 shutil.rmtree(
477 self.fs.path + proposed_revision_path, ignore_errors=True
478 )
beierlmcee2ebf2022-03-29 17:42:48 -0400479 # Only delete the new revision. We need to keep the original version in place
480 # as it has not been changed.
481 self.fs.file_delete(proposed_revision_path, ignore_non_exist=True)
482 raise e
483
tiernob24258a2018-10-04 18:39:49 +0200484 indata = self._remove_envelop(indata)
485
486 # Override descriptor with query string kwargs
487 if kwargs:
488 self._update_input_with_kwargs(indata, kwargs)
tiernob24258a2018-10-04 18:39:49 +0200489
beierlmbc5a5242022-05-17 21:25:29 -0400490 current_desc["_admin"]["storage"] = storage
491 current_desc["_admin"]["onboardingState"] = "ONBOARDED"
492 current_desc["_admin"]["operationalState"] = "ENABLED"
493 current_desc["_admin"]["modified"] = time()
494 current_desc["_admin"]["revision"] = revision
495
tiernob24258a2018-10-04 18:39:49 +0200496 deep_update_rfc7396(current_desc, indata)
garciadeblas4568a372021-03-24 09:19:48 +0100497 current_desc = self.check_conflict_on_edit(
498 session, current_desc, indata, _id=_id
499 )
beierlmbc5a5242022-05-17 21:25:29 -0400500
501 # Copy the revision to the active package name by its original id
502 shutil.rmtree(self.fs.path + current_revision_path, ignore_errors=True)
garciadeblasf2af4a12023-01-24 16:56:54 +0100503 os.rename(
504 self.fs.path + proposed_revision_path,
505 self.fs.path + current_revision_path,
506 )
beierlmbc5a5242022-05-17 21:25:29 -0400507 self.fs.file_delete(current_revision_path, ignore_non_exist=True)
508 self.fs.mkdir(current_revision_path)
509 self.fs.reverse_sync(from_path=current_revision_path)
510
511 shutil.rmtree(self.fs.path + _id)
512
tiernob24258a2018-10-04 18:39:49 +0200513 self.db.replace(self.topic, _id, current_desc)
beierlmcee2ebf2022-03-29 17:42:48 -0400514
515 # Store a copy of the package as a point in time revision
516 revision_desc = dict(current_desc)
517 revision_desc["_id"] = _id + ":" + str(revision_desc["_admin"]["revision"])
518 self.db.create(self.topic + "_revisions", revision_desc)
beierlmbc5a5242022-05-17 21:25:29 -0400519 fs_rollback = []
tiernob24258a2018-10-04 18:39:49 +0200520
521 indata["_id"] = _id
K Sai Kiranc96fd692019-10-16 17:50:53 +0530522 self._send_msg("edited", indata)
tiernob24258a2018-10-04 18:39:49 +0200523
524 # TODO if descriptor has changed because kwargs update content and remove cached zip
525 # TODO if zip is not present creates one
526 return True
527
528 except EngineException:
529 raise
530 except IndexError:
garciadeblas4568a372021-03-24 09:19:48 +0100531 raise EngineException(
532 "invalid Content-Range header format. Expected 'bytes start-end/total'",
533 HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
534 )
tiernob24258a2018-10-04 18:39:49 +0200535 except IOError as e:
garciadeblas4568a372021-03-24 09:19:48 +0100536 raise EngineException(
537 "invalid upload transaction sequence: '{}'".format(e),
538 HTTPStatus.BAD_REQUEST,
539 )
tiernob24258a2018-10-04 18:39:49 +0200540 except tarfile.ReadError as e:
garciadeblas4568a372021-03-24 09:19:48 +0100541 raise EngineException(
542 "invalid file content {}".format(e), HTTPStatus.BAD_REQUEST
543 )
tiernob24258a2018-10-04 18:39:49 +0200544 except (ValueError, yaml.YAMLError) as e:
545 raise EngineException(error_text + str(e))
546 except ValidationError as e:
547 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
548 finally:
549 if file_pkg:
550 file_pkg.close()
beierlmbc5a5242022-05-17 21:25:29 -0400551 for file in fs_rollback:
552 self.fs.file_delete(file, ignore_non_exist=True)
tiernob24258a2018-10-04 18:39:49 +0200553
554 def get_file(self, session, _id, path=None, accept_header=None):
555 """
556 Return the file content of a vnfd or nsd
tierno65ca36d2019-02-12 19:27:52 +0100557 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tierno87006042018-10-24 12:50:20 +0200558 :param _id: Identity of the vnfd, nsd
tiernob24258a2018-10-04 18:39:49 +0200559 :param path: artifact path or "$DESCRIPTOR" or None
560 :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
tierno87006042018-10-24 12:50:20 +0200561 :return: opened file plus Accept format or raises an exception
tiernob24258a2018-10-04 18:39:49 +0200562 """
563 accept_text = accept_zip = False
564 if accept_header:
garciadeblas4568a372021-03-24 09:19:48 +0100565 if "text/plain" in accept_header or "*/*" in accept_header:
tiernob24258a2018-10-04 18:39:49 +0200566 accept_text = True
garciadeblas4568a372021-03-24 09:19:48 +0100567 if "application/zip" in accept_header or "*/*" in accept_header:
568 accept_zip = "application/zip"
569 elif "application/gzip" in accept_header:
570 accept_zip = "application/gzip"
tierno87006042018-10-24 12:50:20 +0200571
tiernob24258a2018-10-04 18:39:49 +0200572 if not accept_text and not accept_zip:
garciadeblas4568a372021-03-24 09:19:48 +0100573 raise EngineException(
574 "provide request header 'Accept' with 'application/zip' or 'text/plain'",
575 http_code=HTTPStatus.NOT_ACCEPTABLE,
576 )
tiernob24258a2018-10-04 18:39:49 +0200577
578 content = self.show(session, _id)
579 if content["_admin"]["onboardingState"] != "ONBOARDED":
garciadeblas4568a372021-03-24 09:19:48 +0100580 raise EngineException(
581 "Cannot get content because this resource is not at 'ONBOARDED' state. "
582 "onboardingState is {}".format(content["_admin"]["onboardingState"]),
583 http_code=HTTPStatus.CONFLICT,
584 )
tiernob24258a2018-10-04 18:39:49 +0200585 storage = content["_admin"]["storage"]
garciaale960531a2020-10-20 18:29:45 -0300586 if path is not None and path != "$DESCRIPTOR": # artifacts
selvi.j5be838c2022-08-25 06:24:49 +0000587 if not storage.get("pkg-dir") and not storage.get("folder"):
garciadeblas4568a372021-03-24 09:19:48 +0100588 raise EngineException(
589 "Packages does not contains artifacts",
590 http_code=HTTPStatus.BAD_REQUEST,
591 )
592 if self.fs.file_exists(
593 (storage["folder"], storage["pkg-dir"], *path), "dir"
594 ):
595 folder_content = self.fs.dir_ls(
596 (storage["folder"], storage["pkg-dir"], *path)
597 )
tiernob24258a2018-10-04 18:39:49 +0200598 return folder_content, "text/plain"
599 # TODO manage folders in http
600 else:
garciadeblas4568a372021-03-24 09:19:48 +0100601 return (
602 self.fs.file_open(
603 (storage["folder"], storage["pkg-dir"], *path), "rb"
604 ),
605 "application/octet-stream",
606 )
tiernob24258a2018-10-04 18:39:49 +0200607
608 # pkgtype accept ZIP TEXT -> result
609 # manyfiles yes X -> zip
610 # no yes -> error
611 # onefile yes no -> zip
612 # X yes -> text
tiernoee002752020-08-04 14:14:16 +0000613 contain_many_files = False
garciadeblas4568a372021-03-24 09:19:48 +0100614 if storage.get("pkg-dir"):
tiernoee002752020-08-04 14:14:16 +0000615 # check if there are more than one file in the package, ignoring checksums.txt.
garciadeblas4568a372021-03-24 09:19:48 +0100616 pkg_files = self.fs.dir_ls((storage["folder"], storage["pkg-dir"]))
617 if len(pkg_files) >= 3 or (
618 len(pkg_files) == 2 and "checksums.txt" not in pkg_files
619 ):
tiernoee002752020-08-04 14:14:16 +0000620 contain_many_files = True
621 if accept_text and (not contain_many_files or path == "$DESCRIPTOR"):
garciadeblas4568a372021-03-24 09:19:48 +0100622 return (
623 self.fs.file_open((storage["folder"], storage["descriptor"]), "r"),
624 "text/plain",
625 )
tiernoee002752020-08-04 14:14:16 +0000626 elif contain_many_files and not accept_zip:
garciadeblas4568a372021-03-24 09:19:48 +0100627 raise EngineException(
628 "Packages that contains several files need to be retrieved with 'application/zip'"
629 "Accept header",
630 http_code=HTTPStatus.NOT_ACCEPTABLE,
631 )
tiernob24258a2018-10-04 18:39:49 +0200632 else:
garciadeblas4568a372021-03-24 09:19:48 +0100633 if not storage.get("zipfile"):
tiernob24258a2018-10-04 18:39:49 +0200634 # TODO generate zipfile if not present
garciadeblas4568a372021-03-24 09:19:48 +0100635 raise EngineException(
636 "Only allowed 'text/plain' Accept header for this descriptor. To be solved in "
637 "future versions",
638 http_code=HTTPStatus.NOT_ACCEPTABLE,
639 )
640 return (
641 self.fs.file_open((storage["folder"], storage["zipfile"]), "rb"),
642 accept_zip,
643 )
tiernob24258a2018-10-04 18:39:49 +0200644
garciaale7cbd03c2020-11-27 10:38:35 -0300645 def _remove_yang_prefixes_from_descriptor(self, descriptor):
646 new_descriptor = {}
647 for k, v in descriptor.items():
648 new_v = v
649 if isinstance(v, dict):
650 new_v = self._remove_yang_prefixes_from_descriptor(v)
651 elif isinstance(v, list):
652 new_v = list()
653 for x in v:
654 if isinstance(x, dict):
655 new_v.append(self._remove_yang_prefixes_from_descriptor(x))
656 else:
657 new_v.append(x)
garciadeblas4568a372021-03-24 09:19:48 +0100658 new_descriptor[k.split(":")[-1]] = new_v
garciaale7cbd03c2020-11-27 10:38:35 -0300659 return new_descriptor
660
gcalvino46e4cb82018-10-26 13:10:22 +0200661 def pyangbind_validation(self, item, data, force=False):
garciadeblas4568a372021-03-24 09:19:48 +0100662 raise EngineException(
663 "Not possible to validate '{}' item".format(item),
664 http_code=HTTPStatus.INTERNAL_SERVER_ERROR,
665 )
gcalvino46e4cb82018-10-26 13:10:22 +0200666
Frank Brydendeba68e2020-07-27 13:55:11 +0000667 def _validate_input_edit(self, indata, content, force=False):
668 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
669 if "_id" in indata:
670 indata.pop("_id")
671 if "_admin" not in indata:
672 indata["_admin"] = {}
673
674 if "operationalState" in indata:
675 if indata["operationalState"] in ("ENABLED", "DISABLED"):
676 indata["_admin"]["operationalState"] = indata.pop("operationalState")
677 else:
garciadeblas4568a372021-03-24 09:19:48 +0100678 raise EngineException(
679 "State '{}' is not a valid operational state".format(
680 indata["operationalState"]
681 ),
682 http_code=HTTPStatus.BAD_REQUEST,
683 )
Frank Brydendeba68e2020-07-27 13:55:11 +0000684
garciadeblas4568a372021-03-24 09:19:48 +0100685 # In the case of user defined data, we need to put the data in the root of the object
Frank Brydendeba68e2020-07-27 13:55:11 +0000686 # to preserve current expected behaviour
687 if "userDefinedData" in indata:
688 data = indata.pop("userDefinedData")
gaticid7debb92023-07-31 14:37:32 +0300689 if isinstance(data, dict):
Frank Brydendeba68e2020-07-27 13:55:11 +0000690 indata["_admin"]["userDefinedData"] = data
691 else:
garciadeblas4568a372021-03-24 09:19:48 +0100692 raise EngineException(
693 "userDefinedData should be an object, but is '{}' instead".format(
694 type(data)
695 ),
696 http_code=HTTPStatus.BAD_REQUEST,
697 )
garciaale960531a2020-10-20 18:29:45 -0300698
garciadeblas4568a372021-03-24 09:19:48 +0100699 if (
700 "operationalState" in indata["_admin"]
701 and content["_admin"]["operationalState"]
702 == indata["_admin"]["operationalState"]
703 ):
704 raise EngineException(
705 "operationalState already {}".format(
706 content["_admin"]["operationalState"]
707 ),
708 http_code=HTTPStatus.CONFLICT,
709 )
Frank Brydendeba68e2020-07-27 13:55:11 +0000710
711 return indata
712
aticig2b5e1232022-08-10 17:30:12 +0300713 def _validate_descriptor_changes(
714 self,
715 descriptor_id,
beierlmcee2ebf2022-03-29 17:42:48 -0400716 descriptor_file_name,
717 old_descriptor_directory,
garciadeblasf2af4a12023-01-24 16:56:54 +0100718 new_descriptor_directory,
aticig2b5e1232022-08-10 17:30:12 +0300719 ):
beierlmcee2ebf2022-03-29 17:42:48 -0400720 # Example:
721 # raise EngineException(
722 # "Error in validating new descriptor: <NODE> cannot be modified",
723 # http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
724 # )
725 pass
tiernob24258a2018-10-04 18:39:49 +0200726
garciadeblasf2af4a12023-01-24 16:56:54 +0100727
tiernob24258a2018-10-04 18:39:49 +0200728class VnfdTopic(DescriptorTopic):
729 topic = "vnfds"
730 topic_msg = "vnfd"
731
delacruzramo32bab472019-09-13 12:24:22 +0200732 def __init__(self, db, fs, msg, auth):
733 DescriptorTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200734
garciaale7cbd03c2020-11-27 10:38:35 -0300735 def pyangbind_validation(self, item, data, force=False):
garciaaledf718ae2020-12-03 19:17:28 -0300736 if self._descriptor_data_is_in_old_format(data):
garciadeblas4568a372021-03-24 09:19:48 +0100737 raise EngineException(
738 "ERROR: Unsupported descriptor format. Please, use an ETSI SOL006 descriptor.",
739 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
740 )
garciaale7cbd03c2020-11-27 10:38:35 -0300741 try:
garciaale7cbd03c2020-11-27 10:38:35 -0300742 myvnfd = etsi_nfv_vnfd.etsi_nfv_vnfd()
garciadeblas4568a372021-03-24 09:19:48 +0100743 pybindJSONDecoder.load_ietf_json(
744 {"etsi-nfv-vnfd:vnfd": data},
745 None,
746 None,
747 obj=myvnfd,
748 path_helper=True,
749 skip_unknown=force,
750 )
garciaale7cbd03c2020-11-27 10:38:35 -0300751 out = pybindJSON.dumps(myvnfd, mode="ietf")
752 desc_out = self._remove_envelop(yaml.safe_load(out))
753 desc_out = self._remove_yang_prefixes_from_descriptor(desc_out)
bravof41a52052021-02-17 18:08:01 -0300754 return utils.deep_update_dict(data, desc_out)
garciaale7cbd03c2020-11-27 10:38:35 -0300755 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +0100756 raise EngineException(
757 "Error in pyangbind validation: {}".format(str(e)),
758 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
759 )
garciaale7cbd03c2020-11-27 10:38:35 -0300760
tiernob24258a2018-10-04 18:39:49 +0200761 @staticmethod
garciaaledf718ae2020-12-03 19:17:28 -0300762 def _descriptor_data_is_in_old_format(data):
garciadeblas4568a372021-03-24 09:19:48 +0100763 return ("vnfd-catalog" in data) or ("vnfd:vnfd-catalog" in data)
garciaaledf718ae2020-12-03 19:17:28 -0300764
765 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200766 def _remove_envelop(indata=None):
767 if not indata:
768 return {}
769 clean_indata = indata
garciaale7cbd03c2020-11-27 10:38:35 -0300770
garciadeblas4568a372021-03-24 09:19:48 +0100771 if clean_indata.get("etsi-nfv-vnfd:vnfd"):
772 if not isinstance(clean_indata["etsi-nfv-vnfd:vnfd"], dict):
garciaale7cbd03c2020-11-27 10:38:35 -0300773 raise EngineException("'etsi-nfv-vnfd:vnfd' must be a dict")
garciadeblas4568a372021-03-24 09:19:48 +0100774 clean_indata = clean_indata["etsi-nfv-vnfd:vnfd"]
775 elif clean_indata.get("vnfd"):
776 if not isinstance(clean_indata["vnfd"], dict):
garciaale7cbd03c2020-11-27 10:38:35 -0300777 raise EngineException("'vnfd' must be dict")
garciadeblas4568a372021-03-24 09:19:48 +0100778 clean_indata = clean_indata["vnfd"]
garciaale7cbd03c2020-11-27 10:38:35 -0300779
tiernob24258a2018-10-04 18:39:49 +0200780 return clean_indata
781
tierno65ca36d2019-02-12 19:27:52 +0100782 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
garciadeblas4568a372021-03-24 09:19:48 +0100783 final_content = super().check_conflict_on_edit(
784 session, final_content, edit_content, _id
785 )
tierno36ec8602018-11-02 17:27:11 +0100786
787 # set type of vnfd
788 contains_pdu = False
789 contains_vdu = False
790 for vdu in get_iterable(final_content.get("vdu")):
791 if vdu.get("pdu-type"):
792 contains_pdu = True
793 else:
794 contains_vdu = True
795 if contains_pdu:
796 final_content["_admin"]["type"] = "hnfd" if contains_vdu else "pnfd"
797 elif contains_vdu:
798 final_content["_admin"]["type"] = "vnfd"
799 # if neither vud nor pdu do not fill type
bravofb995ea22021-02-10 10:57:52 -0300800 return final_content
tierno36ec8602018-11-02 17:27:11 +0100801
tiernob4844ab2019-05-23 08:42:12 +0000802 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200803 """
804 Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note
805 that VNFD can be public and be used by NSD of other projects. Also check there are not deployments, or vnfr
806 that uses this vnfd
tierno65ca36d2019-02-12 19:27:52 +0100807 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob4844ab2019-05-23 08:42:12 +0000808 :param _id: vnfd internal id
809 :param db_content: The database content of the _id.
tiernob24258a2018-10-04 18:39:49 +0200810 :return: None or raises EngineException with the conflict
811 """
tierno65ca36d2019-02-12 19:27:52 +0100812 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200813 return
tiernob4844ab2019-05-23 08:42:12 +0000814 descriptor = db_content
tiernob24258a2018-10-04 18:39:49 +0200815 descriptor_id = descriptor.get("id")
816 if not descriptor_id: # empty vnfd not uploaded
817 return
818
tierno65ca36d2019-02-12 19:27:52 +0100819 _filter = self._get_project_filter(session)
tiernob4844ab2019-05-23 08:42:12 +0000820
tiernob24258a2018-10-04 18:39:49 +0200821 # check vnfrs using this vnfd
822 _filter["vnfd-id"] = _id
823 if self.db.get_list("vnfrs", _filter):
garciadeblas4568a372021-03-24 09:19:48 +0100824 raise EngineException(
825 "There is at least one VNF instance using this descriptor",
826 http_code=HTTPStatus.CONFLICT,
827 )
tiernob4844ab2019-05-23 08:42:12 +0000828
829 # check NSD referencing this VNFD
tiernob24258a2018-10-04 18:39:49 +0200830 del _filter["vnfd-id"]
garciadeblasf576eb92021-04-18 20:54:13 +0000831 _filter["vnfd-id"] = descriptor_id
tiernob24258a2018-10-04 18:39:49 +0200832 if self.db.get_list("nsds", _filter):
garciadeblas4568a372021-03-24 09:19:48 +0100833 raise EngineException(
834 "There is at least one NS package referencing this descriptor",
835 http_code=HTTPStatus.CONFLICT,
836 )
tiernob24258a2018-10-04 18:39:49 +0200837
gcalvinoa6fe0002019-01-09 13:27:11 +0100838 def _validate_input_new(self, indata, storage_params, force=False):
Frank Bryden19b97522020-07-10 12:32:02 +0000839 indata.pop("onboardingState", None)
840 indata.pop("operationalState", None)
841 indata.pop("usageState", None)
Frank Bryden19b97522020-07-10 12:32:02 +0000842 indata.pop("links", None)
843
gcalvino46e4cb82018-10-26 13:10:22 +0200844 indata = self.pyangbind_validation("vnfds", indata, force)
gcalvino5e72d152018-10-23 11:46:57 +0200845 # Cross references validation in the descriptor
garciaale7cbd03c2020-11-27 10:38:35 -0300846
847 self.validate_mgmt_interface_connection_point(indata)
gcalvino5e72d152018-10-23 11:46:57 +0200848
849 for vdu in get_iterable(indata.get("vdu")):
garciaale7cbd03c2020-11-27 10:38:35 -0300850 self.validate_vdu_internal_connection_points(vdu)
garciaale960531a2020-10-20 18:29:45 -0300851 self._validate_vdu_cloud_init_in_package(storage_params, vdu, indata)
bravof41a52052021-02-17 18:08:01 -0300852 self._validate_vdu_charms_in_package(storage_params, indata)
garciaale960531a2020-10-20 18:29:45 -0300853
854 self._validate_vnf_charms_in_package(storage_params, indata)
855
garciaale7cbd03c2020-11-27 10:38:35 -0300856 self.validate_external_connection_points(indata)
857 self.validate_internal_virtual_links(indata)
garciaale960531a2020-10-20 18:29:45 -0300858 self.validate_monitoring_params(indata)
859 self.validate_scaling_group_descriptor(indata)
Adurti10f814e2023-11-08 06:26:04 +0000860 self.validate_healing_group_descriptor(indata)
861 self.validate_alarm_group_descriptor(indata)
862 self.validate_storage_compute_descriptor(indata)
Daniel Arndt00f83aa2023-06-15 16:43:33 +0200863 self.validate_helm_chart(indata)
garciaale960531a2020-10-20 18:29:45 -0300864
865 return indata
866
867 @staticmethod
Daniel Arndt00f83aa2023-06-15 16:43:33 +0200868 def validate_helm_chart(indata):
Gabriel Cuba646773d2023-11-20 01:43:05 -0500869 def is_url(url):
870 result = urlparse(url)
871 return all([result.scheme, result.netloc])
872
Daniel Arndt00f83aa2023-06-15 16:43:33 +0200873 kdus = indata.get("kdu", [])
874 for kdu in kdus:
875 helm_chart_value = kdu.get("helm-chart")
876 if not helm_chart_value:
877 continue
Gabriel Cuba646773d2023-11-20 01:43:05 -0500878 if not (
879 valid_helm_chart_re.match(helm_chart_value) or is_url(helm_chart_value)
880 ):
Daniel Arndt00f83aa2023-06-15 16:43:33 +0200881 raise EngineException(
882 "helm-chart '{}' is not valid".format(helm_chart_value),
883 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
884 )
885
886 @staticmethod
garciaale7cbd03c2020-11-27 10:38:35 -0300887 def validate_mgmt_interface_connection_point(indata):
garciaale960531a2020-10-20 18:29:45 -0300888 if not indata.get("vdu"):
889 return
garciaale7cbd03c2020-11-27 10:38:35 -0300890 if not indata.get("mgmt-cp"):
garciadeblas4568a372021-03-24 09:19:48 +0100891 raise EngineException(
892 "'mgmt-cp' is a mandatory field and it is not defined",
893 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
894 )
garciaale7cbd03c2020-11-27 10:38:35 -0300895
896 for cp in get_iterable(indata.get("ext-cpd")):
897 if cp["id"] == indata["mgmt-cp"]:
898 break
899 else:
garciadeblas4568a372021-03-24 09:19:48 +0100900 raise EngineException(
901 "mgmt-cp='{}' must match an existing ext-cpd".format(indata["mgmt-cp"]),
902 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
903 )
garciaale960531a2020-10-20 18:29:45 -0300904
905 @staticmethod
garciaale7cbd03c2020-11-27 10:38:35 -0300906 def validate_vdu_internal_connection_points(vdu):
907 int_cpds = set()
908 for cpd in get_iterable(vdu.get("int-cpd")):
909 cpd_id = cpd.get("id")
910 if cpd_id and cpd_id in int_cpds:
garciadeblas4568a372021-03-24 09:19:48 +0100911 raise EngineException(
912 "vdu[id='{}']:int-cpd[id='{}'] is already used by other int-cpd".format(
913 vdu["id"], cpd_id
914 ),
915 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
916 )
garciaale7cbd03c2020-11-27 10:38:35 -0300917 int_cpds.add(cpd_id)
918
919 @staticmethod
920 def validate_external_connection_points(indata):
921 all_vdus_int_cpds = set()
922 for vdu in get_iterable(indata.get("vdu")):
923 for int_cpd in get_iterable(vdu.get("int-cpd")):
924 all_vdus_int_cpds.add((vdu.get("id"), int_cpd.get("id")))
925
926 ext_cpds = set()
927 for cpd in get_iterable(indata.get("ext-cpd")):
928 cpd_id = cpd.get("id")
929 if cpd_id and cpd_id in ext_cpds:
garciadeblas4568a372021-03-24 09:19:48 +0100930 raise EngineException(
931 "ext-cpd[id='{}'] is already used by other ext-cpd".format(cpd_id),
932 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
933 )
garciaale7cbd03c2020-11-27 10:38:35 -0300934 ext_cpds.add(cpd_id)
935
936 int_cpd = cpd.get("int-cpd")
937 if int_cpd:
938 if (int_cpd.get("vdu-id"), int_cpd.get("cpd")) not in all_vdus_int_cpds:
garciadeblas4568a372021-03-24 09:19:48 +0100939 raise EngineException(
940 "ext-cpd[id='{}']:int-cpd must match an existing vdu int-cpd".format(
941 cpd_id
942 ),
943 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
944 )
garciaale7cbd03c2020-11-27 10:38:35 -0300945 # TODO: Validate k8s-cluster-net points to a valid k8s-cluster:nets ?
garciaale960531a2020-10-20 18:29:45 -0300946
bravof41a52052021-02-17 18:08:01 -0300947 def _validate_vdu_charms_in_package(self, storage_params, indata):
948 for df in indata["df"]:
garciadeblas4568a372021-03-24 09:19:48 +0100949 if (
950 "lcm-operations-configuration" in df
951 and "operate-vnf-op-config" in df["lcm-operations-configuration"]
952 ):
953 configs = df["lcm-operations-configuration"][
954 "operate-vnf-op-config"
955 ].get("day1-2", [])
garciaale2c4f9ec2021-03-01 11:04:50 -0300956 vdus = df.get("vdu-profile", [])
bravof23258282021-02-22 18:04:40 -0300957 for vdu in vdus:
958 for config in configs:
959 if config["id"] == vdu["id"] and utils.find_in_list(
960 config.get("execution-environment-list", []),
garciadeblas4568a372021-03-24 09:19:48 +0100961 lambda ee: "juju" in ee,
bravof23258282021-02-22 18:04:40 -0300962 ):
garciadeblas4568a372021-03-24 09:19:48 +0100963 if not self._validate_package_folders(
964 storage_params, "charms"
bravofc26740a2021-11-08 09:44:54 -0300965 ) and not self._validate_package_folders(
966 storage_params, "Scripts/charms"
garciadeblas4568a372021-03-24 09:19:48 +0100967 ):
968 raise EngineException(
969 "Charm defined in vnf[id={}] but not present in "
970 "package".format(indata["id"])
971 )
garciaale960531a2020-10-20 18:29:45 -0300972
973 def _validate_vdu_cloud_init_in_package(self, storage_params, vdu, indata):
974 if not vdu.get("cloud-init-file"):
975 return
garciadeblas4568a372021-03-24 09:19:48 +0100976 if not self._validate_package_folders(
977 storage_params, "cloud_init", vdu["cloud-init-file"]
bravofc26740a2021-11-08 09:44:54 -0300978 ) and not self._validate_package_folders(
979 storage_params, "Scripts/cloud_init", vdu["cloud-init-file"]
garciadeblas4568a372021-03-24 09:19:48 +0100980 ):
981 raise EngineException(
982 "Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in "
983 "package".format(indata["id"], vdu["id"])
984 )
garciaale960531a2020-10-20 18:29:45 -0300985
986 def _validate_vnf_charms_in_package(self, storage_params, indata):
bravof41a52052021-02-17 18:08:01 -0300987 # Get VNF configuration through new container
garciadeblas4568a372021-03-24 09:19:48 +0100988 for deployment_flavor in indata.get("df", []):
bravof41a52052021-02-17 18:08:01 -0300989 if "lcm-operations-configuration" not in deployment_flavor:
990 return
garciadeblas4568a372021-03-24 09:19:48 +0100991 if (
992 "operate-vnf-op-config"
993 not in deployment_flavor["lcm-operations-configuration"]
994 ):
bravof41a52052021-02-17 18:08:01 -0300995 return
garciadeblas4568a372021-03-24 09:19:48 +0100996 for day_1_2_config in deployment_flavor["lcm-operations-configuration"][
997 "operate-vnf-op-config"
998 ]["day1-2"]:
bravof41a52052021-02-17 18:08:01 -0300999 if day_1_2_config["id"] == indata["id"]:
bravof23258282021-02-22 18:04:40 -03001000 if utils.find_in_list(
1001 day_1_2_config.get("execution-environment-list", []),
garciadeblas4568a372021-03-24 09:19:48 +01001002 lambda ee: "juju" in ee,
bravof23258282021-02-22 18:04:40 -03001003 ):
bravofc26740a2021-11-08 09:44:54 -03001004 if not self._validate_package_folders(
1005 storage_params, "charms"
1006 ) and not self._validate_package_folders(
1007 storage_params, "Scripts/charms"
1008 ):
garciadeblas4568a372021-03-24 09:19:48 +01001009 raise EngineException(
1010 "Charm defined in vnf[id={}] but not present in "
1011 "package".format(indata["id"])
1012 )
garciaale960531a2020-10-20 18:29:45 -03001013
1014 def _validate_package_folders(self, storage_params, folder, file=None):
bravofc26740a2021-11-08 09:44:54 -03001015 if not storage_params:
1016 return False
1017 elif not storage_params.get("pkg-dir"):
1018 if self.fs.file_exists("{}_".format(storage_params["folder"]), "dir"):
garciadeblasf2af4a12023-01-24 16:56:54 +01001019 f = "{}_/{}".format(storage_params["folder"], folder)
bravofc26740a2021-11-08 09:44:54 -03001020 else:
garciadeblasf2af4a12023-01-24 16:56:54 +01001021 f = "{}/{}".format(storage_params["folder"], folder)
bravofc26740a2021-11-08 09:44:54 -03001022 if file:
1023 return self.fs.file_exists("{}/{}".format(f, file), "file")
1024 else:
bravofc26740a2021-11-08 09:44:54 -03001025 if self.fs.file_exists(f, "dir"):
1026 if self.fs.dir_ls(f):
1027 return True
garciaale960531a2020-10-20 18:29:45 -03001028 return False
1029 else:
garciadeblas4568a372021-03-24 09:19:48 +01001030 if self.fs.file_exists("{}_".format(storage_params["folder"]), "dir"):
1031 f = "{}_/{}/{}".format(
1032 storage_params["folder"], storage_params["pkg-dir"], folder
1033 )
garciaale960531a2020-10-20 18:29:45 -03001034 else:
garciadeblas4568a372021-03-24 09:19:48 +01001035 f = "{}/{}/{}".format(
1036 storage_params["folder"], storage_params["pkg-dir"], folder
1037 )
garciaale960531a2020-10-20 18:29:45 -03001038 if file:
garciadeblas4568a372021-03-24 09:19:48 +01001039 return self.fs.file_exists("{}/{}".format(f, file), "file")
garciaale960531a2020-10-20 18:29:45 -03001040 else:
garciadeblas4568a372021-03-24 09:19:48 +01001041 if self.fs.file_exists(f, "dir"):
garciaale960531a2020-10-20 18:29:45 -03001042 if self.fs.dir_ls(f):
1043 return True
1044 return False
1045
1046 @staticmethod
garciaale7cbd03c2020-11-27 10:38:35 -03001047 def validate_internal_virtual_links(indata):
1048 all_ivld_ids = set()
1049 for ivld in get_iterable(indata.get("int-virtual-link-desc")):
1050 ivld_id = ivld.get("id")
1051 if ivld_id and ivld_id in all_ivld_ids:
garciadeblas4568a372021-03-24 09:19:48 +01001052 raise EngineException(
1053 "Duplicated VLD id in int-virtual-link-desc[id={}]".format(ivld_id),
1054 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1055 )
garciaale960531a2020-10-20 18:29:45 -03001056 else:
garciaale7cbd03c2020-11-27 10:38:35 -03001057 all_ivld_ids.add(ivld_id)
garciaale960531a2020-10-20 18:29:45 -03001058
garciaale7cbd03c2020-11-27 10:38:35 -03001059 for vdu in get_iterable(indata.get("vdu")):
1060 for int_cpd in get_iterable(vdu.get("int-cpd")):
1061 int_cpd_ivld_id = int_cpd.get("int-virtual-link-desc")
1062 if int_cpd_ivld_id and int_cpd_ivld_id not in all_ivld_ids:
1063 raise EngineException(
1064 "vdu[id='{}']:int-cpd[id='{}']:int-virtual-link-desc='{}' must match an existing "
garciadeblas4568a372021-03-24 09:19:48 +01001065 "int-virtual-link-desc".format(
1066 vdu["id"], int_cpd["id"], int_cpd_ivld_id
1067 ),
1068 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1069 )
garciaale960531a2020-10-20 18:29:45 -03001070
garciaale7cbd03c2020-11-27 10:38:35 -03001071 for df in get_iterable(indata.get("df")):
1072 for vlp in get_iterable(df.get("virtual-link-profile")):
1073 vlp_ivld_id = vlp.get("id")
1074 if vlp_ivld_id and vlp_ivld_id not in all_ivld_ids:
garciadeblas4568a372021-03-24 09:19:48 +01001075 raise EngineException(
1076 "df[id='{}']:virtual-link-profile='{}' must match an existing "
1077 "int-virtual-link-desc".format(df["id"], vlp_ivld_id),
1078 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1079 )
garciaale7cbd03c2020-11-27 10:38:35 -03001080
garciaale960531a2020-10-20 18:29:45 -03001081 @staticmethod
1082 def validate_monitoring_params(indata):
garciaale7cbd03c2020-11-27 10:38:35 -03001083 all_monitoring_params = set()
1084 for ivld in get_iterable(indata.get("int-virtual-link-desc")):
1085 for mp in get_iterable(ivld.get("monitoring-parameters")):
1086 mp_id = mp.get("id")
1087 if mp_id and mp_id in all_monitoring_params:
garciadeblas4568a372021-03-24 09:19:48 +01001088 raise EngineException(
1089 "Duplicated monitoring-parameter id in "
1090 "int-virtual-link-desc[id='{}']:monitoring-parameters[id='{}']".format(
1091 ivld["id"], mp_id
1092 ),
1093 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1094 )
gcalvino5e72d152018-10-23 11:46:57 +02001095 else:
garciaale7cbd03c2020-11-27 10:38:35 -03001096 all_monitoring_params.add(mp_id)
1097
1098 for vdu in get_iterable(indata.get("vdu")):
1099 for mp in get_iterable(vdu.get("monitoring-parameter")):
1100 mp_id = mp.get("id")
1101 if mp_id and mp_id in all_monitoring_params:
garciadeblas4568a372021-03-24 09:19:48 +01001102 raise EngineException(
1103 "Duplicated monitoring-parameter id in "
1104 "vdu[id='{}']:monitoring-parameter[id='{}']".format(
1105 vdu["id"], mp_id
1106 ),
1107 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1108 )
garciaale7cbd03c2020-11-27 10:38:35 -03001109 else:
1110 all_monitoring_params.add(mp_id)
1111
1112 for df in get_iterable(indata.get("df")):
1113 for mp in get_iterable(df.get("monitoring-parameter")):
1114 mp_id = mp.get("id")
1115 if mp_id and mp_id in all_monitoring_params:
garciadeblas4568a372021-03-24 09:19:48 +01001116 raise EngineException(
1117 "Duplicated monitoring-parameter id in "
1118 "df[id='{}']:monitoring-parameter[id='{}']".format(
1119 df["id"], mp_id
1120 ),
1121 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1122 )
garciaale7cbd03c2020-11-27 10:38:35 -03001123 else:
1124 all_monitoring_params.add(mp_id)
gcalvino5e72d152018-10-23 11:46:57 +02001125
garciaale960531a2020-10-20 18:29:45 -03001126 @staticmethod
1127 def validate_scaling_group_descriptor(indata):
garciaale7cbd03c2020-11-27 10:38:35 -03001128 all_monitoring_params = set()
Adurti10f814e2023-11-08 06:26:04 +00001129 all_vdu_ids = set()
1130 for df in get_iterable(indata.get("df")):
1131 for il in get_iterable(df.get("instantiation-level")):
1132 for vl in get_iterable(il.get("vdu-level")):
1133 all_vdu_ids.add(vl.get("vdu-id"))
1134
garciaale7cbd03c2020-11-27 10:38:35 -03001135 for ivld in get_iterable(indata.get("int-virtual-link-desc")):
1136 for mp in get_iterable(ivld.get("monitoring-parameters")):
1137 all_monitoring_params.add(mp.get("id"))
1138
1139 for vdu in get_iterable(indata.get("vdu")):
1140 for mp in get_iterable(vdu.get("monitoring-parameter")):
1141 all_monitoring_params.add(mp.get("id"))
1142
1143 for df in get_iterable(indata.get("df")):
1144 for mp in get_iterable(df.get("monitoring-parameter")):
1145 all_monitoring_params.add(mp.get("id"))
1146
1147 for df in get_iterable(indata.get("df")):
1148 for sa in get_iterable(df.get("scaling-aspect")):
Adurti10f814e2023-11-08 06:26:04 +00001149 for deltas in get_iterable(
1150 sa.get("aspect-delta-details").get("deltas")
1151 ):
1152 for vds in get_iterable(deltas.get("vdu-delta")):
1153 sa_vdu_id = vds.get("id")
1154 if sa_vdu_id and sa_vdu_id not in all_vdu_ids:
1155 raise EngineException(
1156 "df[id='{}']:scaling-aspect[id='{}']:aspect-delta-details"
1157 "[delta='{}']: "
1158 "vdu-id='{}' not defined in vdu".format(
1159 df["id"],
1160 sa["id"],
1161 deltas["id"],
1162 sa_vdu_id,
1163 ),
1164 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1165 )
1166
1167 for df in get_iterable(indata.get("df")):
1168 for sa in get_iterable(df.get("scaling-aspect")):
garciaale7cbd03c2020-11-27 10:38:35 -03001169 for sp in get_iterable(sa.get("scaling-policy")):
1170 for sc in get_iterable(sp.get("scaling-criteria")):
1171 sc_monitoring_param = sc.get("vnf-monitoring-param-ref")
garciadeblas4568a372021-03-24 09:19:48 +01001172 if (
1173 sc_monitoring_param
1174 and sc_monitoring_param not in all_monitoring_params
1175 ):
1176 raise EngineException(
1177 "df[id='{}']:scaling-aspect[id='{}']:scaling-policy"
1178 "[name='{}']:scaling-criteria[name='{}']: "
1179 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param".format(
1180 df["id"],
1181 sa["id"],
1182 sp["name"],
1183 sc["name"],
1184 sc_monitoring_param,
1185 ),
1186 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1187 )
garciaale7cbd03c2020-11-27 10:38:35 -03001188
1189 for sca in get_iterable(sa.get("scaling-config-action")):
garciadeblas4568a372021-03-24 09:19:48 +01001190 if (
1191 "lcm-operations-configuration" not in df
1192 or "operate-vnf-op-config"
1193 not in df["lcm-operations-configuration"]
bravof41a52052021-02-17 18:08:01 -03001194 or not utils.find_in_list(
garciadeblas4568a372021-03-24 09:19:48 +01001195 df["lcm-operations-configuration"][
1196 "operate-vnf-op-config"
1197 ].get("day1-2", []),
1198 lambda config: config["id"] == indata["id"],
1199 )
bravof41a52052021-02-17 18:08:01 -03001200 ):
garciadeblas4568a372021-03-24 09:19:48 +01001201 raise EngineException(
1202 "'day1-2 configuration' not defined in the descriptor but it is "
1203 "referenced by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action".format(
1204 df["id"], sa["id"]
1205 ),
1206 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1207 )
1208 for configuration in get_iterable(
1209 df["lcm-operations-configuration"]["operate-vnf-op-config"].get(
1210 "day1-2", []
1211 )
1212 ):
1213 for primitive in get_iterable(
1214 configuration.get("config-primitive")
1215 ):
1216 if (
1217 primitive["name"]
1218 == sca["vnf-config-primitive-name-ref"]
1219 ):
garciaale7cbd03c2020-11-27 10:38:35 -03001220 break
1221 else:
garciadeblas4568a372021-03-24 09:19:48 +01001222 raise EngineException(
1223 "df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-"
1224 "config-primitive-name-ref='{}' does not match any "
1225 "day1-2 configuration:config-primitive:name".format(
1226 df["id"],
1227 sa["id"],
1228 sca["vnf-config-primitive-name-ref"],
1229 ),
1230 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1231 )
gcalvinoa6fe0002019-01-09 13:27:11 +01001232
Adurti10f814e2023-11-08 06:26:04 +00001233 @staticmethod
1234 def validate_healing_group_descriptor(indata):
1235 all_vdu_ids = set()
1236 for df in get_iterable(indata.get("df")):
1237 for il in get_iterable(df.get("instantiation-level")):
1238 for vl in get_iterable(il.get("vdu-level")):
1239 all_vdu_ids.add(vl.get("vdu-id"))
1240
1241 for df in get_iterable(indata.get("df")):
1242 for ha in get_iterable(df.get("healing-aspect")):
1243 for hp in get_iterable(ha.get("healing-policy")):
1244 hp_monitoring_param = hp.get("vdu-id")
1245 if hp_monitoring_param and hp_monitoring_param not in all_vdu_ids:
1246 raise EngineException(
1247 "df[id='{}']:healing-aspect[id='{}']:healing-policy"
1248 "[name='{}']: "
1249 "vdu-id='{}' not defined in vdu".format(
1250 df["id"],
1251 ha["id"],
1252 hp["event-name"],
1253 hp_monitoring_param,
1254 ),
1255 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1256 )
1257
1258 @staticmethod
1259 def validate_alarm_group_descriptor(indata):
1260 all_monitoring_params = set()
1261 for ivld in get_iterable(indata.get("int-virtual-link-desc")):
1262 for mp in get_iterable(ivld.get("monitoring-parameters")):
1263 all_monitoring_params.add(mp.get("id"))
1264
1265 for vdu in get_iterable(indata.get("vdu")):
1266 for mp in get_iterable(vdu.get("monitoring-parameter")):
1267 all_monitoring_params.add(mp.get("id"))
1268
1269 for df in get_iterable(indata.get("df")):
1270 for mp in get_iterable(df.get("monitoring-parameter")):
1271 all_monitoring_params.add(mp.get("id"))
1272
1273 for vdus in get_iterable(indata.get("vdu")):
1274 for alarms in get_iterable(vdus.get("alarm")):
1275 alarm_monitoring_param = alarms.get("vnf-monitoring-param-ref")
1276 if (
1277 alarm_monitoring_param
1278 and alarm_monitoring_param not in all_monitoring_params
1279 ):
1280 raise EngineException(
1281 "vdu[id='{}']:alarm[id='{}']:"
1282 "vnf-monitoring-param-ref='{}' not defined in any monitoring-param".format(
1283 vdus["id"],
1284 alarms["alarm-id"],
1285 alarm_monitoring_param,
1286 ),
1287 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1288 )
1289
1290 @staticmethod
1291 def validate_storage_compute_descriptor(indata):
1292 all_vsd_ids = set()
1293 for vsd in get_iterable(indata.get("virtual-storage-desc")):
1294 all_vsd_ids.add(vsd.get("id"))
1295
1296 all_vcd_ids = set()
1297 for vcd in get_iterable(indata.get("virtual-compute-desc")):
1298 all_vcd_ids.add(vcd.get("id"))
1299
1300 for vdus in get_iterable(indata.get("vdu")):
1301 for vsd_ref in vdus.get("virtual-storage-desc"):
1302 if vsd_ref and vsd_ref not in all_vsd_ids:
1303 raise EngineException(
1304 "vdu[virtual-storage-desc='{}']"
1305 "not defined in vnfd".format(
1306 vsd_ref,
1307 ),
1308 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1309 )
1310
1311 for vdus in get_iterable(indata.get("vdu")):
1312 vcd_ref = vdus.get("virtual-compute-desc")
1313 if vcd_ref and vcd_ref not in all_vcd_ids:
1314 raise EngineException(
1315 "vdu[virtual-compute-desc='{}']"
1316 "not defined in vnfd".format(
1317 vdus["virtual-compute-desc"],
1318 ),
1319 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1320 )
1321
delacruzramo271d2002019-12-02 21:00:37 +01001322 def delete_extra(self, session, _id, db_content, not_send_msg=None):
1323 """
1324 Deletes associate file system storage (via super)
1325 Deletes associated vnfpkgops from database.
1326 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1327 :param _id: server internal id
1328 :param db_content: The database content of the descriptor
1329 :return: None
1330 :raises: FsException in case of error while deleting associated storage
1331 """
1332 super().delete_extra(session, _id, db_content, not_send_msg)
1333 self.db.del_list("vnfpkgops", {"vnfPkgId": _id})
garciadeblasf2af4a12023-01-24 16:56:54 +01001334 self.db.del_list(self.topic + "_revisions", {"_id": {"$regex": _id}})
garciaale960531a2020-10-20 18:29:45 -03001335
Frank Bryden19b97522020-07-10 12:32:02 +00001336 def sol005_projection(self, data):
1337 data["onboardingState"] = data["_admin"]["onboardingState"]
1338 data["operationalState"] = data["_admin"]["operationalState"]
1339 data["usageState"] = data["_admin"]["usageState"]
1340
1341 links = {}
1342 links["self"] = {"href": "/vnfpkgm/v1/vnf_packages/{}".format(data["_id"])}
1343 links["vnfd"] = {"href": "/vnfpkgm/v1/vnf_packages/{}/vnfd".format(data["_id"])}
garciadeblas4568a372021-03-24 09:19:48 +01001344 links["packageContent"] = {
1345 "href": "/vnfpkgm/v1/vnf_packages/{}/package_content".format(data["_id"])
1346 }
Frank Bryden19b97522020-07-10 12:32:02 +00001347 data["_links"] = links
garciaale960531a2020-10-20 18:29:45 -03001348
Frank Bryden19b97522020-07-10 12:32:02 +00001349 return super().sol005_projection(data)
delacruzramo271d2002019-12-02 21:00:37 +01001350
aticig9cfa8162022-04-07 11:57:18 +03001351 @staticmethod
1352 def find_software_version(vnfd: dict) -> str:
1353 """Find the sotware version in the VNFD descriptors
1354
1355 Args:
1356 vnfd (dict): Descriptor as a dictionary
1357
1358 Returns:
1359 software-version (str)
1360 """
1361 default_sw_version = "1.0"
1362 if vnfd.get("vnfd"):
1363 vnfd = vnfd["vnfd"]
1364 if vnfd.get("software-version"):
1365 return vnfd["software-version"]
1366 else:
1367 return default_sw_version
1368
1369 @staticmethod
1370 def extract_policies(vnfd: dict) -> dict:
1371 """Removes the policies from the VNFD descriptors
1372
1373 Args:
1374 vnfd (dict): Descriptor as a dictionary
1375
1376 Returns:
1377 vnfd (dict): VNFD which does not include policies
1378 """
elumalai3622f832022-07-08 12:06:27 +05301379 for df in vnfd.get("df", {}):
1380 for policy in ["scaling-aspect", "healing-aspect"]:
garciadeblasf2af4a12023-01-24 16:56:54 +01001381 if df.get(policy, {}):
elumalai3622f832022-07-08 12:06:27 +05301382 df.pop(policy)
1383 for vdu in vnfd.get("vdu", {}):
1384 for alarm_policy in ["alarm", "monitoring-parameter"]:
garciadeblasf2af4a12023-01-24 16:56:54 +01001385 if vdu.get(alarm_policy, {}):
elumalai3622f832022-07-08 12:06:27 +05301386 vdu.pop(alarm_policy)
aticig9cfa8162022-04-07 11:57:18 +03001387 return vnfd
1388
1389 @staticmethod
1390 def extract_day12_primitives(vnfd: dict) -> dict:
1391 """Removes the day12 primitives from the VNFD descriptors
1392
1393 Args:
1394 vnfd (dict): Descriptor as a dictionary
1395
1396 Returns:
1397 vnfd (dict)
1398 """
1399 for df_id, df in enumerate(vnfd.get("df", {})):
1400 if (
1401 df.get("lcm-operations-configuration", {})
1402 .get("operate-vnf-op-config", {})
1403 .get("day1-2")
1404 ):
1405 day12 = df["lcm-operations-configuration"]["operate-vnf-op-config"].get(
1406 "day1-2"
1407 )
1408 for config_id, config in enumerate(day12):
1409 for key in [
1410 "initial-config-primitive",
1411 "config-primitive",
1412 "terminate-config-primitive",
1413 ]:
1414 config.pop(key, None)
1415 day12[config_id] = config
1416 df["lcm-operations-configuration"]["operate-vnf-op-config"][
1417 "day1-2"
1418 ] = day12
1419 vnfd["df"][df_id] = df
1420 return vnfd
1421
1422 def remove_modifiable_items(self, vnfd: dict) -> dict:
1423 """Removes the modifiable parts from the VNFD descriptors
1424
1425 It calls different extract functions according to different update types
1426 to clear all the modifiable items from VNFD
1427
1428 Args:
1429 vnfd (dict): Descriptor as a dictionary
1430
1431 Returns:
1432 vnfd (dict): Descriptor which does not include modifiable contents
1433 """
1434 if vnfd.get("vnfd"):
1435 vnfd = vnfd["vnfd"]
1436 vnfd.pop("_admin", None)
1437 # If the other extractions need to be done from VNFD,
1438 # the new extract methods could be appended to below list.
1439 for extract_function in [self.extract_day12_primitives, self.extract_policies]:
1440 vnfd_temp = extract_function(vnfd)
1441 vnfd = vnfd_temp
1442 return vnfd
1443
1444 def _validate_descriptor_changes(
1445 self,
aticig2b5e1232022-08-10 17:30:12 +03001446 descriptor_id: str,
aticig9cfa8162022-04-07 11:57:18 +03001447 descriptor_file_name: str,
1448 old_descriptor_directory: str,
1449 new_descriptor_directory: str,
1450 ):
1451 """Compares the old and new VNFD descriptors and validates the new descriptor.
1452
1453 Args:
1454 old_descriptor_directory (str): Directory of descriptor which is in-use
aticig2b5e1232022-08-10 17:30:12 +03001455 new_descriptor_directory (str): Directory of descriptor which is proposed to update (new revision)
aticig9cfa8162022-04-07 11:57:18 +03001456
1457 Returns:
1458 None
1459
1460 Raises:
1461 EngineException: In case of error when there are unallowed changes
1462 """
1463 try:
aticig2b5e1232022-08-10 17:30:12 +03001464 # If VNFD does not exist in DB or it is not in use by any NS,
1465 # validation is not required.
1466 vnfd = self.db.get_one("vnfds", {"_id": descriptor_id})
1467 if not vnfd or not detect_descriptor_usage(vnfd, "vnfds", self.db):
1468 return
1469
1470 # Get the old and new descriptor contents in order to compare them.
aticig9cfa8162022-04-07 11:57:18 +03001471 with self.fs.file_open(
1472 (old_descriptor_directory.rstrip("/"), descriptor_file_name), "r"
1473 ) as old_descriptor_file:
1474 with self.fs.file_open(
aticig2b5e1232022-08-10 17:30:12 +03001475 (new_descriptor_directory.rstrip("/"), descriptor_file_name), "r"
aticig9cfa8162022-04-07 11:57:18 +03001476 ) as new_descriptor_file:
aticig2b5e1232022-08-10 17:30:12 +03001477 old_content = yaml.safe_load(old_descriptor_file.read())
1478 new_content = yaml.safe_load(new_descriptor_file.read())
1479
1480 # If software version has changed, we do not need to validate
1481 # the differences anymore.
aticig9cfa8162022-04-07 11:57:18 +03001482 if old_content and new_content:
1483 if self.find_software_version(
1484 old_content
1485 ) != self.find_software_version(new_content):
1486 return
aticig2b5e1232022-08-10 17:30:12 +03001487
aticig9cfa8162022-04-07 11:57:18 +03001488 disallowed_change = DeepDiff(
1489 self.remove_modifiable_items(old_content),
1490 self.remove_modifiable_items(new_content),
1491 )
aticig2b5e1232022-08-10 17:30:12 +03001492
aticig9cfa8162022-04-07 11:57:18 +03001493 if disallowed_change:
1494 changed_nodes = functools.reduce(
1495 lambda a, b: a + " , " + b,
1496 [
1497 node.lstrip("root")
1498 for node in disallowed_change.get(
1499 "values_changed"
1500 ).keys()
1501 ],
1502 )
aticig2b5e1232022-08-10 17:30:12 +03001503
aticig9cfa8162022-04-07 11:57:18 +03001504 raise EngineException(
1505 f"Error in validating new descriptor: {changed_nodes} cannot be modified, "
1506 "there are disallowed changes in the vnf descriptor.",
1507 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1508 )
1509 except (
1510 DbException,
1511 AttributeError,
1512 IndexError,
1513 KeyError,
1514 ValueError,
1515 ) as e:
1516 raise type(e)(
1517 "VNF Descriptor could not be processed with error: {}.".format(e)
1518 )
1519
tiernob24258a2018-10-04 18:39:49 +02001520
1521class NsdTopic(DescriptorTopic):
1522 topic = "nsds"
1523 topic_msg = "nsd"
1524
delacruzramo32bab472019-09-13 12:24:22 +02001525 def __init__(self, db, fs, msg, auth):
Daniel Arndt00f83aa2023-06-15 16:43:33 +02001526 super().__init__(db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +02001527
garciaale7cbd03c2020-11-27 10:38:35 -03001528 def pyangbind_validation(self, item, data, force=False):
garciaaledf718ae2020-12-03 19:17:28 -03001529 if self._descriptor_data_is_in_old_format(data):
garciadeblas4568a372021-03-24 09:19:48 +01001530 raise EngineException(
1531 "ERROR: Unsupported descriptor format. Please, use an ETSI SOL006 descriptor.",
1532 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1533 )
garciaale7cbd03c2020-11-27 10:38:35 -03001534 try:
garciadeblas4568a372021-03-24 09:19:48 +01001535 nsd_vnf_profiles = data.get("df", [{}])[0].get("vnf-profile", [])
garciaale7cbd03c2020-11-27 10:38:35 -03001536 mynsd = etsi_nfv_nsd.etsi_nfv_nsd()
garciadeblas4568a372021-03-24 09:19:48 +01001537 pybindJSONDecoder.load_ietf_json(
1538 {"nsd": {"nsd": [data]}},
1539 None,
1540 None,
1541 obj=mynsd,
1542 path_helper=True,
1543 skip_unknown=force,
1544 )
garciaale7cbd03c2020-11-27 10:38:35 -03001545 out = pybindJSON.dumps(mynsd, mode="ietf")
1546 desc_out = self._remove_envelop(yaml.safe_load(out))
1547 desc_out = self._remove_yang_prefixes_from_descriptor(desc_out)
garciaale341ac1b2020-12-11 20:04:11 -03001548 if nsd_vnf_profiles:
garciadeblas4568a372021-03-24 09:19:48 +01001549 desc_out["df"][0]["vnf-profile"] = nsd_vnf_profiles
garciaale7cbd03c2020-11-27 10:38:35 -03001550 return desc_out
1551 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001552 raise EngineException(
1553 "Error in pyangbind validation: {}".format(str(e)),
1554 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1555 )
garciaale7cbd03c2020-11-27 10:38:35 -03001556
tiernob24258a2018-10-04 18:39:49 +02001557 @staticmethod
garciaaledf718ae2020-12-03 19:17:28 -03001558 def _descriptor_data_is_in_old_format(data):
garciadeblas4568a372021-03-24 09:19:48 +01001559 return ("nsd-catalog" in data) or ("nsd:nsd-catalog" in data)
garciaaledf718ae2020-12-03 19:17:28 -03001560
1561 @staticmethod
tiernob24258a2018-10-04 18:39:49 +02001562 def _remove_envelop(indata=None):
1563 if not indata:
1564 return {}
1565 clean_indata = indata
1566
garciadeblas4568a372021-03-24 09:19:48 +01001567 if clean_indata.get("nsd"):
1568 clean_indata = clean_indata["nsd"]
1569 elif clean_indata.get("etsi-nfv-nsd:nsd"):
1570 clean_indata = clean_indata["etsi-nfv-nsd:nsd"]
1571 if clean_indata.get("nsd"):
1572 if (
1573 not isinstance(clean_indata["nsd"], list)
1574 or len(clean_indata["nsd"]) != 1
1575 ):
gcalvino46e4cb82018-10-26 13:10:22 +02001576 raise EngineException("'nsd' must be a list of only one element")
garciadeblas4568a372021-03-24 09:19:48 +01001577 clean_indata = clean_indata["nsd"][0]
tiernob24258a2018-10-04 18:39:49 +02001578 return clean_indata
1579
gcalvinoa6fe0002019-01-09 13:27:11 +01001580 def _validate_input_new(self, indata, storage_params, force=False):
Frank Bryden19b97522020-07-10 12:32:02 +00001581 indata.pop("nsdOnboardingState", None)
1582 indata.pop("nsdOperationalState", None)
1583 indata.pop("nsdUsageState", None)
1584
1585 indata.pop("links", None)
1586
gcalvino46e4cb82018-10-26 13:10:22 +02001587 indata = self.pyangbind_validation("nsds", indata, force)
tierno5a5c2182018-11-20 12:27:42 +00001588 # Cross references validation in the descriptor
tiernoaa1ca7b2018-11-08 19:00:20 +01001589 # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none
garciaale7cbd03c2020-11-27 10:38:35 -03001590 for vld in get_iterable(indata.get("virtual-link-desc")):
1591 self.validate_vld_mgmt_network_with_virtual_link_protocol_data(vld, indata)
selvi.j828f3f22023-05-16 05:43:48 +00001592 for fg in get_iterable(indata.get("vnffgd")):
1593 self.validate_vnffgd_data(fg, indata)
garciaale960531a2020-10-20 18:29:45 -03001594
garciaale7cbd03c2020-11-27 10:38:35 -03001595 self.validate_vnf_profiles_vnfd_id(indata)
garciaale960531a2020-10-20 18:29:45 -03001596
tiernob24258a2018-10-04 18:39:49 +02001597 return indata
1598
garciaale960531a2020-10-20 18:29:45 -03001599 @staticmethod
garciaale7cbd03c2020-11-27 10:38:35 -03001600 def validate_vld_mgmt_network_with_virtual_link_protocol_data(vld, indata):
1601 if not vld.get("mgmt-network"):
1602 return
1603 vld_id = vld.get("id")
1604 for df in get_iterable(indata.get("df")):
1605 for vlp in get_iterable(df.get("virtual-link-profile")):
1606 if vld_id and vld_id == vlp.get("virtual-link-desc-id"):
1607 if vlp.get("virtual-link-protocol-data"):
garciadeblas4568a372021-03-24 09:19:48 +01001608 raise EngineException(
1609 "Error at df[id='{}']:virtual-link-profile[id='{}']:virtual-link-"
1610 "protocol-data You cannot set a virtual-link-protocol-data "
1611 "when mgmt-network is True".format(df["id"], vlp["id"]),
1612 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1613 )
garciaale960531a2020-10-20 18:29:45 -03001614
1615 @staticmethod
selvi.j828f3f22023-05-16 05:43:48 +00001616 def validate_vnffgd_data(fg, indata):
1617 position_list = []
1618 all_vnf_ids = set(get_iterable(fg.get("vnf-profile-id")))
1619 for fgposition in get_iterable(fg.get("nfp-position-element")):
1620 position_list.append(fgposition["id"])
1621
1622 for nfpd in get_iterable(fg.get("nfpd")):
1623 nfp_position = []
1624 for position in get_iterable(nfpd.get("position-desc-id")):
1625 nfp_position = position.get("nfp-position-element-id")
1626 if position == "nfp-position-element-id":
1627 nfp_position = position.get("nfp-position-element-id")
1628 if nfp_position[0] not in position_list:
1629 raise EngineException(
1630 "Error at vnffgd nfpd[id='{}']:nfp-position-element-id='{}' "
1631 "does not match any nfp-position-element".format(
1632 nfpd["id"], nfp_position[0]
1633 ),
1634 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1635 )
1636
1637 for cp in get_iterable(position.get("cp-profile-id")):
1638 for cpe in get_iterable(cp.get("constituent-profile-elements")):
1639 constituent_base_element_id = cpe.get(
1640 "constituent-base-element-id"
1641 )
1642 if (
1643 constituent_base_element_id
1644 and constituent_base_element_id not in all_vnf_ids
1645 ):
1646 raise EngineException(
1647 "Error at vnffgd constituent_profile[id='{}']:vnfd-id='{}' "
1648 "does not match any constituent-base-element-id".format(
1649 cpe["id"], constituent_base_element_id
1650 ),
1651 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1652 )
1653
1654 @staticmethod
garciaale7cbd03c2020-11-27 10:38:35 -03001655 def validate_vnf_profiles_vnfd_id(indata):
1656 all_vnfd_ids = set(get_iterable(indata.get("vnfd-id")))
1657 for df in get_iterable(indata.get("df")):
1658 for vnf_profile in get_iterable(df.get("vnf-profile")):
1659 vnfd_id = vnf_profile.get("vnfd-id")
1660 if vnfd_id and vnfd_id not in all_vnfd_ids:
garciadeblas4568a372021-03-24 09:19:48 +01001661 raise EngineException(
1662 "Error at df[id='{}']:vnf_profile[id='{}']:vnfd-id='{}' "
1663 "does not match any vnfd-id".format(
1664 df["id"], vnf_profile["id"], vnfd_id
1665 ),
1666 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1667 )
garciaale960531a2020-10-20 18:29:45 -03001668
Frank Brydendeba68e2020-07-27 13:55:11 +00001669 def _validate_input_edit(self, indata, content, force=False):
tiernoaa1ca7b2018-11-08 19:00:20 +01001670 # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit
Frank Brydendeba68e2020-07-27 13:55:11 +00001671 """
1672 indata looks as follows:
garciadeblas4568a372021-03-24 09:19:48 +01001673 - In the new case (conformant)
1674 {'nsdOperationalState': 'DISABLED', 'userDefinedData': {'id': 'string23',
Frank Brydendeba68e2020-07-27 13:55:11 +00001675 '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}}
1676 - In the old case (backwards-compatible)
1677 {'id': 'string23', '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}
1678 """
1679 if "_admin" not in indata:
1680 indata["_admin"] = {}
1681
1682 if "nsdOperationalState" in indata:
1683 if indata["nsdOperationalState"] in ("ENABLED", "DISABLED"):
1684 indata["_admin"]["operationalState"] = indata.pop("nsdOperationalState")
1685 else:
garciadeblas4568a372021-03-24 09:19:48 +01001686 raise EngineException(
1687 "State '{}' is not a valid operational state".format(
1688 indata["nsdOperationalState"]
1689 ),
1690 http_code=HTTPStatus.BAD_REQUEST,
1691 )
Frank Brydendeba68e2020-07-27 13:55:11 +00001692
garciadeblas4568a372021-03-24 09:19:48 +01001693 # In the case of user defined data, we need to put the data in the root of the object
Frank Brydendeba68e2020-07-27 13:55:11 +00001694 # to preserve current expected behaviour
1695 if "userDefinedData" in indata:
1696 data = indata.pop("userDefinedData")
gaticid7debb92023-07-31 14:37:32 +03001697 if isinstance(data, dict):
Frank Brydendeba68e2020-07-27 13:55:11 +00001698 indata["_admin"]["userDefinedData"] = data
1699 else:
garciadeblas4568a372021-03-24 09:19:48 +01001700 raise EngineException(
1701 "userDefinedData should be an object, but is '{}' instead".format(
1702 type(data)
1703 ),
1704 http_code=HTTPStatus.BAD_REQUEST,
1705 )
1706 if (
1707 "operationalState" in indata["_admin"]
1708 and content["_admin"]["operationalState"]
1709 == indata["_admin"]["operationalState"]
1710 ):
1711 raise EngineException(
1712 "nsdOperationalState already {}".format(
1713 content["_admin"]["operationalState"]
1714 ),
1715 http_code=HTTPStatus.CONFLICT,
1716 )
tiernob24258a2018-10-04 18:39:49 +02001717 return indata
1718
tierno65ca36d2019-02-12 19:27:52 +01001719 def _check_descriptor_dependencies(self, session, descriptor):
tiernob24258a2018-10-04 18:39:49 +02001720 """
tierno5a5c2182018-11-20 12:27:42 +00001721 Check that the dependent descriptors exist on a new descriptor or edition. Also checks references to vnfd
1722 connection points are ok
tierno65ca36d2019-02-12 19:27:52 +01001723 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +02001724 :param descriptor: descriptor to be inserted or edit
1725 :return: None or raises exception
1726 """
tierno65ca36d2019-02-12 19:27:52 +01001727 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001728 return
garciaale7cbd03c2020-11-27 10:38:35 -03001729 vnfds_index = self._get_descriptor_constituent_vnfds_index(session, descriptor)
garciaale960531a2020-10-20 18:29:45 -03001730
1731 # Cross references validation in the descriptor and vnfd connection point validation
garciaale7cbd03c2020-11-27 10:38:35 -03001732 for df in get_iterable(descriptor.get("df")):
1733 self.validate_df_vnf_profiles_constituent_connection_points(df, vnfds_index)
garciaale960531a2020-10-20 18:29:45 -03001734
garciaale7cbd03c2020-11-27 10:38:35 -03001735 def _get_descriptor_constituent_vnfds_index(self, session, descriptor):
1736 vnfds_index = {}
1737 if descriptor.get("vnfd-id") and not session["force"]:
1738 for vnfd_id in get_iterable(descriptor.get("vnfd-id")):
garciaale960531a2020-10-20 18:29:45 -03001739 query_filter = self._get_project_filter(session)
1740 query_filter["id"] = vnfd_id
1741 vnf_list = self.db.get_list("vnfds", query_filter)
tierno5a5c2182018-11-20 12:27:42 +00001742 if not vnf_list:
garciadeblas4568a372021-03-24 09:19:48 +01001743 raise EngineException(
1744 "Descriptor error at 'vnfd-id'='{}' references a non "
1745 "existing vnfd".format(vnfd_id),
1746 http_code=HTTPStatus.CONFLICT,
1747 )
garciaale7cbd03c2020-11-27 10:38:35 -03001748 vnfds_index[vnfd_id] = vnf_list[0]
1749 return vnfds_index
garciaale960531a2020-10-20 18:29:45 -03001750
1751 @staticmethod
garciaale7cbd03c2020-11-27 10:38:35 -03001752 def validate_df_vnf_profiles_constituent_connection_points(df, vnfds_index):
1753 for vnf_profile in get_iterable(df.get("vnf-profile")):
1754 vnfd = vnfds_index.get(vnf_profile["vnfd-id"])
1755 all_vnfd_ext_cpds = set()
1756 for ext_cpd in get_iterable(vnfd.get("ext-cpd")):
garciadeblas4568a372021-03-24 09:19:48 +01001757 if ext_cpd.get("id"):
1758 all_vnfd_ext_cpds.add(ext_cpd.get("id"))
garciaale7cbd03c2020-11-27 10:38:35 -03001759
garciadeblas4568a372021-03-24 09:19:48 +01001760 for virtual_link in get_iterable(
1761 vnf_profile.get("virtual-link-connectivity")
1762 ):
garciaale7cbd03c2020-11-27 10:38:35 -03001763 for vl_cpd in get_iterable(virtual_link.get("constituent-cpd-id")):
garciadeblas4568a372021-03-24 09:19:48 +01001764 vl_cpd_id = vl_cpd.get("constituent-cpd-id")
garciaale7cbd03c2020-11-27 10:38:35 -03001765 if vl_cpd_id and vl_cpd_id not in all_vnfd_ext_cpds:
garciadeblas4568a372021-03-24 09:19:48 +01001766 raise EngineException(
1767 "Error at df[id='{}']:vnf-profile[id='{}']:virtual-link-connectivity"
1768 "[virtual-link-profile-id='{}']:constituent-cpd-id='{}' references a "
1769 "non existing ext-cpd:id inside vnfd '{}'".format(
1770 df["id"],
1771 vnf_profile["id"],
1772 virtual_link["virtual-link-profile-id"],
1773 vl_cpd_id,
1774 vnfd["id"],
1775 ),
1776 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1777 )
tiernob24258a2018-10-04 18:39:49 +02001778
tierno65ca36d2019-02-12 19:27:52 +01001779 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
garciadeblas4568a372021-03-24 09:19:48 +01001780 final_content = super().check_conflict_on_edit(
1781 session, final_content, edit_content, _id
1782 )
tiernob24258a2018-10-04 18:39:49 +02001783
tierno65ca36d2019-02-12 19:27:52 +01001784 self._check_descriptor_dependencies(session, final_content)
tiernob24258a2018-10-04 18:39:49 +02001785
bravofb995ea22021-02-10 10:57:52 -03001786 return final_content
1787
tiernob4844ab2019-05-23 08:42:12 +00001788 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +02001789 """
1790 Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note
1791 that NSD can be public and be used by other projects.
tierno65ca36d2019-02-12 19:27:52 +01001792 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob4844ab2019-05-23 08:42:12 +00001793 :param _id: nsd internal id
1794 :param db_content: The database content of the _id
tiernob24258a2018-10-04 18:39:49 +02001795 :return: None or raises EngineException with the conflict
1796 """
tierno65ca36d2019-02-12 19:27:52 +01001797 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001798 return
tiernob4844ab2019-05-23 08:42:12 +00001799 descriptor = db_content
1800 descriptor_id = descriptor.get("id")
1801 if not descriptor_id: # empty nsd not uploaded
1802 return
1803
1804 # check NSD used by NS
tierno65ca36d2019-02-12 19:27:52 +01001805 _filter = self._get_project_filter(session)
tiernob4844ab2019-05-23 08:42:12 +00001806 _filter["nsd-id"] = _id
tiernob24258a2018-10-04 18:39:49 +02001807 if self.db.get_list("nsrs", _filter):
garciadeblas4568a372021-03-24 09:19:48 +01001808 raise EngineException(
1809 "There is at least one NS instance using this descriptor",
1810 http_code=HTTPStatus.CONFLICT,
1811 )
tiernob4844ab2019-05-23 08:42:12 +00001812
1813 # check NSD referenced by NST
1814 del _filter["nsd-id"]
1815 _filter["netslice-subnet.ANYINDEX.nsd-ref"] = descriptor_id
1816 if self.db.get_list("nsts", _filter):
garciadeblas4568a372021-03-24 09:19:48 +01001817 raise EngineException(
1818 "There is at least one NetSlice Template referencing this descriptor",
1819 http_code=HTTPStatus.CONFLICT,
1820 )
garciaale960531a2020-10-20 18:29:45 -03001821
beierlmcee2ebf2022-03-29 17:42:48 -04001822 def delete_extra(self, session, _id, db_content, not_send_msg=None):
1823 """
1824 Deletes associate file system storage (via super)
1825 Deletes associated vnfpkgops from database.
1826 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1827 :param _id: server internal id
1828 :param db_content: The database content of the descriptor
1829 :return: None
1830 :raises: FsException in case of error while deleting associated storage
1831 """
1832 super().delete_extra(session, _id, db_content, not_send_msg)
garciadeblasf2af4a12023-01-24 16:56:54 +01001833 self.db.del_list(self.topic + "_revisions", {"_id": {"$regex": _id}})
beierlmcee2ebf2022-03-29 17:42:48 -04001834
aticig9cfa8162022-04-07 11:57:18 +03001835 @staticmethod
1836 def extract_day12_primitives(nsd: dict) -> dict:
1837 """Removes the day12 primitives from the NSD descriptors
1838
1839 Args:
1840 nsd (dict): Descriptor as a dictionary
1841
1842 Returns:
1843 nsd (dict): Cleared NSD
1844 """
1845 if nsd.get("ns-configuration"):
1846 for key in [
1847 "config-primitive",
1848 "initial-config-primitive",
1849 "terminate-config-primitive",
1850 ]:
1851 nsd["ns-configuration"].pop(key, None)
1852 return nsd
1853
1854 def remove_modifiable_items(self, nsd: dict) -> dict:
1855 """Removes the modifiable parts from the VNFD descriptors
1856
1857 It calls different extract functions according to different update types
1858 to clear all the modifiable items from NSD
1859
1860 Args:
1861 nsd (dict): Descriptor as a dictionary
1862
1863 Returns:
1864 nsd (dict): Descriptor which does not include modifiable contents
1865 """
1866 while isinstance(nsd, dict) and nsd.get("nsd"):
1867 nsd = nsd["nsd"]
1868 if isinstance(nsd, list):
1869 nsd = nsd[0]
1870 nsd.pop("_admin", None)
1871 # If the more extractions need to be done from NSD,
1872 # the new extract methods could be appended to below list.
1873 for extract_function in [self.extract_day12_primitives]:
1874 nsd_temp = extract_function(nsd)
1875 nsd = nsd_temp
1876 return nsd
1877
1878 def _validate_descriptor_changes(
1879 self,
aticig2b5e1232022-08-10 17:30:12 +03001880 descriptor_id: str,
aticig9cfa8162022-04-07 11:57:18 +03001881 descriptor_file_name: str,
1882 old_descriptor_directory: str,
1883 new_descriptor_directory: str,
1884 ):
1885 """Compares the old and new NSD descriptors and validates the new descriptor
1886
1887 Args:
1888 old_descriptor_directory: Directory of descriptor which is in-use
aticig2b5e1232022-08-10 17:30:12 +03001889 new_descriptor_directory: Directory of descriptor which is proposed to update (new revision)
aticig9cfa8162022-04-07 11:57:18 +03001890
1891 Returns:
1892 None
1893
1894 Raises:
1895 EngineException: In case of error if the changes are not allowed
1896 """
1897
1898 try:
aticig2b5e1232022-08-10 17:30:12 +03001899 # If NSD does not exist in DB, or it is not in use by any NS,
1900 # validation is not required.
1901 nsd = self.db.get_one("nsds", {"_id": descriptor_id}, fail_on_empty=False)
1902 if not nsd or not detect_descriptor_usage(nsd, "nsds", self.db):
1903 return
1904
1905 # Get the old and new descriptor contents in order to compare them.
aticig9cfa8162022-04-07 11:57:18 +03001906 with self.fs.file_open(
aticig2b5e1232022-08-10 17:30:12 +03001907 (old_descriptor_directory.rstrip("/"), descriptor_file_name), "r"
aticig9cfa8162022-04-07 11:57:18 +03001908 ) as old_descriptor_file:
1909 with self.fs.file_open(
1910 (new_descriptor_directory.rstrip("/"), descriptor_file_name), "r"
1911 ) as new_descriptor_file:
aticig2b5e1232022-08-10 17:30:12 +03001912 old_content = yaml.safe_load(old_descriptor_file.read())
1913 new_content = yaml.safe_load(new_descriptor_file.read())
1914
aticig9cfa8162022-04-07 11:57:18 +03001915 if old_content and new_content:
1916 disallowed_change = DeepDiff(
1917 self.remove_modifiable_items(old_content),
1918 self.remove_modifiable_items(new_content),
1919 )
aticig2b5e1232022-08-10 17:30:12 +03001920
aticig9cfa8162022-04-07 11:57:18 +03001921 if disallowed_change:
1922 changed_nodes = functools.reduce(
1923 lambda a, b: a + ", " + b,
1924 [
1925 node.lstrip("root")
1926 for node in disallowed_change.get(
1927 "values_changed"
1928 ).keys()
1929 ],
1930 )
aticig2b5e1232022-08-10 17:30:12 +03001931
aticig9cfa8162022-04-07 11:57:18 +03001932 raise EngineException(
1933 f"Error in validating new descriptor: {changed_nodes} cannot be modified, "
1934 "there are disallowed changes in the ns descriptor. ",
1935 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1936 )
1937 except (
1938 DbException,
1939 AttributeError,
1940 IndexError,
1941 KeyError,
1942 ValueError,
1943 ) as e:
1944 raise type(e)(
1945 "NS Descriptor could not be processed with error: {}.".format(e)
1946 )
1947
Frank Bryden19b97522020-07-10 12:32:02 +00001948 def sol005_projection(self, data):
1949 data["nsdOnboardingState"] = data["_admin"]["onboardingState"]
1950 data["nsdOperationalState"] = data["_admin"]["operationalState"]
1951 data["nsdUsageState"] = data["_admin"]["usageState"]
1952
1953 links = {}
1954 links["self"] = {"href": "/nsd/v1/ns_descriptors/{}".format(data["_id"])}
garciadeblas4568a372021-03-24 09:19:48 +01001955 links["nsd_content"] = {
1956 "href": "/nsd/v1/ns_descriptors/{}/nsd_content".format(data["_id"])
1957 }
Frank Bryden19b97522020-07-10 12:32:02 +00001958 data["_links"] = links
garciaale960531a2020-10-20 18:29:45 -03001959
Frank Bryden19b97522020-07-10 12:32:02 +00001960 return super().sol005_projection(data)
tiernob24258a2018-10-04 18:39:49 +02001961
1962
Felipe Vicensb57758d2018-10-16 16:00:20 +02001963class NstTopic(DescriptorTopic):
1964 topic = "nsts"
1965 topic_msg = "nst"
tierno6b02b052020-06-02 10:07:41 +00001966 quota_name = "slice_templates"
Felipe Vicensb57758d2018-10-16 16:00:20 +02001967
delacruzramo32bab472019-09-13 12:24:22 +02001968 def __init__(self, db, fs, msg, auth):
1969 DescriptorTopic.__init__(self, db, fs, msg, auth)
Felipe Vicensb57758d2018-10-16 16:00:20 +02001970
garciaale7cbd03c2020-11-27 10:38:35 -03001971 def pyangbind_validation(self, item, data, force=False):
1972 try:
1973 mynst = nst_im()
garciadeblas4568a372021-03-24 09:19:48 +01001974 pybindJSONDecoder.load_ietf_json(
1975 {"nst": [data]},
1976 None,
1977 None,
1978 obj=mynst,
1979 path_helper=True,
1980 skip_unknown=force,
1981 )
garciaale7cbd03c2020-11-27 10:38:35 -03001982 out = pybindJSON.dumps(mynst, mode="ietf")
1983 desc_out = self._remove_envelop(yaml.safe_load(out))
1984 return desc_out
1985 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001986 raise EngineException(
1987 "Error in pyangbind validation: {}".format(str(e)),
1988 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
1989 )
garciaale7cbd03c2020-11-27 10:38:35 -03001990
Felipe Vicensb57758d2018-10-16 16:00:20 +02001991 @staticmethod
1992 def _remove_envelop(indata=None):
1993 if not indata:
1994 return {}
1995 clean_indata = indata
1996
garciadeblas4568a372021-03-24 09:19:48 +01001997 if clean_indata.get("nst"):
1998 if (
1999 not isinstance(clean_indata["nst"], list)
2000 or len(clean_indata["nst"]) != 1
2001 ):
Felipe Vicensb57758d2018-10-16 16:00:20 +02002002 raise EngineException("'nst' must be a list only one element")
garciadeblas4568a372021-03-24 09:19:48 +01002003 clean_indata = clean_indata["nst"][0]
2004 elif clean_indata.get("nst:nst"):
2005 if (
2006 not isinstance(clean_indata["nst:nst"], list)
2007 or len(clean_indata["nst:nst"]) != 1
2008 ):
gcalvino70434c12018-11-27 15:17:04 +01002009 raise EngineException("'nst:nst' must be a list only one element")
garciadeblas4568a372021-03-24 09:19:48 +01002010 clean_indata = clean_indata["nst:nst"][0]
Felipe Vicensb57758d2018-10-16 16:00:20 +02002011 return clean_indata
2012
gcalvinoa6fe0002019-01-09 13:27:11 +01002013 def _validate_input_new(self, indata, storage_params, force=False):
Frank Bryden19b97522020-07-10 12:32:02 +00002014 indata.pop("onboardingState", None)
2015 indata.pop("operationalState", None)
2016 indata.pop("usageState", None)
gcalvino70434c12018-11-27 15:17:04 +01002017 indata = self.pyangbind_validation("nsts", indata, force)
Felipe Vicense36ab852018-11-23 14:12:09 +01002018 return indata.copy()
2019
Felipe Vicensb57758d2018-10-16 16:00:20 +02002020 def _check_descriptor_dependencies(self, session, descriptor):
2021 """
2022 Check that the dependent descriptors exist on a new descriptor or edition
tierno65ca36d2019-02-12 19:27:52 +01002023 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Felipe Vicensb57758d2018-10-16 16:00:20 +02002024 :param descriptor: descriptor to be inserted or edit
2025 :return: None or raises exception
2026 """
2027 if not descriptor.get("netslice-subnet"):
2028 return
2029 for nsd in descriptor["netslice-subnet"]:
2030 nsd_id = nsd["nsd-ref"]
tierno65ca36d2019-02-12 19:27:52 +01002031 filter_q = self._get_project_filter(session)
Felipe Vicensb57758d2018-10-16 16:00:20 +02002032 filter_q["id"] = nsd_id
2033 if not self.db.get_list("nsds", filter_q):
garciadeblas4568a372021-03-24 09:19:48 +01002034 raise EngineException(
2035 "Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non "
2036 "existing nsd".format(nsd_id),
2037 http_code=HTTPStatus.CONFLICT,
2038 )
Felipe Vicensb57758d2018-10-16 16:00:20 +02002039
tierno65ca36d2019-02-12 19:27:52 +01002040 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
garciadeblas4568a372021-03-24 09:19:48 +01002041 final_content = super().check_conflict_on_edit(
2042 session, final_content, edit_content, _id
2043 )
Felipe Vicensb57758d2018-10-16 16:00:20 +02002044
2045 self._check_descriptor_dependencies(session, final_content)
bravofb995ea22021-02-10 10:57:52 -03002046 return final_content
Felipe Vicensb57758d2018-10-16 16:00:20 +02002047
tiernob4844ab2019-05-23 08:42:12 +00002048 def check_conflict_on_del(self, session, _id, db_content):
Felipe Vicensb57758d2018-10-16 16:00:20 +02002049 """
2050 Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note
2051 that NST can be public and be used by other projects.
tierno65ca36d2019-02-12 19:27:52 +01002052 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Felipe Vicens07f31722018-10-29 15:16:44 +01002053 :param _id: nst internal id
tiernob4844ab2019-05-23 08:42:12 +00002054 :param db_content: The database content of the _id.
Felipe Vicensb57758d2018-10-16 16:00:20 +02002055 :return: None or raises EngineException with the conflict
2056 """
2057 # TODO: Check this method
tierno65ca36d2019-02-12 19:27:52 +01002058 if session["force"]:
Felipe Vicensb57758d2018-10-16 16:00:20 +02002059 return
Felipe Vicens07f31722018-10-29 15:16:44 +01002060 # Get Network Slice Template from Database
tierno65ca36d2019-02-12 19:27:52 +01002061 _filter = self._get_project_filter(session)
tiernoea97c042019-09-13 09:44:42 +00002062 _filter["_admin.nst-id"] = _id
tiernob4844ab2019-05-23 08:42:12 +00002063 if self.db.get_list("nsis", _filter):
garciadeblas4568a372021-03-24 09:19:48 +01002064 raise EngineException(
2065 "there is at least one Netslice Instance using this descriptor",
2066 http_code=HTTPStatus.CONFLICT,
2067 )
Felipe Vicensb57758d2018-10-16 16:00:20 +02002068
Frank Bryden19b97522020-07-10 12:32:02 +00002069 def sol005_projection(self, data):
2070 data["onboardingState"] = data["_admin"]["onboardingState"]
2071 data["operationalState"] = data["_admin"]["operationalState"]
2072 data["usageState"] = data["_admin"]["usageState"]
2073
2074 links = {}
2075 links["self"] = {"href": "/nst/v1/netslice_templates/{}".format(data["_id"])}
2076 links["nst"] = {"href": "/nst/v1/netslice_templates/{}/nst".format(data["_id"])}
2077 data["_links"] = links
2078
2079 return super().sol005_projection(data)
2080
Felipe Vicensb57758d2018-10-16 16:00:20 +02002081
tiernob24258a2018-10-04 18:39:49 +02002082class PduTopic(BaseTopic):
2083 topic = "pdus"
2084 topic_msg = "pdu"
tierno6b02b052020-06-02 10:07:41 +00002085 quota_name = "pduds"
tiernob24258a2018-10-04 18:39:49 +02002086 schema_new = pdu_new_schema
2087 schema_edit = pdu_edit_schema
2088
delacruzramo32bab472019-09-13 12:24:22 +02002089 def __init__(self, db, fs, msg, auth):
2090 BaseTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +02002091
2092 @staticmethod
2093 def format_on_new(content, project_id=None, make_public=False):
tierno36ec8602018-11-02 17:27:11 +01002094 BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
tiernob24258a2018-10-04 18:39:49 +02002095 content["_admin"]["onboardingState"] = "CREATED"
tierno36ec8602018-11-02 17:27:11 +01002096 content["_admin"]["operationalState"] = "ENABLED"
2097 content["_admin"]["usageState"] = "NOT_IN_USE"
tiernob24258a2018-10-04 18:39:49 +02002098
tiernob4844ab2019-05-23 08:42:12 +00002099 def check_conflict_on_del(self, session, _id, db_content):
2100 """
2101 Check that there is not any vnfr that uses this PDU
2102 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
2103 :param _id: pdu internal id
2104 :param db_content: The database content of the _id.
2105 :return: None or raises EngineException with the conflict
2106 """
tierno65ca36d2019-02-12 19:27:52 +01002107 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +02002108 return
tiernob4844ab2019-05-23 08:42:12 +00002109
2110 _filter = self._get_project_filter(session)
2111 _filter["vdur.pdu-id"] = _id
tiernob24258a2018-10-04 18:39:49 +02002112 if self.db.get_list("vnfrs", _filter):
garciadeblas4568a372021-03-24 09:19:48 +01002113 raise EngineException(
2114 "There is at least one VNF instance using this PDU",
2115 http_code=HTTPStatus.CONFLICT,
2116 )
delacruzramo271d2002019-12-02 21:00:37 +01002117
2118
2119class VnfPkgOpTopic(BaseTopic):
2120 topic = "vnfpkgops"
2121 topic_msg = "vnfd"
2122 schema_new = vnfpkgop_new_schema
2123 schema_edit = None
2124
2125 def __init__(self, db, fs, msg, auth):
2126 BaseTopic.__init__(self, db, fs, msg, auth)
2127
2128 def edit(self, session, _id, indata=None, kwargs=None, content=None):
garciadeblas4568a372021-03-24 09:19:48 +01002129 raise EngineException(
2130 "Method 'edit' not allowed for topic '{}'".format(self.topic),
2131 HTTPStatus.METHOD_NOT_ALLOWED,
2132 )
delacruzramo271d2002019-12-02 21:00:37 +01002133
2134 def delete(self, session, _id, dry_run=False):
garciadeblas4568a372021-03-24 09:19:48 +01002135 raise EngineException(
2136 "Method 'delete' not allowed for topic '{}'".format(self.topic),
2137 HTTPStatus.METHOD_NOT_ALLOWED,
2138 )
delacruzramo271d2002019-12-02 21:00:37 +01002139
2140 def delete_list(self, session, filter_q=None):
garciadeblas4568a372021-03-24 09:19:48 +01002141 raise EngineException(
2142 "Method 'delete_list' not allowed for topic '{}'".format(self.topic),
2143 HTTPStatus.METHOD_NOT_ALLOWED,
2144 )
delacruzramo271d2002019-12-02 21:00:37 +01002145
2146 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
2147 """
2148 Creates a new entry into database.
2149 :param rollback: list to append created items at database in case a rollback may to be done
2150 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
2151 :param indata: data to be inserted
2152 :param kwargs: used to override the indata descriptor
2153 :param headers: http request headers
2154 :return: _id, op_id:
2155 _id: identity of the inserted data.
2156 op_id: None
2157 """
2158 self._update_input_with_kwargs(indata, kwargs)
2159 validate_input(indata, self.schema_new)
2160 vnfpkg_id = indata["vnfPkgId"]
2161 filter_q = BaseTopic._get_project_filter(session)
2162 filter_q["_id"] = vnfpkg_id
2163 vnfd = self.db.get_one("vnfds", filter_q)
2164 operation = indata["lcmOperationType"]
2165 kdu_name = indata["kdu_name"]
2166 for kdu in vnfd.get("kdu", []):
2167 if kdu["name"] == kdu_name:
2168 helm_chart = kdu.get("helm-chart")
2169 juju_bundle = kdu.get("juju-bundle")
2170 break
2171 else:
garciadeblas4568a372021-03-24 09:19:48 +01002172 raise EngineException(
2173 "Not found vnfd[id='{}']:kdu[name='{}']".format(vnfpkg_id, kdu_name)
2174 )
delacruzramo271d2002019-12-02 21:00:37 +01002175 if helm_chart:
2176 indata["helm-chart"] = helm_chart
2177 match = fullmatch(r"([^/]*)/([^/]*)", helm_chart)
2178 repo_name = match.group(1) if match else None
2179 elif juju_bundle:
2180 indata["juju-bundle"] = juju_bundle
2181 match = fullmatch(r"([^/]*)/([^/]*)", juju_bundle)
2182 repo_name = match.group(1) if match else None
2183 else:
garciadeblas4568a372021-03-24 09:19:48 +01002184 raise EngineException(
2185 "Found neither 'helm-chart' nor 'juju-bundle' in vnfd[id='{}']:kdu[name='{}']".format(
2186 vnfpkg_id, kdu_name
2187 )
2188 )
delacruzramo271d2002019-12-02 21:00:37 +01002189 if repo_name:
2190 del filter_q["_id"]
2191 filter_q["name"] = repo_name
2192 repo = self.db.get_one("k8srepos", filter_q)
2193 k8srepo_id = repo.get("_id")
2194 k8srepo_url = repo.get("url")
2195 else:
2196 k8srepo_id = None
2197 k8srepo_url = None
2198 indata["k8srepoId"] = k8srepo_id
2199 indata["k8srepo_url"] = k8srepo_url
2200 vnfpkgop_id = str(uuid4())
2201 vnfpkgop_desc = {
2202 "_id": vnfpkgop_id,
2203 "operationState": "PROCESSING",
2204 "vnfPkgId": vnfpkg_id,
2205 "lcmOperationType": operation,
2206 "isAutomaticInvocation": False,
2207 "isCancelPending": False,
2208 "operationParams": indata,
2209 "links": {
2210 "self": "/osm/vnfpkgm/v1/vnfpkg_op_occs/" + vnfpkgop_id,
2211 "vnfpkg": "/osm/vnfpkgm/v1/vnf_packages/" + vnfpkg_id,
garciadeblas4568a372021-03-24 09:19:48 +01002212 },
delacruzramo271d2002019-12-02 21:00:37 +01002213 }
garciadeblas4568a372021-03-24 09:19:48 +01002214 self.format_on_new(
2215 vnfpkgop_desc, session["project_id"], make_public=session["public"]
2216 )
delacruzramo271d2002019-12-02 21:00:37 +01002217 ctime = vnfpkgop_desc["_admin"]["created"]
2218 vnfpkgop_desc["statusEnteredTime"] = ctime
2219 vnfpkgop_desc["startTime"] = ctime
2220 self.db.create(self.topic, vnfpkgop_desc)
2221 rollback.append({"topic": self.topic, "_id": vnfpkgop_id})
2222 self.msg.write(self.topic_msg, operation, vnfpkgop_desc)
2223 return vnfpkgop_id, None
kayal2001f71c2e82024-06-25 15:26:24 +05302224
2225
2226class NsConfigTemplateTopic(DescriptorTopic):
2227 topic = "ns_config_template"
2228 topic_msg = "nsd"
2229 schema_new = ns_config_template
2230 instantiation_params = {
2231 "vnf": vnf_schema,
2232 "vld": vld_schema,
2233 "additionalParamsForVnf": additional_params_for_vnf,
2234 }
2235
2236 def __init__(self, db, fs, msg, auth):
2237 super().__init__(db, fs, msg, auth)
2238
2239 def check_conflict_on_del(self, session, _id, db_content):
2240 """
2241 Check that there is not any NSR that uses this NS CONFIG TEMPLATE. Only NSRs belonging to this project are considered.
2242 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
2243 :param _id: ns config template internal id
2244 :param db_content: The database content of the _id
2245 :return: None or raises EngineException with the conflict
2246 """
2247 if session["force"]:
2248 return
2249 descriptor = db_content
2250 descriptor_id = descriptor.get("nsdId")
2251 if not descriptor_id: # empty nsd not uploaded
2252 return
2253
2254 # check NS CONFIG TEMPLATE used by NS
2255 ns_config_template_id = _id
2256 self.logger.info("The id is : {}".format(_id))
2257 if self.db.get_list(
2258 "nsrs", {"instantiate_params.nsConfigTemplateId": ns_config_template_id}
2259 ):
2260 raise EngineException(
2261 "There is at least one NS instance using this template",
2262 http_code=HTTPStatus.CONFLICT,
2263 )
2264
2265 def check_unique_template_name(self, edit_content, _id, session):
2266 """
2267 Check whether the name of the template is unique or not
2268 """
2269
2270 if edit_content.get("name"):
2271 name = edit_content.get("name")
2272 db_content = self.db.get_one(
2273 "ns_config_template", {"name": name}, fail_on_empty=False
2274 )
2275 if db_content is not None:
2276 if db_content.get("_id") == _id:
2277 if db_content.get("name") == name:
2278 return
2279 elif db_content.get("_id") != _id:
2280 raise EngineException(
2281 "{} of the template already exist".format(name)
2282 )
2283 else:
2284 return
2285
2286 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
2287 """
2288 Check the input data format
2289 And the edit content data too.
2290 """
2291 final_content = super().check_conflict_on_edit(
2292 session, final_content, edit_content, _id
2293 )
2294 db_content_id = self.db.get_one(
2295 "ns_config_template", {"_id": _id}, fail_on_empty=False
2296 )
2297 if not (
2298 db_content_id.get("name")
2299 and db_content_id.get("nsdId")
2300 and db_content_id.get("config")
2301 ):
2302 validate_input(edit_content, self.schema_new)
2303
2304 try:
2305 for key, value in edit_content.items():
2306 if key == "name":
2307 self.check_unique_template_name(edit_content, _id, session)
2308 elif key == "nsdId":
2309 ns_config_template = self.db.get_one(
2310 "ns_config_template", {"_id": _id}, fail_on_empty=False
2311 )
2312 if not ns_config_template.get("nsdId"):
2313 pass
2314 else:
2315 raise EngineException("Nsd id cannot be edited")
2316 elif key == "config":
2317 edit_content_param = edit_content.get("config")
2318 for key, value in edit_content_param.items():
2319 param = key
2320 param_content = value
2321 validate_input(param_content, self.instantiation_params[param])
2322 return final_content
2323 except Exception as e:
2324 raise EngineException(
2325 "Error in instantiation parameters validation: {}".format(str(e)),
2326 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
2327 )