diff --git a/backend/ciso_assistant/settings.py b/backend/ciso_assistant/settings.py index 0cb7b68a73..13e975070a 100644 --- a/backend/ciso_assistant/settings.py +++ b/backend/ciso_assistant/settings.py @@ -281,6 +281,9 @@ def set_ciso_assistant_url(_, __, event_dict): "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db/ciso-assistant.sqlite3", + "OPTIONS": { + 'timeout': 120, + }, } } logger.info("DATABASE ENGINE: %s", DATABASES["default"]["ENGINE"]) diff --git a/backend/library/utils.py b/backend/library/utils.py index be7603a038..e8f59c191f 100644 --- a/backend/library/utils.py +++ b/backend/library/utils.py @@ -1,4 +1,5 @@ import json +import time import os from typing import List, Union @@ -15,6 +16,7 @@ from django.db import transaction from iam.models import Folder +from django.db.utils import OperationalError def get_available_library_files(): """ @@ -138,7 +140,6 @@ def import_requirement_node(self, framework_object: Framework): SecurityFunction.objects.get(urn=security_function.lower()) ) - # The couple (URN, locale) is unique. ===> Check it in the future class FrameworkImporter: REQUIRED_FIELDS = {"ref_id", "urn"} @@ -505,6 +506,16 @@ def import_objects(self, library_object): for risk_matrix in self._risk_matrices: risk_matrix.import_risk_matrix(library_object) + @transaction.atomic + def _import_library(self) : + library_object = self.create_or_update_library() + self.import_objects(library_object) + library_object.dependencies.set( + Library.objects.filter( + urn__in=self._library_data.get("dependencies", []) + ) + ) + def import_library(self): """Main method to import a library.""" if (error_message := self.init()) is not None: @@ -512,20 +523,29 @@ def import_library(self): self.check_and_import_dependencies() - try: - with transaction.atomic(): - library_object = self.create_or_update_library() - self.import_objects(library_object) - library_object.dependencies.set( - Library.objects.filter( - urn__in=self._library_data.get("dependencies", []) - ) - ) - except Exception as e: - # TODO: Switch to proper logging - print(f"Library import exception: {e}") - raise - + TIMEOUTS = [1,2,3,4,10,15,25] # Put this in settings.py later if we keep it. + # The total timeout is sum(TIMEOUTS) = 60s, the timeouts are progressive to not make the user wait too much but not spamming sqlite at the same time. + for timeout in TIMEOUTS : + try: + self._import_library() + break + """with transaction.atomic(): + library_object = self.create_or_update_library() + self.import_objects(library_object) + library_object.dependencies.set( + Library.objects.filter( + urn__in=self._library_data.get("dependencies", []) + ) + )""" + except OperationalError as e : + if e.args and e.args[0] == 'database is locked' : + time.sleep(timeout) + else : + raise e + except Exception as e : + # TODO: Switch to proper logging + print(f"Library import exception: {e}") + raise e def import_library_view(library: dict) -> Union[str, None]: """ diff --git a/backend/library/views.py b/backend/library/views.py index 05096a6c4e..55c99679a0 100644 --- a/backend/library/views.py +++ b/backend/library/views.py @@ -53,7 +53,8 @@ def destroy(self, request, *args, pk, **kwargs): if library is None: return Response(data="Library not found.", status=status.HTTP_404_NOT_FOUND) - if library["reference_count"] != 0: + # "reference_count" is not always defined (is this normal ?) + if library.get("reference_count",0) != 0 : return Response( data="Library cannot be deleted because it has references.", status=status.HTTP_400_BAD_REQUEST, @@ -100,7 +101,6 @@ def import_library(self, request, pk=None): import_library_view(library) return Response({"status": "success"}) except Exception as e: - print(e) return Response( { "error": "Failed to load library, please check if it has dependencies"