From 400ba1614f29b38f2bd4f29fa132c7f8f2fd21e9 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 30 Oct 2026 13:32:28 +0300 Subject: [PATCH] feat(instances): a VM's clone form in device cards, with fixes to what it clones Settings > Clone was a horizontal form with unlabelled checkboxes. The owner chose this layout from three rendered options: the clone's name on top, a "Network" card with a new MAC address per NIC, a "Storage" card with a new volume name per disk, then the optional title and description and a footer with Clone. Codex reviewed cloning end to end; these are fixed: - Every NIC of the clone gets a new random MAC when the form opens. Only the first did, so a VM with two NICs gave its clone the second NIC's MAC. Guess MAC is offered for the first NIC only: a name's DHCP host entry holds one address. - A VM without a NIC can be cloned: the first NIC's MAC was required. - The disk name script uses the persistent disks, as the form does; a disk added pending the next start kept the source's volume name. - A running or paused VM is not cloned while a copied disk changes at the next start: the backup copies the running disk, so the clone would get the old contents under the new definition. - A derived volume name that does not start with the source's name now starts with the clone's name and the disk's device, so a second clone or a second disk does not take the same name. - Guess for the clone name says when the DHCP file has no free name; it stopped with a script error. - The qcow2 metadata preallocation box shows only for a shut-off VM: a live clone's backup ignores it. Other users no longer get hidden MAC and disk inputs or the scripts that filled them: the server derives those values and ignored the posted ones. --- instances/jobs.py | 5 +- instances/templates/instance.html | 26 ++- .../templates/instances/settings_tab.html | 154 +++++++++--------- instances/tests/test_clone_permission.py | 25 ++- instances/tests/test_pending_edits.py | 17 ++ instances/utils.py | 11 +- vrtManager/instance.py | 5 + vrtManager/tests/test_live_clone.py | 17 +- 8 files changed, 164 insertions(+), 96 deletions(-) diff --git a/instances/jobs.py b/instances/jobs.py index bf467460..2cc43e3c 100644 --- a/instances/jobs.py +++ b/instances/jobs.py @@ -141,14 +141,13 @@ def clone_data(user, instance, fields): if app_settings.CLONE_INSTANCE_AUTO_NAME == "True" and not data["name"]: auto_vname = utils.get_clone_free_names()[0] data["name"] = auto_vname - data["clone-net-mac-0"] = utils.get_dhcp_mac_address(auto_vname) for disk in instance.config_disks: data[f"disk-{disk['dev']}"] = utils.get_clone_disk_name(disk, instance.name, auto_vname) if not user.is_superuser: for disk in instance.config_disks: data[f"disk-{disk['dev']}"] = utils.get_clone_disk_name(disk, instance.name, data["name"]) - for num in range(max(1, len(instance.config_networks))): + for num in range(len(instance.config_networks)): key = f"clone-net-mac-{num}" if not data.get(key): data[key] = (utils.get_dhcp_mac_address(data["name"]) if num == 0 else "") or utils.get_random_mac_address() @@ -173,7 +172,7 @@ def clone_data(user, instance, fields): if not re.match(r"^[a-zA-Z0-9-]+$", data["name"]): raise util.OperationError( _("Instance name '%(clone_name)s' contains invalid characters!") % {"clone_name": data["name"]}) - if "clone-net-mac-0" not in data or invalid_macs: + if invalid_macs: raise util.OperationError(_("Instance MAC '%(clone_mac)s' invalid format!") % {"clone_mac": ", ".join(invalid_macs)}) if invalid_disks: raise util.OperationError( diff --git a/instances/templates/instance.html b/instances/templates/instance.html index 9a37acb6..955df7eb 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -152,6 +152,12 @@ @@ -203,19 +209,11 @@ {% endif %} {% if request.user.is_superuser %} $(document).ready(function () { - random_mac('clone-net-mac-0'); + // every NIC of the clone a new address: one the VM has would clash with it + $('input[name^="clone-net-mac-"]').each(function () { random_mac(this.name); }); random_mac('add-net-mac'); update_clone_disk_name($('#clone_name').val()); }); -{% else %} - $('#select_clone_name').on('change', function () { - update_clone_disk_name($(this).val()); - guess_mac_address('#select_clone_name', 0); - }); - $(document).ready(function () { - update_clone_disk_name($('#select_clone_name').val()); - guess_mac_address('#select_clone_name', 0); - }); {% endif %}