We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Deleting a system (or any object) may not persist across cobbler restarts
Problem is seen in 

```
Cobbler 3.1.2
  source: ?, ?
  build time: Sun May 31 02:32:34 2020
```

This bug was actually fixed via commit 63d8312bac0bc4a1567d7e511beddfa89f4e1562 in December 2020.  File modules/serializers/file.py functions serialize_item() and serialize_delete() were appending the string ".json" to the filename twice.    Rather than trying to delete file "object.json", serialize_delete() would try to delete "object.json.json" which didn't exist, and so the code did nothing.  After a cobbler restart, the "object.json" file would be seen and the object re-appeared in cobbler.   In this case the object was created before the last cobbler upgrade, so it has the "object.json" filename format, it didn't have the ".json.json" suffix.  Objects created after the last cobbler upgrade all have the ".json.json" suffix, there is no "object.json" file.

The duplication was removed, but this leaves existing sites with a mess in their collections subdirectories.  Depending upon when they created objects relative to when they last upgraded cobbler, they can have files with name "object.json", "object.json.json", or both files can exist simultaneously.

Seems like there should be some code added to detect this situation, and rename the "object.json.json" files back to their appropriate "object.json" names.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cobbler/modules/serializers/file.py`
Content:
```
1 """
2 Cobbler's file-based object serializer.
3 As of 9/2014, this is Cobbler's default serializer and the most stable one.
4 It uses multiple JSON files in /var/lib/cobbler/collections/distros, profiles, etc
5 
6 Copyright 2006-2009, Red Hat, Inc and Others
7 Michael DeHaan <michael.dehaan AT gmail>
8 
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 02110-1301  USA
23 """
24 
25 import os
26 import glob
27 import simplejson
28 
29 import cobbler.api as capi
30 from cobbler import settings
31 from cobbler.cexceptions import CX
32 
33 libpath = "/var/lib/cobbler/collections"
34 
35 
36 def register():
37     """
38     The mandatory Cobbler module registration hook.
39     """
40     return "serializer"
41 
42 
43 def what():
44     """
45     Module identification function
46     """
47     return "serializer/file"
48 
49 
50 def serialize_item(collection, item):
51     """
52     Save a collection item to file system
53 
54     :param collection: collection
55     :param item: collection item
56     """
57 
58     if not item.name:
59         raise CX("name unset for item!")
60 
61     collection_types = collection.collection_types()
62     filename = os.path.join(libpath, collection_types, item.name + ".json")
63 
64     _dict = item.to_dict()
65 
66     if capi.CobblerAPI().settings().serializer_pretty_json:
67         sort_keys = True
68         indent = 4
69     else:
70         sort_keys = False
71         indent = None
72 
73     _dict = item.to_dict()
74     with open(filename, "w+") as fd:
75         data = simplejson.dumps(_dict, encoding="utf-8", sort_keys=sort_keys, indent=indent)
76         fd.write(data)
77 
78 
79 def serialize_delete(collection, item):
80     """
81     Delete a collection item from file system.
82 
83     :param collection: collection
84     :param item: collection item
85     """
86 
87     collection_types = collection.collection_types()
88     filename = os.path.join(libpath, collection_types, item.name + ".json")
89 
90     if os.path.exists(filename):
91         os.remove(filename)
92 
93 
94 def serialize(collection):
95     """
96     Save a collection to file system
97 
98     :param collection: collection
99     """
100 
101     # do not serialize settings
102     ctype = collection.collection_type()
103     if ctype != "settings":
104         for x in collection:
105             serialize_item(collection, x)
106 
107 
108 def deserialize_raw(collection_types):
109     """
110     Loads a collection from the disk.
111 
112     :param collection_types: The type of collection to load.
113     :return: The loaded dictionary.
114     """
115     if collection_types == "settings":
116         return settings.read_settings_file()
117     else:
118         results = []
119 
120         path = os.path.join(libpath, collection_types)
121         all_files = glob.glob("%s/*.json" % path)
122 
123         for f in all_files:
124             with open(f) as fd:
125                 json_data = fd.read()
126                 _dict = simplejson.loads(json_data, encoding='utf-8')
127                 results.append(_dict)
128         return results
129 
130 
131 def filter_upgrade_duplicates(file_list):
132     """
133     In a set of files, some ending with .json, some not, return the list of files with the .json ones taking priority
134     over the ones that are not.
135 
136     :param file_list: The list of files to remove duplicates from.
137     :return: The filtered list of files. Normally this should only return ``.json``-Files.
138     """
139     bases = {}
140     for f in file_list:
141         basekey = f.replace(".json", "")
142         if f.endswith(".json"):
143             bases[basekey] = f
144         else:
145             lookup = bases.get(basekey, "")
146             if not lookup.endswith(".json"):
147                 bases[basekey] = f
148     return list(bases.values())
149 
150 
151 def deserialize(collection, topological=True):
152     """
153     Load a collection from file system.
154 
155     :param collection: The collection to deserialize.
156     :param topological: If the collection list should be sorted by the
157                         collection dict depth value or not.
158     :type topological: bool
159     """
160 
161     datastruct = deserialize_raw(collection.collection_types())
162     if topological and type(datastruct) == list:
163         datastruct.sort(key=lambda x: x["depth"])
164     if type(datastruct) == dict:
165         collection.from_dict(datastruct)
166     elif type(datastruct) == list:
167         collection.from_list(datastruct)
168
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/cobbler/modules/serializers/file.py b/cobbler/modules/serializers/file.py
--- a/cobbler/modules/serializers/file.py
+++ b/cobbler/modules/serializers/file.py
@@ -33,20 +33,35 @@
 libpath = "/var/lib/cobbler/collections"
 
 
-def register():
+def register() -> str:
     """
     The mandatory Cobbler module registration hook.
     """
     return "serializer"
 
 
-def what():
+def what() -> str:
     """
     Module identification function
     """
     return "serializer/file"
 
 
+def __find_double_json_files(filename: str):
+    """
+    Finds a file with duplicate .json ending and renames it.
+    :param filename: Filename to be checked
+    :raises FileExistsError: If both JSON files exist
+    """
+
+    if not os.path.isfile(filename):
+        if os.path.isfile(filename + ".json"):
+            os.rename(filename + ".json", filename)
+    else:
+        if os.path.isfile(filename + ".json"):
+            raise FileExistsError("Both JSON files (%s) exist!" % filename)
+
+
 def serialize_item(collection, item):
     """
     Save a collection item to file system
@@ -60,6 +75,7 @@
 
     collection_types = collection.collection_types()
     filename = os.path.join(libpath, collection_types, item.name + ".json")
+    __find_double_json_files(filename)
 
     _dict = item.to_dict()
 
@@ -86,6 +102,7 @@
 
     collection_types = collection.collection_types()
     filename = os.path.join(libpath, collection_types, item.name + ".json")
+    __find_double_json_files(filename)
 
     if os.path.exists(filename):
         os.remove(filename)
@@ -128,26 +145,6 @@
         return results
 
 
-def filter_upgrade_duplicates(file_list):
-    """
-    In a set of files, some ending with .json, some not, return the list of files with the .json ones taking priority
-    over the ones that are not.
-
-    :param file_list: The list of files to remove duplicates from.
-    :return: The filtered list of files. Normally this should only return ``.json``-Files.
-    """
-    bases = {}
-    for f in file_list:
-        basekey = f.replace(".json", "")
-        if f.endswith(".json"):
-            bases[basekey] = f
-        else:
-            lookup = bases.get(basekey, "")
-            if not lookup.endswith(".json"):
-                bases[basekey] = f
-    return list(bases.values())
-
-
 def deserialize(collection, topological=True):
     """
     Load a collection from file system.

{"golden_diff": "diff --git a/cobbler/modules/serializers/file.py b/cobbler/modules/serializers/file.py\n--- a/cobbler/modules/serializers/file.py\n+++ b/cobbler/modules/serializers/file.py\n@@ -33,20 +33,35 @@\n libpath = \"/var/lib/cobbler/collections\"\n \n \n-def register():\n+def register() -> str:\n     \"\"\"\n     The mandatory Cobbler module registration hook.\n     \"\"\"\n     return \"serializer\"\n \n \n-def what():\n+def what() -> str:\n     \"\"\"\n     Module identification function\n     \"\"\"\n     return \"serializer/file\"\n \n \n+def __find_double_json_files(filename: str):\n+    \"\"\"\n+    Finds a file with duplicate .json ending and renames it.\n+    :param filename: Filename to be checked\n+    :raises FileExistsError: If both JSON files exist\n+    \"\"\"\n+\n+    if not os.path.isfile(filename):\n+        if os.path.isfile(filename + \".json\"):\n+            os.rename(filename + \".json\", filename)\n+    else:\n+        if os.path.isfile(filename + \".json\"):\n+            raise FileExistsError(\"Both JSON files (%s) exist!\" % filename)\n+\n+\n def serialize_item(collection, item):\n     \"\"\"\n     Save a collection item to file system\n@@ -60,6 +75,7 @@\n \n     collection_types = collection.collection_types()\n     filename = os.path.join(libpath, collection_types, item.name + \".json\")\n+    __find_double_json_files(filename)\n \n     _dict = item.to_dict()\n \n@@ -86,6 +102,7 @@\n \n     collection_types = collection.collection_types()\n     filename = os.path.join(libpath, collection_types, item.name + \".json\")\n+    __find_double_json_files(filename)\n \n     if os.path.exists(filename):\n         os.remove(filename)\n@@ -128,26 +145,6 @@\n         return results\n \n \n-def filter_upgrade_duplicates(file_list):\n-    \"\"\"\n-    In a set of files, some ending with .json, some not, return the list of files with the .json ones taking priority\n-    over the ones that are not.\n-\n-    :param file_list: The list of files to remove duplicates from.\n-    :return: The filtered list of files. Normally this should only return ``.json``-Files.\n-    \"\"\"\n-    bases = {}\n-    for f in file_list:\n-        basekey = f.replace(\".json\", \"\")\n-        if f.endswith(\".json\"):\n-            bases[basekey] = f\n-        else:\n-            lookup = bases.get(basekey, \"\")\n-            if not lookup.endswith(\".json\"):\n-                bases[basekey] = f\n-    return list(bases.values())\n-\n-\n def deserialize(collection, topological=True):\n     \"\"\"\n     Load a collection from file system.\n", "issue": "Deleting a system (or any object) may not persist across cobbler restarts\nProblem is seen in \r\n\r\n```\r\nCobbler 3.1.2\r\n  source: ?, ?\r\n  build time: Sun May 31 02:32:34 2020\r\n```\r\n\r\nThis bug was actually fixed via commit 63d8312bac0bc4a1567d7e511beddfa89f4e1562 in December 2020.  File modules/serializers/file.py functions serialize_item() and serialize_delete() were appending the string \".json\" to the filename twice.    Rather than trying to delete file \"object.json\", serialize_delete() would try to delete \"object.json.json\" which didn't exist, and so the code did nothing.  After a cobbler restart, the \"object.json\" file would be seen and the object re-appeared in cobbler.   In this case the object was created before the last cobbler upgrade, so it has the \"object.json\" filename format, it didn't have the \".json.json\" suffix.  Objects created after the last cobbler upgrade all have the \".json.json\" suffix, there is no \"object.json\" file.\r\n\r\nThe duplication was removed, but this leaves existing sites with a mess in their collections subdirectories.  Depending upon when they created objects relative to when they last upgraded cobbler, they can have files with name \"object.json\", \"object.json.json\", or both files can exist simultaneously.\r\n\r\nSeems like there should be some code added to detect this situation, and rename the \"object.json.json\" files back to their appropriate \"object.json\" names.\r\n\n", "before_files": [{"content": "\"\"\"\nCobbler's file-based object serializer.\nAs of 9/2014, this is Cobbler's default serializer and the most stable one.\nIt uses multiple JSON files in /var/lib/cobbler/collections/distros, profiles, etc\n\nCopyright 2006-2009, Red Hat, Inc and Others\nMichael DeHaan <michael.dehaan AT gmail>\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n02110-1301  USA\n\"\"\"\n\nimport os\nimport glob\nimport simplejson\n\nimport cobbler.api as capi\nfrom cobbler import settings\nfrom cobbler.cexceptions import CX\n\nlibpath = \"/var/lib/cobbler/collections\"\n\n\ndef register():\n    \"\"\"\n    The mandatory Cobbler module registration hook.\n    \"\"\"\n    return \"serializer\"\n\n\ndef what():\n    \"\"\"\n    Module identification function\n    \"\"\"\n    return \"serializer/file\"\n\n\ndef serialize_item(collection, item):\n    \"\"\"\n    Save a collection item to file system\n\n    :param collection: collection\n    :param item: collection item\n    \"\"\"\n\n    if not item.name:\n        raise CX(\"name unset for item!\")\n\n    collection_types = collection.collection_types()\n    filename = os.path.join(libpath, collection_types, item.name + \".json\")\n\n    _dict = item.to_dict()\n\n    if capi.CobblerAPI().settings().serializer_pretty_json:\n        sort_keys = True\n        indent = 4\n    else:\n        sort_keys = False\n        indent = None\n\n    _dict = item.to_dict()\n    with open(filename, \"w+\") as fd:\n        data = simplejson.dumps(_dict, encoding=\"utf-8\", sort_keys=sort_keys, indent=indent)\n        fd.write(data)\n\n\ndef serialize_delete(collection, item):\n    \"\"\"\n    Delete a collection item from file system.\n\n    :param collection: collection\n    :param item: collection item\n    \"\"\"\n\n    collection_types = collection.collection_types()\n    filename = os.path.join(libpath, collection_types, item.name + \".json\")\n\n    if os.path.exists(filename):\n        os.remove(filename)\n\n\ndef serialize(collection):\n    \"\"\"\n    Save a collection to file system\n\n    :param collection: collection\n    \"\"\"\n\n    # do not serialize settings\n    ctype = collection.collection_type()\n    if ctype != \"settings\":\n        for x in collection:\n            serialize_item(collection, x)\n\n\ndef deserialize_raw(collection_types):\n    \"\"\"\n    Loads a collection from the disk.\n\n    :param collection_types: The type of collection to load.\n    :return: The loaded dictionary.\n    \"\"\"\n    if collection_types == \"settings\":\n        return settings.read_settings_file()\n    else:\n        results = []\n\n        path = os.path.join(libpath, collection_types)\n        all_files = glob.glob(\"%s/*.json\" % path)\n\n        for f in all_files:\n            with open(f) as fd:\n                json_data = fd.read()\n                _dict = simplejson.loads(json_data, encoding='utf-8')\n                results.append(_dict)\n        return results\n\n\ndef filter_upgrade_duplicates(file_list):\n    \"\"\"\n    In a set of files, some ending with .json, some not, return the list of files with the .json ones taking priority\n    over the ones that are not.\n\n    :param file_list: The list of files to remove duplicates from.\n    :return: The filtered list of files. Normally this should only return ``.json``-Files.\n    \"\"\"\n    bases = {}\n    for f in file_list:\n        basekey = f.replace(\".json\", \"\")\n        if f.endswith(\".json\"):\n            bases[basekey] = f\n        else:\n            lookup = bases.get(basekey, \"\")\n            if not lookup.endswith(\".json\"):\n                bases[basekey] = f\n    return list(bases.values())\n\n\ndef deserialize(collection, topological=True):\n    \"\"\"\n    Load a collection from file system.\n\n    :param collection: The collection to deserialize.\n    :param topological: If the collection list should be sorted by the\n                        collection dict depth value or not.\n    :type topological: bool\n    \"\"\"\n\n    datastruct = deserialize_raw(collection.collection_types())\n    if topological and type(datastruct) == list:\n        datastruct.sort(key=lambda x: x[\"depth\"])\n    if type(datastruct) == dict:\n        collection.from_dict(datastruct)\n    elif type(datastruct) == list:\n        collection.from_list(datastruct)\n", "path": "cobbler/modules/serializers/file.py"}], "after_files": [{"content": "\"\"\"\nCobbler's file-based object serializer.\nAs of 9/2014, this is Cobbler's default serializer and the most stable one.\nIt uses multiple JSON files in /var/lib/cobbler/collections/distros, profiles, etc\n\nCopyright 2006-2009, Red Hat, Inc and Others\nMichael DeHaan <michael.dehaan AT gmail>\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n02110-1301  USA\n\"\"\"\n\nimport os\nimport glob\nimport simplejson\n\nimport cobbler.api as capi\nfrom cobbler import settings\nfrom cobbler.cexceptions import CX\n\nlibpath = \"/var/lib/cobbler/collections\"\n\n\ndef register() -> str:\n    \"\"\"\n    The mandatory Cobbler module registration hook.\n    \"\"\"\n    return \"serializer\"\n\n\ndef what() -> str:\n    \"\"\"\n    Module identification function\n    \"\"\"\n    return \"serializer/file\"\n\n\ndef __find_double_json_files(filename: str):\n    \"\"\"\n    Finds a file with duplicate .json ending and renames it.\n    :param filename: Filename to be checked\n    :raises FileExistsError: If both JSON files exist\n    \"\"\"\n\n    if not os.path.isfile(filename):\n        if os.path.isfile(filename + \".json\"):\n            os.rename(filename + \".json\", filename)\n    else:\n        if os.path.isfile(filename + \".json\"):\n            raise FileExistsError(\"Both JSON files (%s) exist!\" % filename)\n\n\ndef serialize_item(collection, item):\n    \"\"\"\n    Save a collection item to file system\n\n    :param collection: collection\n    :param item: collection item\n    \"\"\"\n\n    if not item.name:\n        raise CX(\"name unset for item!\")\n\n    collection_types = collection.collection_types()\n    filename = os.path.join(libpath, collection_types, item.name + \".json\")\n    __find_double_json_files(filename)\n\n    _dict = item.to_dict()\n\n    if capi.CobblerAPI().settings().serializer_pretty_json:\n        sort_keys = True\n        indent = 4\n    else:\n        sort_keys = False\n        indent = None\n\n    _dict = item.to_dict()\n    with open(filename, \"w+\") as fd:\n        data = simplejson.dumps(_dict, encoding=\"utf-8\", sort_keys=sort_keys, indent=indent)\n        fd.write(data)\n\n\ndef serialize_delete(collection, item):\n    \"\"\"\n    Delete a collection item from file system.\n\n    :param collection: collection\n    :param item: collection item\n    \"\"\"\n\n    collection_types = collection.collection_types()\n    filename = os.path.join(libpath, collection_types, item.name + \".json\")\n    __find_double_json_files(filename)\n\n    if os.path.exists(filename):\n        os.remove(filename)\n\n\ndef serialize(collection):\n    \"\"\"\n    Save a collection to file system\n\n    :param collection: collection\n    \"\"\"\n\n    # do not serialize settings\n    ctype = collection.collection_type()\n    if ctype != \"settings\":\n        for x in collection:\n            serialize_item(collection, x)\n\n\ndef deserialize_raw(collection_types):\n    \"\"\"\n    Loads a collection from the disk.\n\n    :param collection_types: The type of collection to load.\n    :return: The loaded dictionary.\n    \"\"\"\n    if collection_types == \"settings\":\n        return settings.read_settings_file()\n    else:\n        results = []\n\n        path = os.path.join(libpath, collection_types)\n        all_files = glob.glob(\"%s/*.json\" % path)\n\n        for f in all_files:\n            with open(f) as fd:\n                json_data = fd.read()\n                _dict = simplejson.loads(json_data, encoding='utf-8')\n                results.append(_dict)\n        return results\n\n\ndef deserialize(collection, topological=True):\n    \"\"\"\n    Load a collection from file system.\n\n    :param collection: The collection to deserialize.\n    :param topological: If the collection list should be sorted by the\n                        collection dict depth value or not.\n    :type topological: bool\n    \"\"\"\n\n    datastruct = deserialize_raw(collection.collection_types())\n    if topological and type(datastruct) == list:\n        datastruct.sort(key=lambda x: x[\"depth\"])\n    if type(datastruct) == dict:\n        collection.from_dict(datastruct)\n    elif type(datastruct) == list:\n        collection.from_list(datastruct)\n", "path": "cobbler/modules/serializers/file.py"}]}