blob: a3272604e0c25dd2b24559fb15fbbf38db31d407 [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
garciadeblas6e88d9c2024-08-15 10:55:04 +020016from pyrage import x25519
yshah53cc9eb2024-07-05 13:06:31 +000017import logging
rshri2d386cb2024-07-05 14:35:51 +000018import random
19import string
tiernob24258a2018-10-04 18:39:49 +020020from uuid import uuid4
21from http import HTTPStatus
22from time import time
aticig2b5e1232022-08-10 17:30:12 +030023from osm_common.dbbase import deep_update_rfc7396, DbException
rshri2d386cb2024-07-05 14:35:51 +000024from osm_common.msgbase import MsgException
25from osm_common.fsbase import FsException
tierno23acf402019-08-28 13:36:34 +000026from osm_nbi.validation import validate_input, ValidationError, is_valid_uuid
tierno1c38f2f2020-03-24 11:51:39 +000027from yaml import safe_load, YAMLError
tiernob24258a2018-10-04 18:39:49 +020028
29__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
30
31
32class EngineException(Exception):
tiernob24258a2018-10-04 18:39:49 +020033 def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST):
34 self.http_code = http_code
tierno23acf402019-08-28 13:36:34 +000035 super(Exception, self).__init__(message)
tiernob24258a2018-10-04 18:39:49 +020036
garciadeblasf2af4a12023-01-24 16:56:54 +010037
aticig2b5e1232022-08-10 17:30:12 +030038class NBIBadArgumentsException(Exception):
39 """
40 Bad argument values exception
41 """
42
43 def __init__(self, message: str = "", bad_args: list = None):
44 Exception.__init__(self, message)
45 self.message = message
46 self.bad_args = bad_args
47
48 def __str__(self):
garciadeblasf2af4a12023-01-24 16:56:54 +010049 return "{}, Bad arguments: {}".format(self.message, self.bad_args)
50
tiernob24258a2018-10-04 18:39:49 +020051
tierno714954e2019-11-29 13:43:26 +000052def deep_get(target_dict, key_list):
53 """
54 Get a value from target_dict entering in the nested keys. If keys does not exist, it returns None
55 Example target_dict={a: {b: 5}}; key_list=[a,b] returns 5; both key_list=[a,b,c] and key_list=[f,h] return None
56 :param target_dict: dictionary to be read
57 :param key_list: list of keys to read from target_dict
58 :return: The wanted value if exist, None otherwise
59 """
60 for key in key_list:
61 if not isinstance(target_dict, dict) or key not in target_dict:
62 return None
63 target_dict = target_dict[key]
64 return target_dict
65
66
garciadeblasf2af4a12023-01-24 16:56:54 +010067def detect_descriptor_usage(descriptor: dict, db_collection: str, db: object) -> bool:
aticig2b5e1232022-08-10 17:30:12 +030068 """Detect the descriptor usage state.
69
70 Args:
71 descriptor (dict): VNF or NS Descriptor as dictionary
72 db_collection (str): collection name which is looked for in DB
73 db (object): name of db object
74
75 Returns:
76 True if descriptor is in use else None
77
78 """
79 try:
80 if not descriptor:
81 raise NBIBadArgumentsException(
82 "Argument is mandatory and can not be empty", "descriptor"
83 )
84
85 if not db:
86 raise NBIBadArgumentsException("A valid DB object should be provided", "db")
87
88 search_dict = {
89 "vnfds": ("vnfrs", "vnfd-id"),
90 "nsds": ("nsrs", "nsd-id"),
kayal2001f71c2e82024-06-25 15:26:24 +053091 "ns_config_template": ("ns_config_template", "_id"),
aticig2b5e1232022-08-10 17:30:12 +030092 }
93
94 if db_collection not in search_dict:
garciadeblasf2af4a12023-01-24 16:56:54 +010095 raise NBIBadArgumentsException(
96 "db_collection should be equal to vnfds or nsds", "db_collection"
97 )
aticig2b5e1232022-08-10 17:30:12 +030098
99 record_list = db.get_list(
100 search_dict[db_collection][0],
101 {search_dict[db_collection][1]: descriptor["_id"]},
102 )
103
104 if record_list:
105 return True
106
107 except (DbException, KeyError, NBIBadArgumentsException) as error:
garciadeblasf2af4a12023-01-24 16:56:54 +0100108 raise EngineException(
109 f"Error occured while detecting the descriptor usage: {error}"
110 )
aticig2b5e1232022-08-10 17:30:12 +0300111
112
113def update_descriptor_usage_state(
114 descriptor: dict, db_collection: str, db: object
115) -> None:
116 """Updates the descriptor usage state.
117
118 Args:
119 descriptor (dict): VNF or NS Descriptor as dictionary
120 db_collection (str): collection name which is looked for in DB
121 db (object): name of db object
122
123 Returns:
124 None
125
126 """
127 try:
128 descriptor_update = {
129 "_admin.usageState": "NOT_IN_USE",
130 }
131
132 if detect_descriptor_usage(descriptor, db_collection, db):
133 descriptor_update = {
134 "_admin.usageState": "IN_USE",
135 }
136
garciadeblasf2af4a12023-01-24 16:56:54 +0100137 db.set_one(
138 db_collection, {"_id": descriptor["_id"]}, update_dict=descriptor_update
139 )
aticig2b5e1232022-08-10 17:30:12 +0300140
141 except (DbException, KeyError, NBIBadArgumentsException) as error:
garciadeblasf2af4a12023-01-24 16:56:54 +0100142 raise EngineException(
143 f"Error occured while updating the descriptor usage state: {error}"
144 )
aticig2b5e1232022-08-10 17:30:12 +0300145
146
tiernob24258a2018-10-04 18:39:49 +0200147def get_iterable(input_var):
148 """
149 Returns an iterable, in case input_var is None it just returns an empty tuple
150 :param input_var: can be a list, tuple or None
151 :return: input_var or () if it is None
152 """
153 if input_var is None:
154 return ()
155 return input_var
156
157
158def versiontuple(v):
159 """utility for compare dot separate versions. Fills with zeros to proper number comparison"""
160 filled = []
161 for point in v.split("."):
162 filled.append(point.zfill(8))
163 return tuple(filled)
164
165
tiernocddb07d2020-10-06 08:28:00 +0000166def increment_ip_mac(ip_mac, vm_index=1):
167 if not isinstance(ip_mac, str):
168 return ip_mac
169 try:
170 # try with ipv4 look for last dot
171 i = ip_mac.rfind(".")
172 if i > 0:
173 i += 1
174 return "{}{}".format(ip_mac[:i], int(ip_mac[i:]) + vm_index)
175 # try with ipv6 or mac look for last colon. Operate in hex
176 i = ip_mac.rfind(":")
177 if i > 0:
178 i += 1
179 # format in hex, len can be 2 for mac or 4 for ipv6
garciadeblas4568a372021-03-24 09:19:48 +0100180 return ("{}{:0" + str(len(ip_mac) - i) + "x}").format(
181 ip_mac[:i], int(ip_mac[i:], 16) + vm_index
182 )
tiernocddb07d2020-10-06 08:28:00 +0000183 except Exception:
184 pass
185 return None
186
187
tiernob24258a2018-10-04 18:39:49 +0200188class BaseTopic:
189 # static variables for all instance classes
garciadeblas4568a372021-03-24 09:19:48 +0100190 topic = None # to_override
191 topic_msg = None # to_override
192 quota_name = None # to_override. If not provided topic will be used for quota_name
193 schema_new = None # to_override
tiernob24258a2018-10-04 18:39:49 +0200194 schema_edit = None # to_override
tierno65ca36d2019-02-12 19:27:52 +0100195 multiproject = True # True if this Topic can be shared by several projects. Then it contains _admin.projects_read
tiernob24258a2018-10-04 18:39:49 +0200196
delacruzramo32bab472019-09-13 12:24:22 +0200197 default_quota = 500
198
delacruzramoc061f562019-04-05 11:00:02 +0200199 # Alternative ID Fields for some Topics
garciadeblas4568a372021-03-24 09:19:48 +0100200 alt_id_field = {"projects": "name", "users": "username", "roles": "name"}
delacruzramoc061f562019-04-05 11:00:02 +0200201
delacruzramo32bab472019-09-13 12:24:22 +0200202 def __init__(self, db, fs, msg, auth):
tiernob24258a2018-10-04 18:39:49 +0200203 self.db = db
204 self.fs = fs
205 self.msg = msg
yshah53cc9eb2024-07-05 13:06:31 +0000206 self.logger = logging.getLogger("nbi.base")
delacruzramo32bab472019-09-13 12:24:22 +0200207 self.auth = auth
tiernob24258a2018-10-04 18:39:49 +0200208
209 @staticmethod
delacruzramoc061f562019-04-05 11:00:02 +0200210 def id_field(topic, value):
tierno65ca36d2019-02-12 19:27:52 +0100211 """Returns ID Field for given topic and field value"""
delacruzramoceb8baf2019-06-21 14:25:38 +0200212 if topic in BaseTopic.alt_id_field.keys() and not is_valid_uuid(value):
delacruzramoc061f562019-04-05 11:00:02 +0200213 return BaseTopic.alt_id_field[topic]
214 else:
215 return "_id"
216
217 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200218 def _remove_envelop(indata=None):
219 if not indata:
220 return {}
221 return indata
222
delacruzramo32bab472019-09-13 12:24:22 +0200223 def check_quota(self, session):
224 """
225 Check whether topic quota is exceeded by the given project
226 Used by relevant topics' 'new' function to decide whether or not creation of the new item should be allowed
tierno6b02b052020-06-02 10:07:41 +0000227 :param session[project_id]: projects (tuple) for which quota should be checked
228 :param session[force]: boolean. If true, skip quota checking
delacruzramo32bab472019-09-13 12:24:22 +0200229 :return: None
230 :raise:
231 DbException if project not found
tierno6b02b052020-06-02 10:07:41 +0000232 ValidationError if quota exceeded in one of the projects
delacruzramo32bab472019-09-13 12:24:22 +0200233 """
tiernod7749582020-05-28 10:41:10 +0000234 if session["force"]:
delacruzramo32bab472019-09-13 12:24:22 +0200235 return
236 projects = session["project_id"]
237 for project in projects:
238 proj = self.auth.get_project(project)
239 pid = proj["_id"]
tierno6b02b052020-06-02 10:07:41 +0000240 quota_name = self.quota_name or self.topic
241 quota = proj.get("quotas", {}).get(quota_name, self.default_quota)
delacruzramo32bab472019-09-13 12:24:22 +0200242 count = self.db.count(self.topic, {"_admin.projects_read": pid})
243 if count >= quota:
244 name = proj["name"]
garciadeblas4568a372021-03-24 09:19:48 +0100245 raise ValidationError(
246 "quota ({}={}) exceeded for project {} ({})".format(
247 quota_name, quota, name, pid
248 ),
249 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
250 )
delacruzramo32bab472019-09-13 12:24:22 +0200251
tiernob24258a2018-10-04 18:39:49 +0200252 def _validate_input_new(self, input, force=False):
253 """
254 Validates input user content for a new entry. It uses jsonschema. Some overrides will use pyangbind
255 :param input: user input content for the new topic
256 :param force: may be used for being more tolerant
257 :return: The same input content, or a changed version of it.
258 """
259 if self.schema_new:
260 validate_input(input, self.schema_new)
261 return input
262
Frank Brydendeba68e2020-07-27 13:55:11 +0000263 def _validate_input_edit(self, input, content, force=False):
tiernob24258a2018-10-04 18:39:49 +0200264 """
265 Validates input user content for an edition. It uses jsonschema. Some overrides will use pyangbind
266 :param input: user input content for the new topic
267 :param force: may be used for being more tolerant
268 :return: The same input content, or a changed version of it.
269 """
270 if self.schema_edit:
271 validate_input(input, self.schema_edit)
272 return input
273
274 @staticmethod
tierno65ca36d2019-02-12 19:27:52 +0100275 def _get_project_filter(session):
tiernob24258a2018-10-04 18:39:49 +0200276 """
277 Generates a filter dictionary for querying database, so that only allowed items for this project can be
tiernof5f2e3f2020-03-23 14:42:10 +0000278 addressed. Only proprietary or public can be used. Allowed projects are at _admin.project_read/write. If it is
tiernob24258a2018-10-04 18:39:49 +0200279 not present or contains ANY mean public.
tierno65ca36d2019-02-12 19:27:52 +0100280 :param session: contains:
281 project_id: project list this session has rights to access. Can be empty, one or several
garciadeblas4568a372021-03-24 09:19:48 +0100282 set_project: items created will contain this project list
tierno65ca36d2019-02-12 19:27:52 +0100283 force: True or False
284 public: True, False or None
285 method: "list", "show", "write", "delete"
286 admin: True or False
287 :return: dictionary with project filter
tiernob24258a2018-10-04 18:39:49 +0200288 """
tierno65ca36d2019-02-12 19:27:52 +0100289 p_filter = {}
290 project_filter_n = []
291 project_filter = list(session["project_id"])
tiernob24258a2018-10-04 18:39:49 +0200292
tierno65ca36d2019-02-12 19:27:52 +0100293 if session["method"] not in ("list", "delete"):
294 if project_filter:
295 project_filter.append("ANY")
296 elif session["public"] is not None:
297 if session["public"]:
298 project_filter.append("ANY")
299 else:
300 project_filter_n.append("ANY")
301
302 if session.get("PROJECT.ne"):
303 project_filter_n.append(session["PROJECT.ne"])
304
305 if project_filter:
garciadeblas4568a372021-03-24 09:19:48 +0100306 if session["method"] in ("list", "show", "delete") or session.get(
307 "set_project"
308 ):
tierno65ca36d2019-02-12 19:27:52 +0100309 p_filter["_admin.projects_read.cont"] = project_filter
310 else:
311 p_filter["_admin.projects_write.cont"] = project_filter
312 if project_filter_n:
garciadeblas4568a372021-03-24 09:19:48 +0100313 if session["method"] in ("list", "show", "delete") or session.get(
314 "set_project"
315 ):
tierno65ca36d2019-02-12 19:27:52 +0100316 p_filter["_admin.projects_read.ncont"] = project_filter_n
317 else:
318 p_filter["_admin.projects_write.ncont"] = project_filter_n
319
320 return p_filter
321
322 def check_conflict_on_new(self, session, indata):
tiernob24258a2018-10-04 18:39:49 +0200323 """
324 Check that the data to be inserted is valid
tierno65ca36d2019-02-12 19:27:52 +0100325 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200326 :param indata: data to be inserted
tiernob24258a2018-10-04 18:39:49 +0200327 :return: None or raises EngineException
328 """
329 pass
330
tierno65ca36d2019-02-12 19:27:52 +0100331 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
tiernob24258a2018-10-04 18:39:49 +0200332 """
333 Check that the data to be edited/uploaded is valid
tierno65ca36d2019-02-12 19:27:52 +0100334 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernobdebce92019-07-01 15:36:49 +0000335 :param final_content: data once modified. This method may change it.
tiernob24258a2018-10-04 18:39:49 +0200336 :param edit_content: incremental data that contains the modifications to apply
337 :param _id: internal _id
bravofb995ea22021-02-10 10:57:52 -0300338 :return: final_content or raises EngineException
tiernob24258a2018-10-04 18:39:49 +0200339 """
tierno65ca36d2019-02-12 19:27:52 +0100340 if not self.multiproject:
bravofb995ea22021-02-10 10:57:52 -0300341 return final_content
tierno65ca36d2019-02-12 19:27:52 +0100342 # Change public status
343 if session["public"] is not None:
garciadeblas4568a372021-03-24 09:19:48 +0100344 if (
345 session["public"]
346 and "ANY" not in final_content["_admin"]["projects_read"]
347 ):
tierno65ca36d2019-02-12 19:27:52 +0100348 final_content["_admin"]["projects_read"].append("ANY")
349 final_content["_admin"]["projects_write"].clear()
garciadeblas4568a372021-03-24 09:19:48 +0100350 if (
351 not session["public"]
352 and "ANY" in final_content["_admin"]["projects_read"]
353 ):
tierno65ca36d2019-02-12 19:27:52 +0100354 final_content["_admin"]["projects_read"].remove("ANY")
355
356 # Change project status
357 if session.get("set_project"):
358 for p in session["set_project"]:
359 if p not in final_content["_admin"]["projects_read"]:
360 final_content["_admin"]["projects_read"].append(p)
tiernob24258a2018-10-04 18:39:49 +0200361
bravofb995ea22021-02-10 10:57:52 -0300362 return final_content
363
tiernob24258a2018-10-04 18:39:49 +0200364 def check_unique_name(self, session, name, _id=None):
365 """
366 Check that the name is unique for this project
tierno65ca36d2019-02-12 19:27:52 +0100367 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200368 :param name: name to be checked
369 :param _id: If not None, ignore this entry that are going to change
370 :return: None or raises EngineException
371 """
tierno1f029d82019-06-13 22:37:04 +0000372 if not self.multiproject:
373 _filter = {}
374 else:
375 _filter = self._get_project_filter(session)
tiernob24258a2018-10-04 18:39:49 +0200376 _filter["name"] = name
377 if _id:
378 _filter["_id.neq"] = _id
garciadeblas4568a372021-03-24 09:19:48 +0100379 if self.db.get_one(
380 self.topic, _filter, fail_on_empty=False, fail_on_more=False
381 ):
382 raise EngineException(
383 "name '{}' already exists for {}".format(name, self.topic),
384 HTTPStatus.CONFLICT,
385 )
tiernob24258a2018-10-04 18:39:49 +0200386
387 @staticmethod
388 def format_on_new(content, project_id=None, make_public=False):
389 """
390 Modifies content descriptor to include _admin
391 :param content: descriptor to be modified
tierno65ca36d2019-02-12 19:27:52 +0100392 :param project_id: if included, it add project read/write permissions. Can be None or a list
tiernob24258a2018-10-04 18:39:49 +0200393 :param make_public: if included it is generated as public for reading.
tiernobdebce92019-07-01 15:36:49 +0000394 :return: op_id: operation id on asynchronous operation, None otherwise. In addition content is modified
tiernob24258a2018-10-04 18:39:49 +0200395 """
396 now = time()
397 if "_admin" not in content:
398 content["_admin"] = {}
399 if not content["_admin"].get("created"):
400 content["_admin"]["created"] = now
401 content["_admin"]["modified"] = now
402 if not content.get("_id"):
403 content["_id"] = str(uuid4())
tierno65ca36d2019-02-12 19:27:52 +0100404 if project_id is not None:
tiernob24258a2018-10-04 18:39:49 +0200405 if not content["_admin"].get("projects_read"):
tierno65ca36d2019-02-12 19:27:52 +0100406 content["_admin"]["projects_read"] = list(project_id)
tiernob24258a2018-10-04 18:39:49 +0200407 if make_public:
408 content["_admin"]["projects_read"].append("ANY")
409 if not content["_admin"].get("projects_write"):
tierno65ca36d2019-02-12 19:27:52 +0100410 content["_admin"]["projects_write"] = list(project_id)
tiernobdebce92019-07-01 15:36:49 +0000411 return None
tiernob24258a2018-10-04 18:39:49 +0200412
413 @staticmethod
shahithya11664ac2024-10-17 05:51:39 +0000414 def format_on_operation(
415 content, operation_type, operation_params=None, launch_workflow=True
416 ):
rshri2d386cb2024-07-05 14:35:51 +0000417 if content["current_operation"] is None:
418 op_id = str(uuid4())
419 content["current_operation"] = op_id
420 else:
421 op_id = content["current_operation"]
422 now = time()
423 if "operationHistory" not in content:
424 content["operationHistory"] = []
425
426 operation = {}
427 operation["operationType"] = operation_type
rshri2d386cb2024-07-05 14:35:51 +0000428 operation["op_id"] = op_id
429 operation["result"] = None
rshri2d386cb2024-07-05 14:35:51 +0000430 operation["creationDate"] = now
431 operation["endDate"] = None
shahithya11664ac2024-10-17 05:51:39 +0000432 if launch_workflow:
433 operation["workflowState"] = "PROCESSING"
434 operation["resourceState"] = "NOT_READY"
435 operation["git_operation_info"] = None
436 operation["operationParams"] = operation_params
rshri2d386cb2024-07-05 14:35:51 +0000437
438 content["operationHistory"].append(operation)
439 return op_id
440
441 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200442 def format_on_edit(final_content, edit_content):
tiernobdebce92019-07-01 15:36:49 +0000443 """
444 Modifies final_content to admin information upon edition
445 :param final_content: final content to be stored at database
446 :param edit_content: user requested update content
447 :return: operation id, if this edit implies an asynchronous operation; None otherwise
448 """
tiernob24258a2018-10-04 18:39:49 +0200449 if final_content.get("_admin"):
450 now = time()
451 final_content["_admin"]["modified"] = now
tiernobdebce92019-07-01 15:36:49 +0000452 return None
tiernob24258a2018-10-04 18:39:49 +0200453
tiernobee3bad2019-12-05 12:26:01 +0000454 def _send_msg(self, action, content, not_send_msg=None):
455 if self.topic_msg and not_send_msg is not False:
agarwalat53471982020-10-08 13:06:14 +0000456 content = content.copy()
tiernob24258a2018-10-04 18:39:49 +0200457 content.pop("_admin", None)
tiernobee3bad2019-12-05 12:26:01 +0000458 if isinstance(not_send_msg, list):
459 not_send_msg.append((self.topic_msg, action, content))
460 else:
461 self.msg.write(self.topic_msg, action, content)
tiernob24258a2018-10-04 18:39:49 +0200462
tiernob4844ab2019-05-23 08:42:12 +0000463 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200464 """
465 Check if deletion can be done because of dependencies if it is not force. To override
tierno65ca36d2019-02-12 19:27:52 +0100466 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
467 :param _id: internal _id
tiernob4844ab2019-05-23 08:42:12 +0000468 :param db_content: The database content of this item _id
tiernob24258a2018-10-04 18:39:49 +0200469 :return: None if ok or raises EngineException with the conflict
470 """
471 pass
472
473 @staticmethod
tierno1c38f2f2020-03-24 11:51:39 +0000474 def _update_input_with_kwargs(desc, kwargs, yaml_format=False):
tiernob24258a2018-10-04 18:39:49 +0200475 """
476 Update descriptor with the kwargs. It contains dot separated keys
477 :param desc: dictionary to be updated
478 :param kwargs: plain dictionary to be used for updating.
tierno1c38f2f2020-03-24 11:51:39 +0000479 :param yaml_format: get kwargs values as yaml format.
delacruzramoc061f562019-04-05 11:00:02 +0200480 :return: None, 'desc' is modified. It raises EngineException.
tiernob24258a2018-10-04 18:39:49 +0200481 """
482 if not kwargs:
483 return
484 try:
485 for k, v in kwargs.items():
486 update_content = desc
487 kitem_old = None
488 klist = k.split(".")
489 for kitem in klist:
490 if kitem_old is not None:
491 update_content = update_content[kitem_old]
492 if isinstance(update_content, dict):
493 kitem_old = kitem
tiernoac55f062020-06-17 07:42:30 +0000494 if not isinstance(update_content.get(kitem_old), (dict, list)):
495 update_content[kitem_old] = {}
tiernob24258a2018-10-04 18:39:49 +0200496 elif isinstance(update_content, list):
tiernoac55f062020-06-17 07:42:30 +0000497 # key must be an index of the list, must be integer
tiernob24258a2018-10-04 18:39:49 +0200498 kitem_old = int(kitem)
tiernoac55f062020-06-17 07:42:30 +0000499 # if index greater than list, extend the list
500 if kitem_old >= len(update_content):
garciadeblas4568a372021-03-24 09:19:48 +0100501 update_content += [None] * (
502 kitem_old - len(update_content) + 1
503 )
tiernoac55f062020-06-17 07:42:30 +0000504 if not isinstance(update_content[kitem_old], (dict, list)):
505 update_content[kitem_old] = {}
tiernob24258a2018-10-04 18:39:49 +0200506 else:
507 raise EngineException(
garciadeblas4568a372021-03-24 09:19:48 +0100508 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(
509 k, kitem
510 )
511 )
tiernoac55f062020-06-17 07:42:30 +0000512 if v is None:
513 del update_content[kitem_old]
514 else:
515 update_content[kitem_old] = v if not yaml_format else safe_load(v)
tiernob24258a2018-10-04 18:39:49 +0200516 except KeyError:
517 raise EngineException(
garciadeblas4568a372021-03-24 09:19:48 +0100518 "Invalid query string '{}'. Descriptor does not contain '{}'".format(
519 k, kitem_old
520 )
521 )
tiernob24258a2018-10-04 18:39:49 +0200522 except ValueError:
garciadeblas4568a372021-03-24 09:19:48 +0100523 raise EngineException(
524 "Invalid query string '{}'. Expected integer index list instead of '{}'".format(
525 k, kitem
526 )
527 )
tiernob24258a2018-10-04 18:39:49 +0200528 except IndexError:
529 raise EngineException(
garciadeblas4568a372021-03-24 09:19:48 +0100530 "Invalid query string '{}'. Index '{}' out of range".format(
531 k, kitem_old
532 )
533 )
tierno1c38f2f2020-03-24 11:51:39 +0000534 except YAMLError:
535 raise EngineException("Invalid query string '{}' yaml format".format(k))
tiernob24258a2018-10-04 18:39:49 +0200536
Frank Bryden19b97522020-07-10 12:32:02 +0000537 def sol005_projection(self, data):
538 # Projection was moved to child classes
539 return data
540
K Sai Kiran57589552021-01-27 21:38:34 +0530541 def show(self, session, _id, filter_q=None, api_req=False):
tiernob24258a2018-10-04 18:39:49 +0200542 """
543 Get complete information on an topic
tierno65ca36d2019-02-12 19:27:52 +0100544 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200545 :param _id: server internal id
K Sai Kiran57589552021-01-27 21:38:34 +0530546 :param filter_q: dict: query parameter
Frank Bryden19b97522020-07-10 12:32:02 +0000547 :param api_req: True if this call is serving an external API request. False if serving internal request.
tiernob24258a2018-10-04 18:39:49 +0200548 :return: dictionary, raise exception if not found.
549 """
tierno1f029d82019-06-13 22:37:04 +0000550 if not self.multiproject:
551 filter_db = {}
552 else:
553 filter_db = self._get_project_filter(session)
delacruzramoc061f562019-04-05 11:00:02 +0200554 # To allow project&user addressing by name AS WELL AS _id
555 filter_db[BaseTopic.id_field(self.topic, _id)] = _id
Frank Bryden19b97522020-07-10 12:32:02 +0000556 data = self.db.get_one(self.topic, filter_db)
557
558 # Only perform SOL005 projection if we are serving an external request
559 if api_req:
560 self.sol005_projection(data)
Frank Bryden19b97522020-07-10 12:32:02 +0000561 return data
garciadeblas4568a372021-03-24 09:19:48 +0100562
tiernob24258a2018-10-04 18:39:49 +0200563 # TODO transform data for SOL005 URL requests
564 # TODO remove _admin if not admin
565
566 def get_file(self, session, _id, path=None, accept_header=None):
567 """
568 Only implemented for descriptor topics. Return the file content of a descriptor
tierno65ca36d2019-02-12 19:27:52 +0100569 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200570 :param _id: Identity of the item to get content
571 :param path: artifact path or "$DESCRIPTOR" or None
572 :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
573 :return: opened file or raises an exception
574 """
garciadeblas4568a372021-03-24 09:19:48 +0100575 raise EngineException(
576 "Method get_file not valid for this topic", HTTPStatus.INTERNAL_SERVER_ERROR
577 )
tiernob24258a2018-10-04 18:39:49 +0200578
Frank Bryden19b97522020-07-10 12:32:02 +0000579 def list(self, session, filter_q=None, api_req=False):
tiernob24258a2018-10-04 18:39:49 +0200580 """
581 Get a list of the topic that matches a filter
582 :param session: contains the used login username and working project
583 :param filter_q: filter of data to be applied
Frank Bryden19b97522020-07-10 12:32:02 +0000584 :param api_req: True if this call is serving an external API request. False if serving internal request.
tiernob24258a2018-10-04 18:39:49 +0200585 :return: The list, it can be empty if no one match the filter.
586 """
587 if not filter_q:
588 filter_q = {}
tierno1f029d82019-06-13 22:37:04 +0000589 if self.multiproject:
590 filter_q.update(self._get_project_filter(session))
tiernob24258a2018-10-04 18:39:49 +0200591
592 # TODO transform data for SOL005 URL requests. Transform filtering
593 # TODO implement "field-type" query string SOL005
Frank Bryden19b97522020-07-10 12:32:02 +0000594 data = self.db.get_list(self.topic, filter_q)
595
596 # Only perform SOL005 projection if we are serving an external request
597 if api_req:
598 data = [self.sol005_projection(inst) for inst in data]
garciadeblas4568a372021-03-24 09:19:48 +0100599
Frank Bryden19b97522020-07-10 12:32:02 +0000600 return data
tiernob24258a2018-10-04 18:39:49 +0200601
tierno65ca36d2019-02-12 19:27:52 +0100602 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
tiernob24258a2018-10-04 18:39:49 +0200603 """
604 Creates a new entry into database.
605 :param rollback: list to append created items at database in case a rollback may to be done
tierno65ca36d2019-02-12 19:27:52 +0100606 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200607 :param indata: data to be inserted
608 :param kwargs: used to override the indata descriptor
609 :param headers: http request headers
tiernobdebce92019-07-01 15:36:49 +0000610 :return: _id, op_id:
611 _id: identity of the inserted data.
612 op_id: operation id if this is asynchronous, None otherwise
tiernob24258a2018-10-04 18:39:49 +0200613 """
614 try:
delacruzramo32bab472019-09-13 12:24:22 +0200615 if self.multiproject:
616 self.check_quota(session)
617
tiernob24258a2018-10-04 18:39:49 +0200618 content = self._remove_envelop(indata)
619
620 # Override descriptor with query string kwargs
621 self._update_input_with_kwargs(content, kwargs)
tierno65ca36d2019-02-12 19:27:52 +0100622 content = self._validate_input_new(content, force=session["force"])
623 self.check_conflict_on_new(session, content)
garciadeblas4568a372021-03-24 09:19:48 +0100624 op_id = self.format_on_new(
625 content, project_id=session["project_id"], make_public=session["public"]
626 )
tiernob24258a2018-10-04 18:39:49 +0200627 _id = self.db.create(self.topic, content)
628 rollback.append({"topic": self.topic, "_id": _id})
tiernobdebce92019-07-01 15:36:49 +0000629 if op_id:
630 content["op_id"] = op_id
tierno15a1f682019-10-16 09:00:13 +0000631 self._send_msg("created", content)
tiernobdebce92019-07-01 15:36:49 +0000632 return _id, op_id
tiernob24258a2018-10-04 18:39:49 +0200633 except ValidationError as e:
634 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
635
tierno65ca36d2019-02-12 19:27:52 +0100636 def upload_content(self, session, _id, indata, kwargs, headers):
tiernob24258a2018-10-04 18:39:49 +0200637 """
638 Only implemented for descriptor topics. Used for receiving content by chunks (with a transaction_id header
639 and/or gzip file. It will store and extract)
tierno65ca36d2019-02-12 19:27:52 +0100640 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200641 :param _id : the database id of entry to be updated
642 :param indata: http body request
643 :param kwargs: user query string to override parameters. NOT USED
644 :param headers: http request headers
tiernob24258a2018-10-04 18:39:49 +0200645 :return: True package has is completely uploaded or False if partial content has been uplodaed.
646 Raise exception on error
647 """
garciadeblas4568a372021-03-24 09:19:48 +0100648 raise EngineException(
649 "Method upload_content not valid for this topic",
650 HTTPStatus.INTERNAL_SERVER_ERROR,
651 )
tiernob24258a2018-10-04 18:39:49 +0200652
rshri2d386cb2024-07-05 14:35:51 +0000653 def create_gitname(self, content, session, _id=None):
654 if not self.multiproject:
655 _filter = {}
656 else:
657 _filter = self._get_project_filter(session)
garciadeblasb6025472024-08-15 09:50:55 +0200658 _filter["git_name"] = content["name"]
rshri2d386cb2024-07-05 14:35:51 +0000659 if _id:
660 _filter["_id.neq"] = _id
661 if self.db.get_one(
662 self.topic, _filter, fail_on_empty=False, fail_on_more=False
663 ):
664 n = 5
665 # using random.choices()
666 # generating random strings
667 res = "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
668 res1 = content["name"]
669 new_name1 = res1 + res
670 new_name = new_name1.lower()
671 return new_name
672 else:
673 return content["name"]
674
675 def new_profile(self, rollback, session, indata=None, kwargs=None, headers=None):
676 step = "name unique check"
677 try:
678 self.check_unique_name(session, indata["name"])
679
680 step = "validating input parameters"
681 profile_request = self._remove_envelop(indata)
682 self._update_input_with_kwargs(profile_request, kwargs)
683 profile_request = self._validate_input_new(
684 profile_request, session["force"]
685 )
686 operation_params = profile_request
687
688 step = "filling profile details from input data"
689 profile_create = self._create_profile(profile_request, session)
690
691 step = "creating profile at database"
692 self.format_on_new(
693 profile_create, session["project_id"], make_public=session["public"]
694 )
695 profile_create["current_operation"] = None
696 op_id = self.format_on_operation(
697 profile_create,
698 "create",
699 operation_params,
700 )
701
702 _id = self.db.create(self.topic, profile_create)
garciadeblas6e88d9c2024-08-15 10:55:04 +0200703 pubkey, privkey = self._generate_age_key()
704 profile_create["age_pubkey"] = self.db.encrypt(
705 pubkey, schema_version="1.11", salt=_id
706 )
707 profile_create["age_privkey"] = self.db.encrypt(
708 privkey, schema_version="1.11", salt=_id
709 )
rshri2d386cb2024-07-05 14:35:51 +0000710 rollback.append({"topic": self.topic, "_id": _id})
711 self.db.set_one(self.topic, {"_id": _id}, profile_create)
712 if op_id:
713 profile_create["op_id"] = op_id
714 self._send_msg("profile_create", {"profile_id": _id, "operation_id": op_id})
715
716 return _id, None
717 except (
718 ValidationError,
719 EngineException,
720 DbException,
721 MsgException,
722 FsException,
723 ) as e:
724 raise type(e)("{} while '{}'".format(e, step), http_code=e.http_code)
725
726 def _create_profile(self, profile_request, session):
727 profile_desc = {
728 "name": profile_request["name"],
729 "description": profile_request["description"],
730 "default": False,
731 "git_name": self.create_gitname(profile_request, session),
732 "state": "IN_CREATION",
733 "operatingState": "IN_PROGRESS",
734 "resourceState": "IN_PROGRESS.REQUEST_RECEIVED",
735 }
736 return profile_desc
737
738 def default_profile(
739 self, rollback, session, indata=None, kwargs=None, headers=None
740 ):
741 step = "validating input parameters"
742 try:
743 profile_request = self._remove_envelop(indata)
744 self._update_input_with_kwargs(profile_request, kwargs)
745 operation_params = profile_request
746
747 step = "filling profile details from input data"
748 profile_create = self._create_default_profile(profile_request, session)
749
750 step = "creating profile at database"
751 self.format_on_new(
752 profile_create, session["project_id"], make_public=session["public"]
753 )
754 profile_create["current_operation"] = None
755 self.format_on_operation(
756 profile_create,
757 "create",
758 operation_params,
759 )
760 _id = self.db.create(self.topic, profile_create)
761 rollback.append({"topic": self.topic, "_id": _id})
762 return _id
763 except (
764 ValidationError,
765 EngineException,
766 DbException,
767 MsgException,
768 FsException,
769 ) as e:
770 raise type(e)("{} while '{}'".format(e, step), http_code=e.http_code)
771
772 def _create_default_profile(self, profile_request, session):
773 profile_desc = {
774 "name": profile_request["name"],
775 "description": f"{self.topic} profile for cluster {profile_request['name']}",
776 "default": True,
777 "git_name": self.create_gitname(profile_request, session),
778 "state": "IN_CREATION",
779 "operatingState": "IN_PROGRESS",
780 "resourceState": "IN_PROGRESS.REQUEST_RECEIVED",
781 }
782 return profile_desc
783
tiernob24258a2018-10-04 18:39:49 +0200784 def delete_list(self, session, filter_q=None):
785 """
786 Delete a several entries of a topic. This is for internal usage and test only, not exposed to NBI API
tierno65ca36d2019-02-12 19:27:52 +0100787 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200788 :param filter_q: filter of data to be applied
789 :return: The deleted list, it can be empty if no one match the filter.
790 """
791 # TODO add admin to filter, validate rights
792 if not filter_q:
793 filter_q = {}
tierno1f029d82019-06-13 22:37:04 +0000794 if self.multiproject:
795 filter_q.update(self._get_project_filter(session))
tiernob24258a2018-10-04 18:39:49 +0200796 return self.db.del_list(self.topic, filter_q)
797
tiernobee3bad2019-12-05 12:26:01 +0000798 def delete_extra(self, session, _id, db_content, not_send_msg=None):
tierno65ca36d2019-02-12 19:27:52 +0100799 """
800 Delete other things apart from database entry of a item _id.
801 e.g.: other associated elements at database and other file system storage
802 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
803 :param _id: server internal id
tiernob4844ab2019-05-23 08:42:12 +0000804 :param db_content: The database content of the _id. It is already deleted when reached this method, but the
805 content is needed in same cases
tiernobee3bad2019-12-05 12:26:01 +0000806 :param not_send_msg: To not send message (False) or store content (list) instead
tiernob4844ab2019-05-23 08:42:12 +0000807 :return: None if ok or raises EngineException with the problem
tierno65ca36d2019-02-12 19:27:52 +0100808 """
809 pass
810
tiernobee3bad2019-12-05 12:26:01 +0000811 def delete(self, session, _id, dry_run=False, not_send_msg=None):
tiernob24258a2018-10-04 18:39:49 +0200812 """
813 Delete item by its internal _id
tierno65ca36d2019-02-12 19:27:52 +0100814 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200815 :param _id: server internal id
tiernob24258a2018-10-04 18:39:49 +0200816 :param dry_run: make checking but do not delete
tiernobee3bad2019-12-05 12:26:01 +0000817 :param not_send_msg: To not send message (False) or store content (list) instead
tiernobdebce92019-07-01 15:36:49 +0000818 :return: operation id (None if there is not operation), raise exception if error or not found, conflict, ...
tiernob24258a2018-10-04 18:39:49 +0200819 """
tiernob4844ab2019-05-23 08:42:12 +0000820 # To allow addressing projects and users by name AS WELL AS by _id
tiernof5f2e3f2020-03-23 14:42:10 +0000821 if not self.multiproject:
822 filter_q = {}
823 else:
824 filter_q = self._get_project_filter(session)
825 filter_q[self.id_field(self.topic, _id)] = _id
rshri2d386cb2024-07-05 14:35:51 +0000826
tiernob4844ab2019-05-23 08:42:12 +0000827 item_content = self.db.get_one(self.topic, filter_q)
kayal2001f71c2e82024-06-25 15:26:24 +0530828 nsd_id = item_content.get("_id")
tiernob4844ab2019-05-23 08:42:12 +0000829
rshri2d386cb2024-07-05 14:35:51 +0000830 if (
831 self.topic == "k8sinfra_controller"
832 or self.topic == "k8sinfra_config"
833 or self.topic == "k8sapp"
834 or self.topic == "k8sresource"
835 or self.topic == "clusters"
836 ):
837 if "state" in item_content:
838 item_content["state"] = "IN_DELETION"
839 item_content["operatingState"] = "PROCESSING"
840 self.db.set_one(self.topic, {"_id": _id}, item_content)
841
842 item_content_1 = self.db.get_one(self.topic, filter_q)
843 item_content_1["current_operation"] = None
844 op_id = self.format_on_operation(
845 item_content_1,
846 "delete",
847 None,
848 )
849
tiernob4844ab2019-05-23 08:42:12 +0000850 self.check_conflict_on_del(session, _id, item_content)
kayal2001f71c2e82024-06-25 15:26:24 +0530851
852 # While deteling ns descriptor associated ns config template should also get deleted.
853 if self.topic == "nsds":
854 ns_config_template_content = self.db.get_list(
855 "ns_config_template", {"nsdId": _id}
856 )
857 for template_content in ns_config_template_content:
858 if template_content is not None:
859 if template_content.get("nsdId") == nsd_id:
860 ns_config_template_id = template_content.get("_id")
861 self.db.del_one("ns_config_template", {"nsdId": nsd_id})
862 self.delete_extra(
863 session,
864 ns_config_template_id,
865 template_content,
866 not_send_msg=not_send_msg,
867 )
tierno65ca36d2019-02-12 19:27:52 +0100868 if dry_run:
869 return None
870 if self.multiproject and session["project_id"]:
tiernof5f2e3f2020-03-23 14:42:10 +0000871 # remove reference from project_read if there are more projects referencing it. If it last one,
872 # do not remove reference, but delete
garciadeblas4568a372021-03-24 09:19:48 +0100873 other_projects_referencing = next(
874 (
875 p
876 for p in item_content["_admin"]["projects_read"]
877 if p not in session["project_id"] and p != "ANY"
878 ),
879 None,
880 )
tiernof5f2e3f2020-03-23 14:42:10 +0000881 # check if there are projects referencing it (apart from ANY, that means, public)....
882 if other_projects_referencing:
883 # remove references but not delete
garciadeblas4568a372021-03-24 09:19:48 +0100884 update_dict_pull = {
885 "_admin.projects_read": session["project_id"],
886 "_admin.projects_write": session["project_id"],
887 }
888 self.db.set_one(
889 self.topic, filter_q, update_dict=None, pull_list=update_dict_pull
890 )
tiernobdebce92019-07-01 15:36:49 +0000891 return None
tiernof5f2e3f2020-03-23 14:42:10 +0000892 else:
garciadeblas4568a372021-03-24 09:19:48 +0100893 can_write = next(
894 (
895 p
896 for p in item_content["_admin"]["projects_write"]
897 if p == "ANY" or p in session["project_id"]
898 ),
899 None,
900 )
tiernof5f2e3f2020-03-23 14:42:10 +0000901 if not can_write:
garciadeblas4568a372021-03-24 09:19:48 +0100902 raise EngineException(
903 "You have not write permission to delete it",
904 http_code=HTTPStatus.UNAUTHORIZED,
905 )
tiernof5f2e3f2020-03-23 14:42:10 +0000906 # delete
rshri2d386cb2024-07-05 14:35:51 +0000907 if (
908 self.topic == "k8sinfra_controller"
909 or self.topic == "k8sinfra_config"
910 or self.topic == "k8sapp"
911 or self.topic == "k8sresource"
912 ):
913 self.db.set_one(self.topic, {"_id": _id}, item_content_1)
914 self._send_msg(
915 "delete",
916 {"profile_id": _id, "operation_id": op_id},
917 not_send_msg=not_send_msg,
918 )
919 elif self.topic == "clusters":
920 self.db.set_one("clusters", {"_id": _id}, item_content_1)
921 self._send_msg(
922 "delete",
923 {"cluster_id": _id, "operation_id": op_id},
924 not_send_msg=not_send_msg,
925 )
926 else:
927 self.db.del_one(self.topic, filter_q)
928 self.delete_extra(session, _id, item_content, not_send_msg=not_send_msg)
929 self._send_msg("deleted", {"_id": _id}, not_send_msg=not_send_msg)
yshahffcac5f2024-08-19 12:49:07 +0000930 return _id
tiernob24258a2018-10-04 18:39:49 +0200931
tierno65ca36d2019-02-12 19:27:52 +0100932 def edit(self, session, _id, indata=None, kwargs=None, content=None):
933 """
934 Change the content of an item
935 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
936 :param _id: server internal id
937 :param indata: contains the changes to apply
938 :param kwargs: modifies indata
939 :param content: original content of the item
tiernobdebce92019-07-01 15:36:49 +0000940 :return: op_id: operation id if this is processed asynchronously, None otherwise
tierno65ca36d2019-02-12 19:27:52 +0100941 """
tiernob24258a2018-10-04 18:39:49 +0200942 indata = self._remove_envelop(indata)
943
944 # Override descriptor with query string kwargs
945 if kwargs:
946 self._update_input_with_kwargs(indata, kwargs)
947 try:
rshri2d386cb2024-07-05 14:35:51 +0000948 if (
949 self.topic == "k8sinfra_controller"
950 or self.topic == "k8sinfra_config"
951 or self.topic == "k8sapp"
952 or self.topic == "k8sresource"
953 ):
954 check = self.db.get_one(self.topic, {"_id": _id})
955 if check["default"] is True:
956 raise EngineException(
957 "Cannot edit default profiles",
958 HTTPStatus.UNPROCESSABLE_ENTITY,
959 )
960 if "name" in indata:
961 if check["name"] == indata["name"]:
962 pass
963 else:
964 self.check_unique_name(session, indata["name"])
tierno65ca36d2019-02-12 19:27:52 +0100965 if indata and session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +0100966 raise EngineException(
967 "Cannot edit content and set to project (query string SET_PROJECT) at same time",
968 HTTPStatus.UNPROCESSABLE_ENTITY,
969 )
tiernob24258a2018-10-04 18:39:49 +0200970 # TODO self._check_edition(session, indata, _id, force)
971 if not content:
972 content = self.show(session, _id)
Frank Brydendeba68e2020-07-27 13:55:11 +0000973 indata = self._validate_input_edit(indata, content, force=session["force"])
tiernob24258a2018-10-04 18:39:49 +0200974 deep_update_rfc7396(content, indata)
tiernobdebce92019-07-01 15:36:49 +0000975
976 # To allow project addressing by name AS WELL AS _id. Get the _id, just in case the provided one is a name
977 _id = content.get("_id") or _id
978
bravofb995ea22021-02-10 10:57:52 -0300979 content = self.check_conflict_on_edit(session, content, indata, _id=_id)
tiernobdebce92019-07-01 15:36:49 +0000980 op_id = self.format_on_edit(content, indata)
981
982 self.db.replace(self.topic, _id, content)
tiernob24258a2018-10-04 18:39:49 +0200983
984 indata.pop("_admin", None)
tiernobdebce92019-07-01 15:36:49 +0000985 if op_id:
986 indata["op_id"] = op_id
tiernob24258a2018-10-04 18:39:49 +0200987 indata["_id"] = _id
rshri2d386cb2024-07-05 14:35:51 +0000988 if (
989 self.topic == "k8sinfra_controller"
990 or self.topic == "k8sinfra_config"
991 or self.topic == "k8sapp"
992 or self.topic == "k8sresource"
993 ):
994 pass
995 else:
996 self._send_msg("edited", indata)
tiernobdebce92019-07-01 15:36:49 +0000997 return op_id
tiernob24258a2018-10-04 18:39:49 +0200998 except ValidationError as e:
999 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
rshri2d386cb2024-07-05 14:35:51 +00001000
1001 def detach(self, session, _id, profile_type):
1002 # To detach the profiles from every cluster
1003 filter_q = {}
1004 existing_clusters = self.db.get_list("clusters", filter_q)
1005 existing_clusters_profiles = [
1006 profile["_id"]
1007 for profile in existing_clusters
1008 if profile.get("profile_type", _id)
1009 ]
1010 update_dict = None
1011 for profile in existing_clusters_profiles:
1012 filter_q = {"_id": profile}
1013 data = self.db.get_one("clusters", filter_q)
1014 if profile_type in data:
1015 profile_ids = data[profile_type]
1016 if _id in profile_ids:
1017 profile_ids.remove(_id)
1018 update_dict = {profile_type: profile_ids}
1019 self.db.set_one("clusters", filter_q, update_dict)
garciadeblas6e88d9c2024-08-15 10:55:04 +02001020
1021 def _generate_age_key(self):
1022 ident = x25519.Identity.generate()
1023 # gets the public key
garciadeblas68f62ee2024-08-19 14:04:06 +02001024 pubkey = str(ident.to_public())
garciadeblas6e88d9c2024-08-15 10:55:04 +02001025 # gets the private key
1026 privkey = str(ident)
1027 # return both public and private key
1028 return pubkey, privkey