Repository: cobbler/cobbler

Issue:
replicate fails with Invalid URL for remote boot kernel
### Describe the bug

When adding or importing a distro (example is from imported esxi distro), looks like remote_boot_initrd and remote_boot_kernel are set to None.

```
# python3.6 -m json.tool /var/lib/cobbler/collections/distros/esxi67-x86_64.json
{
    "ctime": 1609220911.160294,
    "depth": 0,
    "mtime": 1609220911.160294,
    "source_repos": [],
    "tree_build_time": 0,
    "uid": "5bebae44987b45928035b83f2b8f73fb",
    "arch": "x86_64",
    "autoinstall_meta": {
        "tree": "http://@@http_server@@/cblr/links/esxi67-x86_64"
    },
    "boot_files": {
        "$local_img_path/*.*": "/var/www/cobbler/distro_mirror/esxi67-x86_64/*.*"
    },
    "boot_loader": "grub",
    "breed": "vmware",
    "comment": "",
    "fetchable_files": {},
    "initrd": "/var/www/cobbler/distro_mirror/esxi67-x86_64/imgpayld.tgz",
    "kernel": "/var/www/cobbler/distro_mirror/esxi67-x86_64/mboot.c32",
    "remote_boot_initrd": null,
    "remote_boot_kernel": null,
    "kernel_options": {},
    "kernel_options_post": {},
    "mgmt_classes": [],
    "name": "esxi67-x86_64",
    "os_version": "esxi67",
    "owners": [
        "admin"
    ],
    "redhat_management_key": "",
    "template_files": {
        "/etc/cobbler/boot_loader_conf/bootcfg_esxi67.template": "$local_img_path/cobbler-boot.cfg"
    }
}
```
Not sure what is going on there, as in cobbler/items/distro.py both defaults are set to None but initialized to empty string later at init.

And problem starts when I try to replicate that distro from another server:
```
# cobbler replicate --master 192.168.122.12 --distros=* --profiles=*
task started: 2021-01-05_113948_replicate
task started (id=Replicate, time=Tue Jan  5 11:39:48 2021)
...
adding distro esxi67-x86_64
Exception occurred: <class 'cobbler.cexceptions.CX'>
Exception value: 'Invalid URL for remote boot kernel: ~'
Exception Info:
  File "/usr/local/lib/python3.6/site-packages/cobbler/actions/replicate.py", line 114, in add_objects_not_on_local
    if not self.api.add_item(obj_type, newobj, logger=self.logger):

  File "/usr/local/lib/python3.6/site-packages/cobbler/api.py", line 739, in add_item
    self.get_items(what).add(ref, check_for_duplicate_names=check_for_duplicate_names, save=save, logger=logger)

  File "/usr/local/lib/python3.6/site-packages/cobbler/cobbler_collections/collection.py", line 349, in add
    ref.check_if_valid()

  File "/usr/local/lib/python3.6/site-packages/cobbler/items/distro.py", line 131, in check_if_valid
    raise CX("Invalid URL for remote boot kernel: %s" % self.remote_boot_kernel)
...
```

If I set default to empty string instead of None in cobbler/items/distro.py replication succeeds.
```
<     ["remote_boot_initrd", "", 0, "Remote Boot Initrd", True, "URL the bootloader directly retrieves and boots from", 0, "str"],
<     ["remote_boot_kernel", "", 0, "Remote Boot Kernel", True, "URL the bootloader directly retrieves and boots from", 0, "str"],
---
>     ["remote_boot_initrd", None, 0, "Remote Boot Initrd", True, "URL the bootloader directly retrieves and boots from", 0, "str"],
>     ["remote_boot_kernel", None, 0, "Remote Boot Kernel", True, "URL the bootloader directly retrieves and boots from", 0, "str"],
```


### To Reproduce

1. Add or import a distro
2. Replicate distro from another server

### Expected behavior

replication should work.

### Version

````
Cobbler 3.2.0
  source: b'0d3523f1', b'Mon Dec 21 16:59:42 2020 +0100'
  build time: Tue Dec 29 05:33:11 2020
````

### Screenshots

<!--- If applicable, add screenshots to help explain your problem. -->

### Additional context

<!--- Add any other context about the problem here. -->



Discussion:
Yeah this is the problem with XMLRPC. `None` is there an illegal type and thus this fails per design. This is a problem in general. Many places have wrapper methods but we need to fix that. Thanks for confirming!

Resolving patch:
diff --git a/cobbler/actions/replicate.py b/cobbler/actions/replicate.py
--- a/cobbler/actions/replicate.py
+++ b/cobbler/actions/replicate.py
@@ -108,7 +108,7 @@ def add_objects_not_on_local(self, obj_type):
             if not rdata["uid"] in locals:
                 creator = getattr(self.api, "new_%s" % obj_type)
                 newobj = creator()
-                newobj.from_dict(rdata)
+                newobj.from_dict(utils.revert_strip_none(rdata))
                 try:
                     self.logger.info("adding %s %s" % (obj_type, rdata["name"]))
                     if not self.api.add_item(obj_type, newobj, logger=self.logger):
@@ -141,7 +141,7 @@ def replace_objects_newer_on_remote(self, obj_type):
                         self.api.remove_item(obj_type, ldata["name"], recursive=True, logger=self.logger)
                     creator = getattr(self.api, "new_%s" % obj_type)
                     newobj = creator()
-                    newobj.from_dict(rdata)
+                    newobj.from_dict(utils.revert_strip_none(rdata))
                     try:
                         self.logger.info("updating %s %s" % (obj_type, rdata["name"]))
                         if not self.api.add_item(obj_type, newobj):
@@ -216,7 +216,7 @@ def replicate_data(self):
         else:
             self.logger.info("*NOT* Rsyncing Data")
 
-        self.logger.info("Removing Objects Not Stored On Local")
+        self.logger.info("Adding Objects Not Stored On Local")
         for what in OBJ_TYPES:
             self.add_objects_not_on_local(what)
 


Test patch: