File add_item_to_dict_cache_and_threadpool.patch of Package cobbler

Index: cobbler-3.3.3/cobbler/items/item.py
===================================================================
--- cobbler-3.3.3.orig/cobbler/items/item.py
+++ cobbler-3.3.3/cobbler/items/item.py
@@ -17,7 +17,7 @@ import logging
 import pprint
 import re
 import uuid
-from typing import Any, List, Type, Union
+from typing import Any, List, Type, Union, Dict
 
 import yaml
 
@@ -128,7 +128,7 @@ class Item:
         self._template_files = {}
         self._last_cached_mtime = 0
         self._owners: Union[list, str] = enums.VALUE_INHERITED
-        self._cached_dict = ""
+        self._cached_dict: Dict[bool, Any] = {True: None, False: None}
         self._mgmt_classes: Union[list, str] = []
         self._mgmt_parameters: Union[dict, str] = {}
         self._conceptual_parent = None
@@ -148,6 +148,28 @@ class Item:
             return self._uid == other.uid
         return False
 
+    def __setattr__(self, name, value):
+        """
+        Intercepting an attempt to assign a value to an attribute.
+
+        :name: The attribute name.
+        :value: The attribute value.
+        """
+        if (
+            name.startswith("_")
+            and not name.startswith("__")
+            and name
+            not in (
+                "_conceptual_parent",
+                "_last_cached_mtime",
+                "_cached_dict",
+                "_supported_boot_loaders",
+            )
+        ):
+            # Avoid recursive call to __setattr__
+            self.__dict__["_cached_dict"] = {True: None, False: None}
+        super().__setattr__(name, value)
+
     def _resolve(self, property_name: str) -> Any:
         """
         Resolve the ``property_name`` value in the object tree. This function traverses the tree from the object to its
@@ -902,6 +924,9 @@ class Item:
                      objects raw value.
         :return: A dictionary with all values present in this object.
         """
+        if self._cached_dict[resolved]:
+            return self._cached_dict[resolved]
+
         value = {}
         for key in self.__dict__:
             if key.startswith("_") and not key.startswith("__"):
@@ -937,6 +962,7 @@ class Item:
             value.update({"kickstart": value["autoinstall"]})
         if "autoinstall_meta" in value:
             value.update({"ks_meta": value["autoinstall_meta"]})
+        self._cached_dict[resolved] = value
         return value
 
     def serialize(self) -> dict:
Index: cobbler-3.3.3/cobbler/items/item.py
===================================================================
--- cobbler-3.3.3.orig/cobbler/items/item.py
+++ cobbler-3.3.3/cobbler/items/item.py
@@ -129,6 +129,7 @@ class Item:
         self._last_cached_mtime = 0
         self._owners: Union[list, str] = enums.VALUE_INHERITED
         self._cached_dict: Dict[bool, Any] = {True: None, False: None}
+        self._cached_dict_valid = False
         self._mgmt_classes: Union[list, str] = []
         self._mgmt_parameters: Union[dict, str] = {}
         self._conceptual_parent = None
@@ -163,13 +164,32 @@ class Item:
                 "_conceptual_parent",
                 "_last_cached_mtime",
                 "_cached_dict",
+                "_cached_dict_valid",
                 "_supported_boot_loaders",
             )
         ):
             # Avoid recursive call to __setattr__
-            self.__dict__["_cached_dict"] = {True: None, False: None}
+            self._cached_dict_valid = False
+#            self._invalidate_to_dict_cache()
         super().__setattr__(name, value)
 
+    def _invalidate_to_dict_cache(self):
+         self.__dict__["_cached_dict"] = {True: None, False: None}
+         # If this item was invalidated, we need to invalidate children
+         # and parents
+         if hasattr(self, "children"):
+             for child in self.children:
+                 child = self.api.find_items(what="", name=child)
+                 if child:
+                     child.__dict__["_cached_dict"] = {True: None, False: None}
+         self.__invalidate_parents_to_dict_cache(self)
+
+    def __invalidate_parents_to_dict_cache(self, node):
+        if not hasattr(node, "parent") or not node.parent:
+            return
+        node.parent.__dict__["_cached_dict"] = {True: None, False: None}
+        self.__invalidate_parents_to_dict_cache(node.parent)
+
     def _resolve(self, property_name: str) -> Any:
         """
         Resolve the ``property_name`` value in the object tree. This function traverses the tree from the object to its
@@ -914,7 +934,7 @@ class Item:
                     raise AttributeError("Attribute \"%s\" could not be set!" % lowered_key) from error
                 result.pop(key)
         if len(result) > 0:
-            raise KeyError("The following keys supplied could not be set: %s" % result.keys())
+            raise KeyError("The following keys supplied could not be set: %s" % list(result.keys()))
 
     def to_dict(self, resolved: bool = False) -> dict:
         """
@@ -924,13 +944,17 @@ class Item:
                      objects raw value.
         :return: A dictionary with all values present in this object.
         """
-        if self._cached_dict[resolved]:
+        # We check if cached dict exists and that children list has not changed.
+        # If children list has changed, we need to recalculate the dict.
+        if self._cached_dict[resolved] and self._cached_dict_valid and self._cached_dict[resolved]["children"] == self.children:
             return self._cached_dict[resolved]
+        elif self._cached_dict[resolved] and not self._cached_dict_valid:
+            self._invalidate_to_dict_cache()
 
         value = {}
         for key in self.__dict__:
             if key.startswith("_") and not key.startswith("__"):
-                if key in ("_conceptual_parent", "_last_cached_mtime", "_cached_dict", "_supported_boot_loaders"):
+                if key in ("_conceptual_parent", "_last_cached_mtime", "_cached_dict", "_cached_dict_valid", "_supported_boot_loaders"):
                     continue
                 new_key = key[1:].lower()
                 key_value = self.__dict__[key]
@@ -963,6 +987,7 @@ class Item:
         if "autoinstall_meta" in value:
             value.update({"ks_meta": value["autoinstall_meta"]})
         self._cached_dict[resolved] = value
+        self._cached_dict_valid = True
         return value
 
     def serialize(self) -> dict:
Index: cobbler-3.3.3/cobbler/modules/managers/in_tftpd.py
===================================================================
--- cobbler-3.3.3.orig/cobbler/modules/managers/in_tftpd.py
+++ cobbler-3.3.3/cobbler/modules/managers/in_tftpd.py
@@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fi
 import glob
 import os.path
 import shutil
+from concurrent.futures import ThreadPoolExecutor
 from typing import List
 
 from cobbler import templar
@@ -167,6 +168,8 @@ class _InTftpdManager(ManagerModule):
 
         self.logger.info("copying distros to tftpboot")
 
+        pool = ThreadPoolExecutor()
+
         # Adding in the exception handling to not blow up if files have been moved (or the path references an NFS
         # directory that's no longer mounted)
         for d in self.distros:
@@ -180,10 +183,13 @@ class _InTftpdManager(ManagerModule):
         self.tftpgen.copy_images()
 
         # the actual pxelinux.cfg files, for each interface
-        self.logger.info("generating PXE configuration files")
+        self.logger.info("generating PXE configuration files - this can take a while (to see the progress check the cobbler logs)")
         menu_items = self.tftpgen.get_menu_items()
+
         for system in self.systems:
-            self.tftpgen.write_all_system_files(system, menu_items)
+            pool.submit(self.tftpgen.write_all_system_files, system, menu_items)
+
+        pool.shutdown()
 
         self.logger.info("generating PXE menu structure")
         self.tftpgen.make_pxe_menu()

openSUSE Build Service is sponsored by