blob: 35514a56b295bfcaf449021d12cad58165cda87c [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
rshri2d386cb2024-07-05 14:35:51 +0000414 def format_on_operation(content, operation_type, operation_params):
415 if content["current_operation"] is None:
416 op_id = str(uuid4())
417 content["current_operation"] = op_id
418 else:
419 op_id = content["current_operation"]
420 now = time()
421 if "operationHistory" not in content:
422 content["operationHistory"] = []
423
424 operation = {}
425 operation["operationType"] = operation_type
426 operation["git_operation_info"] = None
427 operation["op_id"] = op_id
428 operation["result"] = None
429 operation["workflowState"] = "PROCESSING"
430 operation["resourceState"] = "NOT_READY"
431 operation["creationDate"] = now
432 operation["endDate"] = None
433 operation["operationParams"] = operation_params
434
435 content["operationHistory"].append(operation)
436 return op_id
437
438 @staticmethod
tiernob24258a2018-10-04 18:39:49 +0200439 def format_on_edit(final_content, edit_content):
tiernobdebce92019-07-01 15:36:49 +0000440 """
441 Modifies final_content to admin information upon edition
442 :param final_content: final content to be stored at database
443 :param edit_content: user requested update content
444 :return: operation id, if this edit implies an asynchronous operation; None otherwise
445 """
tiernob24258a2018-10-04 18:39:49 +0200446 if final_content.get("_admin"):
447 now = time()
448 final_content["_admin"]["modified"] = now
tiernobdebce92019-07-01 15:36:49 +0000449 return None
tiernob24258a2018-10-04 18:39:49 +0200450
tiernobee3bad2019-12-05 12:26:01 +0000451 def _send_msg(self, action, content, not_send_msg=None):
452 if self.topic_msg and not_send_msg is not False:
agarwalat53471982020-10-08 13:06:14 +0000453 content = content.copy()
tiernob24258a2018-10-04 18:39:49 +0200454 content.pop("_admin", None)
tiernobee3bad2019-12-05 12:26:01 +0000455 if isinstance(not_send_msg, list):
456 not_send_msg.append((self.topic_msg, action, content))
457 else:
458 self.msg.write(self.topic_msg, action, content)
tiernob24258a2018-10-04 18:39:49 +0200459
tiernob4844ab2019-05-23 08:42:12 +0000460 def check_conflict_on_del(self, session, _id, db_content):
tiernob24258a2018-10-04 18:39:49 +0200461 """
462 Check if deletion can be done because of dependencies if it is not force. To override
tierno65ca36d2019-02-12 19:27:52 +0100463 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
464 :param _id: internal _id
tiernob4844ab2019-05-23 08:42:12 +0000465 :param db_content: The database content of this item _id
tiernob24258a2018-10-04 18:39:49 +0200466 :return: None if ok or raises EngineException with the conflict
467 """
468 pass
469
470 @staticmethod
tierno1c38f2f2020-03-24 11:51:39 +0000471 def _update_input_with_kwargs(desc, kwargs, yaml_format=False):
tiernob24258a2018-10-04 18:39:49 +0200472 """
473 Update descriptor with the kwargs. It contains dot separated keys
474 :param desc: dictionary to be updated
475 :param kwargs: plain dictionary to be used for updating.
tierno1c38f2f2020-03-24 11:51:39 +0000476 :param yaml_format: get kwargs values as yaml format.
delacruzramoc061f562019-04-05 11:00:02 +0200477 :return: None, 'desc' is modified. It raises EngineException.
tiernob24258a2018-10-04 18:39:49 +0200478 """
479 if not kwargs:
480 return
481 try:
482 for k, v in kwargs.items():
483 update_content = desc
484 kitem_old = None
485 klist = k.split(".")
486 for kitem in klist:
487 if kitem_old is not None:
488 update_content = update_content[kitem_old]
489 if isinstance(update_content, dict):
490 kitem_old = kitem
tiernoac55f062020-06-17 07:42:30 +0000491 if not isinstance(update_content.get(kitem_old), (dict, list)):
492 update_content[kitem_old] = {}
tiernob24258a2018-10-04 18:39:49 +0200493 elif isinstance(update_content, list):
tiernoac55f062020-06-17 07:42:30 +0000494 # key must be an index of the list, must be integer
tiernob24258a2018-10-04 18:39:49 +0200495 kitem_old = int(kitem)
tiernoac55f062020-06-17 07:42:30 +0000496 # if index greater than list, extend the list
497 if kitem_old >= len(update_content):
garciadeblas4568a372021-03-24 09:19:48 +0100498 update_content += [None] * (
499 kitem_old - len(update_content) + 1
500 )
tiernoac55f062020-06-17 07:42:30 +0000501 if not isinstance(update_content[kitem_old], (dict, list)):
502 update_content[kitem_old] = {}
tiernob24258a2018-10-04 18:39:49 +0200503 else:
504 raise EngineException(
garciadeblas4568a372021-03-24 09:19:48 +0100505 "Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(
506 k, kitem
507 )
508 )
tiernoac55f062020-06-17 07:42:30 +0000509 if v is None:
510 del update_content[kitem_old]
511 else:
512 update_content[kitem_old] = v if not yaml_format else safe_load(v)
tiernob24258a2018-10-04 18:39:49 +0200513 except KeyError:
514 raise EngineException(
garciadeblas4568a372021-03-24 09:19:48 +0100515 "Invalid query string '{}'. Descriptor does not contain '{}'".format(
516 k, kitem_old
517 )
518 )
tiernob24258a2018-10-04 18:39:49 +0200519 except ValueError:
garciadeblas4568a372021-03-24 09:19:48 +0100520 raise EngineException(
521 "Invalid query string '{}'. Expected integer index list instead of '{}'".format(
522 k, kitem
523 )
524 )
tiernob24258a2018-10-04 18:39:49 +0200525 except IndexError:
526 raise EngineException(
garciadeblas4568a372021-03-24 09:19:48 +0100527 "Invalid query string '{}'. Index '{}' out of range".format(
528 k, kitem_old
529 )
530 )
tierno1c38f2f2020-03-24 11:51:39 +0000531 except YAMLError:
532 raise EngineException("Invalid query string '{}' yaml format".format(k))
tiernob24258a2018-10-04 18:39:49 +0200533
Frank Bryden19b97522020-07-10 12:32:02 +0000534 def sol005_projection(self, data):
535 # Projection was moved to child classes
536 return data
537
K Sai Kiran57589552021-01-27 21:38:34 +0530538 def show(self, session, _id, filter_q=None, api_req=False):
tiernob24258a2018-10-04 18:39:49 +0200539 """
540 Get complete information on an topic
tierno65ca36d2019-02-12 19:27:52 +0100541 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200542 :param _id: server internal id
K Sai Kiran57589552021-01-27 21:38:34 +0530543 :param filter_q: dict: query parameter
Frank Bryden19b97522020-07-10 12:32:02 +0000544 :param api_req: True if this call is serving an external API request. False if serving internal request.
tiernob24258a2018-10-04 18:39:49 +0200545 :return: dictionary, raise exception if not found.
546 """
tierno1f029d82019-06-13 22:37:04 +0000547 if not self.multiproject:
548 filter_db = {}
549 else:
550 filter_db = self._get_project_filter(session)
delacruzramoc061f562019-04-05 11:00:02 +0200551 # To allow project&user addressing by name AS WELL AS _id
552 filter_db[BaseTopic.id_field(self.topic, _id)] = _id
Frank Bryden19b97522020-07-10 12:32:02 +0000553 data = self.db.get_one(self.topic, filter_db)
554
555 # Only perform SOL005 projection if we are serving an external request
556 if api_req:
557 self.sol005_projection(data)
Frank Bryden19b97522020-07-10 12:32:02 +0000558 return data
garciadeblas4568a372021-03-24 09:19:48 +0100559
tiernob24258a2018-10-04 18:39:49 +0200560 # TODO transform data for SOL005 URL requests
561 # TODO remove _admin if not admin
562
563 def get_file(self, session, _id, path=None, accept_header=None):
564 """
565 Only implemented for descriptor topics. Return the file content of a descriptor
tierno65ca36d2019-02-12 19:27:52 +0100566 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200567 :param _id: Identity of the item to get content
568 :param path: artifact path or "$DESCRIPTOR" or None
569 :param accept_header: Content of Accept header. Must contain applition/zip or/and text/plain
570 :return: opened file or raises an exception
571 """
garciadeblas4568a372021-03-24 09:19:48 +0100572 raise EngineException(
573 "Method get_file not valid for this topic", HTTPStatus.INTERNAL_SERVER_ERROR
574 )
tiernob24258a2018-10-04 18:39:49 +0200575
Frank Bryden19b97522020-07-10 12:32:02 +0000576 def list(self, session, filter_q=None, api_req=False):
tiernob24258a2018-10-04 18:39:49 +0200577 """
578 Get a list of the topic that matches a filter
579 :param session: contains the used login username and working project
580 :param filter_q: filter of data to be applied
Frank Bryden19b97522020-07-10 12:32:02 +0000581 :param api_req: True if this call is serving an external API request. False if serving internal request.
tiernob24258a2018-10-04 18:39:49 +0200582 :return: The list, it can be empty if no one match the filter.
583 """
584 if not filter_q:
585 filter_q = {}
tierno1f029d82019-06-13 22:37:04 +0000586 if self.multiproject:
587 filter_q.update(self._get_project_filter(session))
tiernob24258a2018-10-04 18:39:49 +0200588
589 # TODO transform data for SOL005 URL requests. Transform filtering
590 # TODO implement "field-type" query string SOL005
Frank Bryden19b97522020-07-10 12:32:02 +0000591 data = self.db.get_list(self.topic, filter_q)
592
593 # Only perform SOL005 projection if we are serving an external request
594 if api_req:
595 data = [self.sol005_projection(inst) for inst in data]
garciadeblas4568a372021-03-24 09:19:48 +0100596
Frank Bryden19b97522020-07-10 12:32:02 +0000597 return data
tiernob24258a2018-10-04 18:39:49 +0200598
tierno65ca36d2019-02-12 19:27:52 +0100599 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
tiernob24258a2018-10-04 18:39:49 +0200600 """
601 Creates a new entry into database.
602 :param rollback: list to append created items at database in case a rollback may to be done
tierno65ca36d2019-02-12 19:27:52 +0100603 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200604 :param indata: data to be inserted
605 :param kwargs: used to override the indata descriptor
606 :param headers: http request headers
tiernobdebce92019-07-01 15:36:49 +0000607 :return: _id, op_id:
608 _id: identity of the inserted data.
609 op_id: operation id if this is asynchronous, None otherwise
tiernob24258a2018-10-04 18:39:49 +0200610 """
611 try:
delacruzramo32bab472019-09-13 12:24:22 +0200612 if self.multiproject:
613 self.check_quota(session)
614
tiernob24258a2018-10-04 18:39:49 +0200615 content = self._remove_envelop(indata)
616
617 # Override descriptor with query string kwargs
618 self._update_input_with_kwargs(content, kwargs)
tierno65ca36d2019-02-12 19:27:52 +0100619 content = self._validate_input_new(content, force=session["force"])
620 self.check_conflict_on_new(session, content)
garciadeblas4568a372021-03-24 09:19:48 +0100621 op_id = self.format_on_new(
622 content, project_id=session["project_id"], make_public=session["public"]
623 )
tiernob24258a2018-10-04 18:39:49 +0200624 _id = self.db.create(self.topic, content)
625 rollback.append({"topic": self.topic, "_id": _id})
tiernobdebce92019-07-01 15:36:49 +0000626 if op_id:
627 content["op_id"] = op_id
tierno15a1f682019-10-16 09:00:13 +0000628 self._send_msg("created", content)
tiernobdebce92019-07-01 15:36:49 +0000629 return _id, op_id
tiernob24258a2018-10-04 18:39:49 +0200630 except ValidationError as e:
631 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
632
tierno65ca36d2019-02-12 19:27:52 +0100633 def upload_content(self, session, _id, indata, kwargs, headers):
tiernob24258a2018-10-04 18:39:49 +0200634 """
635 Only implemented for descriptor topics. Used for receiving content by chunks (with a transaction_id header
636 and/or gzip file. It will store and extract)
tierno65ca36d2019-02-12 19:27:52 +0100637 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200638 :param _id : the database id of entry to be updated
639 :param indata: http body request
640 :param kwargs: user query string to override parameters. NOT USED
641 :param headers: http request headers
tiernob24258a2018-10-04 18:39:49 +0200642 :return: True package has is completely uploaded or False if partial content has been uplodaed.
643 Raise exception on error
644 """
garciadeblas4568a372021-03-24 09:19:48 +0100645 raise EngineException(
646 "Method upload_content not valid for this topic",
647 HTTPStatus.INTERNAL_SERVER_ERROR,
648 )
tiernob24258a2018-10-04 18:39:49 +0200649
rshri2d386cb2024-07-05 14:35:51 +0000650 def create_gitname(self, content, session, _id=None):
651 if not self.multiproject:
652 _filter = {}
653 else:
654 _filter = self._get_project_filter(session)
garciadeblasb6025472024-08-15 09:50:55 +0200655 _filter["git_name"] = content["name"]
rshri2d386cb2024-07-05 14:35:51 +0000656 if _id:
657 _filter["_id.neq"] = _id
658 if self.db.get_one(
659 self.topic, _filter, fail_on_empty=False, fail_on_more=False
660 ):
661 n = 5
662 # using random.choices()
663 # generating random strings
664 res = "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
665 res1 = content["name"]
666 new_name1 = res1 + res
667 new_name = new_name1.lower()
668 return new_name
669 else:
670 return content["name"]
671
672 def new_profile(self, rollback, session, indata=None, kwargs=None, headers=None):
673 step = "name unique check"
674 try:
675 self.check_unique_name(session, indata["name"])
676
677 step = "validating input parameters"
678 profile_request = self._remove_envelop(indata)
679 self._update_input_with_kwargs(profile_request, kwargs)
680 profile_request = self._validate_input_new(
681 profile_request, session["force"]
682 )
683 operation_params = profile_request
684
685 step = "filling profile details from input data"
686 profile_create = self._create_profile(profile_request, session)
687
688 step = "creating profile at database"
689 self.format_on_new(
690 profile_create, session["project_id"], make_public=session["public"]
691 )
692 profile_create["current_operation"] = None
693 op_id = self.format_on_operation(
694 profile_create,
695 "create",
696 operation_params,
697 )
698
699 _id = self.db.create(self.topic, profile_create)
garciadeblas6e88d9c2024-08-15 10:55:04 +0200700 pubkey, privkey = self._generate_age_key()
701 profile_create["age_pubkey"] = self.db.encrypt(
702 pubkey, schema_version="1.11", salt=_id
703 )
704 profile_create["age_privkey"] = self.db.encrypt(
705 privkey, schema_version="1.11", salt=_id
706 )
rshri2d386cb2024-07-05 14:35:51 +0000707 rollback.append({"topic": self.topic, "_id": _id})
708 self.db.set_one(self.topic, {"_id": _id}, profile_create)
709 if op_id:
710 profile_create["op_id"] = op_id
711 self._send_msg("profile_create", {"profile_id": _id, "operation_id": op_id})
712
713 return _id, None
714 except (
715 ValidationError,
716 EngineException,
717 DbException,
718 MsgException,
719 FsException,
720 ) as e:
721 raise type(e)("{} while '{}'".format(e, step), http_code=e.http_code)
722
723 def _create_profile(self, profile_request, session):
724 profile_desc = {
725 "name": profile_request["name"],
726 "description": profile_request["description"],
727 "default": False,
728 "git_name": self.create_gitname(profile_request, session),
729 "state": "IN_CREATION",
730 "operatingState": "IN_PROGRESS",
731 "resourceState": "IN_PROGRESS.REQUEST_RECEIVED",
732 }
733 return profile_desc
734
735 def default_profile(
736 self, rollback, session, indata=None, kwargs=None, headers=None
737 ):
738 step = "validating input parameters"
739 try:
740 profile_request = self._remove_envelop(indata)
741 self._update_input_with_kwargs(profile_request, kwargs)
742 operation_params = profile_request
743
744 step = "filling profile details from input data"
745 profile_create = self._create_default_profile(profile_request, session)
746
747 step = "creating profile at database"
748 self.format_on_new(
749 profile_create, session["project_id"], make_public=session["public"]
750 )
751 profile_create["current_operation"] = None
752 self.format_on_operation(
753 profile_create,
754 "create",
755 operation_params,
756 )
757 _id = self.db.create(self.topic, profile_create)
758 rollback.append({"topic": self.topic, "_id": _id})
759 return _id
760 except (
761 ValidationError,
762 EngineException,
763 DbException,
764 MsgException,
765 FsException,
766 ) as e:
767 raise type(e)("{} while '{}'".format(e, step), http_code=e.http_code)
768
769 def _create_default_profile(self, profile_request, session):
770 profile_desc = {
771 "name": profile_request["name"],
772 "description": f"{self.topic} profile for cluster {profile_request['name']}",
773 "default": True,
774 "git_name": self.create_gitname(profile_request, session),
775 "state": "IN_CREATION",
776 "operatingState": "IN_PROGRESS",
777 "resourceState": "IN_PROGRESS.REQUEST_RECEIVED",
778 }
779 return profile_desc
780
tiernob24258a2018-10-04 18:39:49 +0200781 def delete_list(self, session, filter_q=None):
782 """
783 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 +0100784 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200785 :param filter_q: filter of data to be applied
786 :return: The deleted list, it can be empty if no one match the filter.
787 """
788 # TODO add admin to filter, validate rights
789 if not filter_q:
790 filter_q = {}
tierno1f029d82019-06-13 22:37:04 +0000791 if self.multiproject:
792 filter_q.update(self._get_project_filter(session))
tiernob24258a2018-10-04 18:39:49 +0200793 return self.db.del_list(self.topic, filter_q)
794
tiernobee3bad2019-12-05 12:26:01 +0000795 def delete_extra(self, session, _id, db_content, not_send_msg=None):
tierno65ca36d2019-02-12 19:27:52 +0100796 """
797 Delete other things apart from database entry of a item _id.
798 e.g.: other associated elements at database and other file system storage
799 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
800 :param _id: server internal id
tiernob4844ab2019-05-23 08:42:12 +0000801 :param db_content: The database content of the _id. It is already deleted when reached this method, but the
802 content is needed in same cases
tiernobee3bad2019-12-05 12:26:01 +0000803 :param not_send_msg: To not send message (False) or store content (list) instead
tiernob4844ab2019-05-23 08:42:12 +0000804 :return: None if ok or raises EngineException with the problem
tierno65ca36d2019-02-12 19:27:52 +0100805 """
806 pass
807
tiernobee3bad2019-12-05 12:26:01 +0000808 def delete(self, session, _id, dry_run=False, not_send_msg=None):
tiernob24258a2018-10-04 18:39:49 +0200809 """
810 Delete item by its internal _id
tierno65ca36d2019-02-12 19:27:52 +0100811 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200812 :param _id: server internal id
tiernob24258a2018-10-04 18:39:49 +0200813 :param dry_run: make checking but do not delete
tiernobee3bad2019-12-05 12:26:01 +0000814 :param not_send_msg: To not send message (False) or store content (list) instead
tiernobdebce92019-07-01 15:36:49 +0000815 :return: operation id (None if there is not operation), raise exception if error or not found, conflict, ...
tiernob24258a2018-10-04 18:39:49 +0200816 """
tiernob4844ab2019-05-23 08:42:12 +0000817 # To allow addressing projects and users by name AS WELL AS by _id
tiernof5f2e3f2020-03-23 14:42:10 +0000818 if not self.multiproject:
819 filter_q = {}
820 else:
821 filter_q = self._get_project_filter(session)
822 filter_q[self.id_field(self.topic, _id)] = _id
rshri2d386cb2024-07-05 14:35:51 +0000823
tiernob4844ab2019-05-23 08:42:12 +0000824 item_content = self.db.get_one(self.topic, filter_q)
kayal2001f71c2e82024-06-25 15:26:24 +0530825 nsd_id = item_content.get("_id")
tiernob4844ab2019-05-23 08:42:12 +0000826
rshri2d386cb2024-07-05 14:35:51 +0000827 if (
828 self.topic == "k8sinfra_controller"
829 or self.topic == "k8sinfra_config"
830 or self.topic == "k8sapp"
831 or self.topic == "k8sresource"
832 or self.topic == "clusters"
833 ):
834 if "state" in item_content:
835 item_content["state"] = "IN_DELETION"
836 item_content["operatingState"] = "PROCESSING"
837 self.db.set_one(self.topic, {"_id": _id}, item_content)
838
839 item_content_1 = self.db.get_one(self.topic, filter_q)
840 item_content_1["current_operation"] = None
841 op_id = self.format_on_operation(
842 item_content_1,
843 "delete",
844 None,
845 )
846
tiernob4844ab2019-05-23 08:42:12 +0000847 self.check_conflict_on_del(session, _id, item_content)
kayal2001f71c2e82024-06-25 15:26:24 +0530848
849 # While deteling ns descriptor associated ns config template should also get deleted.
850 if self.topic == "nsds":
851 ns_config_template_content = self.db.get_list(
852 "ns_config_template", {"nsdId": _id}
853 )
854 for template_content in ns_config_template_content:
855 if template_content is not None:
856 if template_content.get("nsdId") == nsd_id:
857 ns_config_template_id = template_content.get("_id")
858 self.db.del_one("ns_config_template", {"nsdId": nsd_id})
859 self.delete_extra(
860 session,
861 ns_config_template_id,
862 template_content,
863 not_send_msg=not_send_msg,
864 )
tierno65ca36d2019-02-12 19:27:52 +0100865 if dry_run:
866 return None
867 if self.multiproject and session["project_id"]:
tiernof5f2e3f2020-03-23 14:42:10 +0000868 # remove reference from project_read if there are more projects referencing it. If it last one,
869 # do not remove reference, but delete
garciadeblas4568a372021-03-24 09:19:48 +0100870 other_projects_referencing = next(
871 (
872 p
873 for p in item_content["_admin"]["projects_read"]
874 if p not in session["project_id"] and p != "ANY"
875 ),
876 None,
877 )
tiernof5f2e3f2020-03-23 14:42:10 +0000878 # check if there are projects referencing it (apart from ANY, that means, public)....
879 if other_projects_referencing:
880 # remove references but not delete
garciadeblas4568a372021-03-24 09:19:48 +0100881 update_dict_pull = {
882 "_admin.projects_read": session["project_id"],
883 "_admin.projects_write": session["project_id"],
884 }
885 self.db.set_one(
886 self.topic, filter_q, update_dict=None, pull_list=update_dict_pull
887 )
tiernobdebce92019-07-01 15:36:49 +0000888 return None
tiernof5f2e3f2020-03-23 14:42:10 +0000889 else:
garciadeblas4568a372021-03-24 09:19:48 +0100890 can_write = next(
891 (
892 p
893 for p in item_content["_admin"]["projects_write"]
894 if p == "ANY" or p in session["project_id"]
895 ),
896 None,
897 )
tiernof5f2e3f2020-03-23 14:42:10 +0000898 if not can_write:
garciadeblas4568a372021-03-24 09:19:48 +0100899 raise EngineException(
900 "You have not write permission to delete it",
901 http_code=HTTPStatus.UNAUTHORIZED,
902 )
tiernof5f2e3f2020-03-23 14:42:10 +0000903 # delete
rshri2d386cb2024-07-05 14:35:51 +0000904 if (
905 self.topic == "k8sinfra_controller"
906 or self.topic == "k8sinfra_config"
907 or self.topic == "k8sapp"
908 or self.topic == "k8sresource"
909 ):
910 self.db.set_one(self.topic, {"_id": _id}, item_content_1)
911 self._send_msg(
912 "delete",
913 {"profile_id": _id, "operation_id": op_id},
914 not_send_msg=not_send_msg,
915 )
916 elif self.topic == "clusters":
917 self.db.set_one("clusters", {"_id": _id}, item_content_1)
918 self._send_msg(
919 "delete",
920 {"cluster_id": _id, "operation_id": op_id},
921 not_send_msg=not_send_msg,
922 )
923 else:
924 self.db.del_one(self.topic, filter_q)
925 self.delete_extra(session, _id, item_content, not_send_msg=not_send_msg)
926 self._send_msg("deleted", {"_id": _id}, not_send_msg=not_send_msg)
yshahffcac5f2024-08-19 12:49:07 +0000927 return _id
tiernob24258a2018-10-04 18:39:49 +0200928
tierno65ca36d2019-02-12 19:27:52 +0100929 def edit(self, session, _id, indata=None, kwargs=None, content=None):
930 """
931 Change the content of an item
932 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
933 :param _id: server internal id
934 :param indata: contains the changes to apply
935 :param kwargs: modifies indata
936 :param content: original content of the item
tiernobdebce92019-07-01 15:36:49 +0000937 :return: op_id: operation id if this is processed asynchronously, None otherwise
tierno65ca36d2019-02-12 19:27:52 +0100938 """
tiernob24258a2018-10-04 18:39:49 +0200939 indata = self._remove_envelop(indata)
940
941 # Override descriptor with query string kwargs
942 if kwargs:
943 self._update_input_with_kwargs(indata, kwargs)
944 try:
rshri2d386cb2024-07-05 14:35:51 +0000945 if (
946 self.topic == "k8sinfra_controller"
947 or self.topic == "k8sinfra_config"
948 or self.topic == "k8sapp"
949 or self.topic == "k8sresource"
950 ):
951 check = self.db.get_one(self.topic, {"_id": _id})
952 if check["default"] is True:
953 raise EngineException(
954 "Cannot edit default profiles",
955 HTTPStatus.UNPROCESSABLE_ENTITY,
956 )
957 if "name" in indata:
958 if check["name"] == indata["name"]:
959 pass
960 else:
961 self.check_unique_name(session, indata["name"])
tierno65ca36d2019-02-12 19:27:52 +0100962 if indata and session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +0100963 raise EngineException(
964 "Cannot edit content and set to project (query string SET_PROJECT) at same time",
965 HTTPStatus.UNPROCESSABLE_ENTITY,
966 )
tiernob24258a2018-10-04 18:39:49 +0200967 # TODO self._check_edition(session, indata, _id, force)
968 if not content:
969 content = self.show(session, _id)
Frank Brydendeba68e2020-07-27 13:55:11 +0000970 indata = self._validate_input_edit(indata, content, force=session["force"])
tiernob24258a2018-10-04 18:39:49 +0200971 deep_update_rfc7396(content, indata)
tiernobdebce92019-07-01 15:36:49 +0000972
973 # To allow project addressing by name AS WELL AS _id. Get the _id, just in case the provided one is a name
974 _id = content.get("_id") or _id
975
bravofb995ea22021-02-10 10:57:52 -0300976 content = self.check_conflict_on_edit(session, content, indata, _id=_id)
tiernobdebce92019-07-01 15:36:49 +0000977 op_id = self.format_on_edit(content, indata)
978
979 self.db.replace(self.topic, _id, content)
tiernob24258a2018-10-04 18:39:49 +0200980
981 indata.pop("_admin", None)
tiernobdebce92019-07-01 15:36:49 +0000982 if op_id:
983 indata["op_id"] = op_id
tiernob24258a2018-10-04 18:39:49 +0200984 indata["_id"] = _id
rshri2d386cb2024-07-05 14:35:51 +0000985 if (
986 self.topic == "k8sinfra_controller"
987 or self.topic == "k8sinfra_config"
988 or self.topic == "k8sapp"
989 or self.topic == "k8sresource"
990 ):
991 pass
992 else:
993 self._send_msg("edited", indata)
tiernobdebce92019-07-01 15:36:49 +0000994 return op_id
tiernob24258a2018-10-04 18:39:49 +0200995 except ValidationError as e:
996 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
rshri2d386cb2024-07-05 14:35:51 +0000997
998 def detach(self, session, _id, profile_type):
999 # To detach the profiles from every cluster
1000 filter_q = {}
1001 existing_clusters = self.db.get_list("clusters", filter_q)
1002 existing_clusters_profiles = [
1003 profile["_id"]
1004 for profile in existing_clusters
1005 if profile.get("profile_type", _id)
1006 ]
1007 update_dict = None
1008 for profile in existing_clusters_profiles:
1009 filter_q = {"_id": profile}
1010 data = self.db.get_one("clusters", filter_q)
1011 if profile_type in data:
1012 profile_ids = data[profile_type]
1013 if _id in profile_ids:
1014 profile_ids.remove(_id)
1015 update_dict = {profile_type: profile_ids}
1016 self.db.set_one("clusters", filter_q, update_dict)
garciadeblas6e88d9c2024-08-15 10:55:04 +02001017
1018 def _generate_age_key(self):
1019 ident = x25519.Identity.generate()
1020 # gets the public key
garciadeblas68f62ee2024-08-19 14:04:06 +02001021 pubkey = str(ident.to_public())
garciadeblas6e88d9c2024-08-15 10:55:04 +02001022 # gets the private key
1023 privkey = str(ident)
1024 # return both public and private key
1025 return pubkey, privkey