mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
fix(instances): a clone stays on the host of the VM it was made from
With "VM Clone Auto Migrate" on, a new clone was migrated to a random compute, whatever storage, CPU or network that host had, and a failed migration showed as a failed clone although the clone existed. The setting and its code are removed; migration appsettings.0015 deletes the setting. A clone can be migrated afterwards like any VM. The README's update notes list the migration changes: the migration address, storage pools for the disk check, and this setting.
This commit is contained in:
parent
5c22fc8436
commit
1016f745c2
5 changed files with 51 additions and 7 deletions
|
|
@ -415,6 +415,8 @@ The template is not copied over an existing settings file during an upgrade.
|
|||
|
||||
Failed logins are limited with `django-axes` (installed from `conf/requirements.txt`). Copy the `axes` entries of `webvirtcloud/settings.py.template` into your `webvirtcloud/settings.py`: `"axes"` in `INSTALLED_APPS`, `"axes.middleware.AxesMiddleware"` at the end of `MIDDLEWARE`, `"axes.backends.AxesStandaloneBackend"` first in `AUTHENTICATION_BACKENDS`, and the `AXES_*` settings; then run `migrate`. The number of failed logins and the lock time are on the Settings page; unlock a user with `python3 manage.py axes_reset_username <username>`. Behind a proxy other than the bundled nginx, the proxy must set `X-Real-IP` to the client address and its address must be listed in `LOGIN_TRUSTED_PROXIES`.
|
||||
|
||||
Migration: where the hosts cannot resolve each other's host names, set each compute's **Migration address** (the compute's edit page) to the IP the other hosts reach it on for migrations. Define shared storage as a storage pool on every host: a VM migrates only when its disk, backing and ISO files and block devices are volumes of the destination's pools (network disks are not checked). The "VM Clone Auto Migrate" setting is removed (migration `appsettings.0015`); a clone stays on the host of the VM it was made from.
|
||||
|
||||
Migration `accounts.0007` transfers the first-login requirement from any existing
|
||||
`data/admin_password` file to the user record. Run migrations as a user who can read
|
||||
that file. Thereafter, deleting or losing access to the file does not bypass the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
from django.db import migrations
|
||||
|
||||
|
||||
def remove_auto_migrate(apps, schema_editor):
|
||||
"""A clone stays on the host of the VM it was made from: the setting sent
|
||||
it to a random host, whatever storage, CPU or network that host had."""
|
||||
AppSettings = apps.get_model("appsettings", "AppSettings")
|
||||
AppSettings.objects.using(schema_editor.connection.alias).filter(key="CLONE_INSTANCE_AUTO_MIGRATE").delete()
|
||||
|
||||
|
||||
def restore_auto_migrate(apps, schema_editor):
|
||||
AppSettings = apps.get_model("appsettings", "AppSettings")
|
||||
AppSettings.objects.using(schema_editor.connection.alias).get_or_create(
|
||||
key="CLONE_INSTANCE_AUTO_MIGRATE",
|
||||
defaults={
|
||||
"name": "VM Clone Auto Migrate",
|
||||
"value": "False",
|
||||
"choices": "True,False",
|
||||
"description": "Auto migrate instance after clone",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("appsettings", "0014_login_lockout_settings"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_auto_migrate, restore_auto_migrate),
|
||||
]
|
||||
|
|
@ -43,3 +43,8 @@ class AppSettingsTestCase(TestCase):
|
|||
self.client.force_login(regular_user)
|
||||
response = self.client.get(reverse("appsettings"))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_clones_are_not_migrated_automatically(self):
|
||||
# a clone stays on the host of the VM it was made from; the setting
|
||||
# that sent it to a random host is removed by migration 0015
|
||||
self.assertFalse(AppSettings.objects.filter(key="CLONE_INSTANCE_AUTO_MIGRATE").exists())
|
||||
|
|
|
|||
|
|
@ -107,6 +107,19 @@ class ClonePermissionTestCase(TestCase):
|
|||
self.assertEqual(res.status_code, 403)
|
||||
proxy.clone_instance.assert_not_called()
|
||||
|
||||
def test_a_clone_stays_on_the_host_of_its_vm(self):
|
||||
# the removed "VM Clone Auto Migrate" setting sent it to a random host
|
||||
other = Compute.objects.create(name="other-host", hostname="198.51.100.20", login="", password="", type=4)
|
||||
with self.mocked_libvirt() as proxy, \
|
||||
patch("instances.views.utils.migrate_instance") as migrate, \
|
||||
patch.object(app_settings, "CLONE_INSTANCE_AUTO_MIGRATE", "True", create=True), \
|
||||
patch("instances.views.Compute.objects.order_by") as random_host:
|
||||
random_host.return_value.first.return_value = other
|
||||
self._clone(self.owner_change, self._valid_post())
|
||||
proxy.clone_instance.assert_called_once()
|
||||
migrate.assert_not_called()
|
||||
self.assertEqual(Instance.objects.get(name="clone-vm").compute, self.instance.compute)
|
||||
|
||||
def test_viewer_can_clone_a_template(self):
|
||||
self.instance.is_template = True
|
||||
self.instance.save(update_fields=["is_template"])
|
||||
|
|
|
|||
|
|
@ -1854,13 +1854,6 @@ def clone(request, pk):
|
|||
request.user.username, instance.compute.name, new_instance.name, msg
|
||||
)
|
||||
|
||||
if app_settings.CLONE_INSTANCE_AUTO_MIGRATE == "True":
|
||||
new_compute = Compute.objects.order_by("?").first()
|
||||
if new_compute != instance.compute:
|
||||
utils.migrate_instance(
|
||||
new_compute, new_instance, request.user, offline=True
|
||||
)
|
||||
|
||||
return redirect(reverse("instances:instance", args=[new_instance.id]))
|
||||
except (libvirtError, ValueError, OSError) as e: # OSError includes a lock timeout
|
||||
messages.error(request, error_text(request, e))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue