1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00

fix(instances): guard mutations and refresh lists after sync

This commit is contained in:
catborise 2026-09-28 14:56:19 +03:00
parent 2cdf5dfca1
commit 73d8fd71fb
7 changed files with 741 additions and 207 deletions

View file

@ -81,9 +81,13 @@
<div class="col-sm-12 text-center">
<p>{% trans "Autostart your instance when host server is power on " %}
{% if instance.autostart == 0 %}
<a class="btn btn-success" href="{% url 'instances:set_autostart' instance.id %}">{% trans "Enable" %}</a>
<form action="{% url 'instances:set_autostart' instance.id %}" method="post" class="d-inline">{% csrf_token %}
<button type="submit" class="btn btn-success">{% trans "Enable" %}</button>
</form>
{% else %}
<a class="btn btn-danger" href="{% url 'instances:unset_autostart' instance.id %}">{% trans "Disable" %}</a>
<form action="{% url 'instances:unset_autostart' instance.id %}" method="post" class="d-inline">{% csrf_token %}
<button type="submit" class="btn btn-danger">{% trans "Disable" %}</button>
</form>
{% endif %}
</p>
</div>
@ -198,9 +202,9 @@
<button class="btn btn-sm btn-success disabled">{% trans "Mount" %}</button>
{% endif %}
{% if instance.status == 5 and allow_admin_or_not_template %}
<a href="{% url 'instances:detach_cdrom' instance.id cd.dev %}" class="btn btn-sm btn-danger float-end" title="{% trans "Detach CD-ROM (remove device)" %}">
<button type="submit" class="btn btn-sm btn-danger float-end" formaction="{% url 'instances:detach_cdrom' instance.id cd.dev %}" formmethod="post" title="{% trans "Detach CD-ROM (remove device)" %}">
{% bs_icon 'dash-circle' %}
</a>
</button>
{% endif %}
</div>

View file

@ -0,0 +1,64 @@
"""Regression tests for the instance creation request."""
from unittest.mock import patch
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from instances.models import Instance
class CreateInstanceViewTests(TestCase):
@patch("instances.views.wvmCreate")
@patch("instances.views.NewVMForm")
def test_successful_create_redirects_after_saving_instance(self, form_class, connection_class):
user = get_user_model().objects.create_superuser(
username="create-test-admin", password="password", email="create@example.com"
)
self.client.force_login(user)
compute = Compute.objects.create(
name="create-test-compute", hostname="localhost", type=4,
login="", password="",
)
conn = connection_class.return_value
conn.get_instances.return_value = []
conn.get_video_models.return_value = ["vga"]
conn.get_cache_modes.return_value = {"default": "Default"}
conn.get_disk_device_types.return_value = ["disk"]
conn.get_disk_bus_types.return_value = ["virtio"]
conn.get_networks.return_value = ["default"]
conn.get_nwfilters.return_value = []
conn.get_network_models.return_value = ["virtio"]
conn.get_storages.return_value = ["default"]
conn.get_dom_capabilities.return_value = {"loader_enums": [], "loaders": []}
conn.get_capabilities.return_value = {}
conn.label_for_firmware_path.return_value = None
conn.find_uefi_path_for_arch.return_value = None
conn.get_volume_path.return_value = "/var/lib/libvirt/images/base.qcow2"
conn.get_volume_format_type.return_value = "qcow2"
form = form_class.return_value
form.is_valid.return_value = True
form.cleaned_data = {
"name": "new-vm", "meta_prealloc": False, "hdd_size": 0,
"template": "", "images": "base.qcow2", "cache_mode": "default",
"firmware": "BIOS", "net_model": "virtio", "memory": 1024,
"vcpu": 1, "vcpu_mode": "host-model", "networks": "default",
"virtio": True, "listener_addr": "0.0.0.0", "nwfilter": "",
"graphics": "vnc", "video": "vga", "console_pass": "",
"mac": "52:54:00:00:00:01", "qemu_ga": False,
"add_cdrom": "sata", "add_input": "default",
}
response = self.client.post(
reverse("instances:create_instance", args=[compute.id, "x86_64", "q35"]),
{"create": "1"},
)
instance = Instance.objects.get(compute=compute, name="new-vm")
self.assertRedirects(
response, reverse("instances:instance", args=[instance.id]),
fetch_redirect_response=False,
)
conn.create_instance.assert_called_once()

46
instances/test_index.py Normal file
View file

@ -0,0 +1,46 @@
"""Tests for the instances list after out-of-band libvirt changes."""
from unittest.mock import PropertyMock, patch
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from instances.models import Instance
class InstancesIndexTests(TestCase):
def test_removed_domain_is_not_rendered_from_prefetch_cache(self):
user = get_user_model().objects.create_superuser(
username="instances-index-admin", password="password", email="index@example.com"
)
self.client.force_login(user)
compute = Compute.objects.create(
name="index-compute", hostname="localhost", type=4,
login="", password="",
)
instance = Instance.objects.create(
compute=compute, name="test4", uuid="cb2a2697-0945-408a-ac6b-c3e07a280851"
)
self.assertEqual(Compute.objects.count(), 1)
def synchronize(_compute):
Instance.objects.filter(pk=instance.pk).delete()
self.assertFalse(Instance.objects.filter(pk=instance.pk).exists())
with patch("instances.views.utils.refr", side_effect=synchronize), patch.object(
Compute, "status", new_callable=PropertyMock, return_value=True
), patch.object(
Compute, "cpu_count", new_callable=PropertyMock, return_value=1
), patch.object(
Compute, "ram_size", new_callable=PropertyMock, return_value=1024
), patch.object(
Compute, "ram_usage", new_callable=PropertyMock, return_value=0
), patch.object(
Compute, "cpu_usage", new_callable=PropertyMock, return_value=0
):
response = self.client.get(reverse("instances:index"))
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, "test4")
self.assertEqual(list(response.context["computes"][0].instance_set.all()), [])

View file

@ -46,10 +46,10 @@ class SetRootPassTestCase(TestCase):
# SHA-512 crypt starts with $6$kgPoiREy$
self.assertTrue(data["passwd"].startswith("$6$kgPoiREy$"))
def test_set_root_pass_get_redirects(self):
def test_set_root_pass_get_not_allowed(self):
response = self.client.get(reverse("instances:rootpasswd", args=[self.instance.id]))
self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 405)
def test_set_root_pass_not_found(self):
response = self.client.get(reverse("instances:rootpasswd", args=[99999]))
response = self.client.post(reverse("instances:rootpasswd", args=[99999]), {"passwd": "secret"})
self.assertEqual(response.status_code, 404)

View file

@ -1,12 +1,15 @@
from unittest.mock import MagicMock, patch
from django.core.exceptions import PermissionDenied
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.http import Http404
from django.test import TestCase
from django.urls import reverse
from accounts.models import UserInstance
from computes.models import Compute
from instances.models import Instance
from instances.views import get_instance
class InstanceSecurityMutationsTestCase(TestCase):
@ -105,3 +108,294 @@ class InstanceSecurityMutationsTestCase(TestCase):
url = f"/api/v1/computes/{other_compute.id}/instances/{self.instance.id}/"
res = self.client.get(url)
self.assertEqual(res.status_code, 404)
def test_disk_media_and_snapshots_reject_get_requests(self):
self.client.force_login(self.change_user)
endpoints = [
reverse("instances:add_new_vol", args=[self.instance.id]),
reverse("instances:add_existing_vol", args=[self.instance.id]),
reverse("instances:edit_volume", args=[self.instance.id]),
reverse("instances:delete_vol", args=[self.instance.id]),
reverse("instances:detach_vol", args=[self.instance.id]),
reverse("instances:add_cdrom", args=[self.instance.id]),
reverse("instances:detach_cdrom", args=[self.instance.id, "hda"]),
reverse("instances:mount_iso", args=[self.instance.id]),
reverse("instances:unmount_iso", args=[self.instance.id]),
reverse("instances:snapshot", args=[self.instance.id]),
reverse("instances:delete_snapshot", args=[self.instance.id]),
reverse("instances:revert_snapshot", args=[self.instance.id]),
]
for url in endpoints:
res = self.client.get(url)
self.assertEqual(res.status_code, 405, f"GET {url} should return 405")
def test_disk_and_media_mutations_require_is_change(self):
# Viewer (global read-only) -> 403
self.client.force_login(self.viewer)
urls_to_test = [
reverse("instances:add_new_vol", args=[self.instance.id]),
reverse("instances:detach_cdrom", args=[self.instance.id, "hda"]),
reverse("instances:mount_iso", args=[self.instance.id]),
reverse("instances:delete_vol", args=[self.instance.id]),
]
for url in urls_to_test:
res = self.client.post(url, {})
self.assertEqual(res.status_code, 403, f"Viewer POST {url} should be 403")
# Owner without is_change (is_change=False, is_delete=False) -> 403
self.client.force_login(self.owner)
for url in urls_to_test:
res = self.client.post(url, {})
self.assertEqual(res.status_code, 403, f"Owner without is_change POST {url} should be 403")
def test_resize_views_reject_get_requests(self):
self.client.force_login(self.change_user)
endpoints = [
reverse("instances:resize_disk", args=[self.instance.id]),
reverse("instances:resizevm_cpu", args=[self.instance.id]),
reverse("instances:resize_memory", args=[self.instance.id]),
]
for url in endpoints:
res = self.client.get(url)
self.assertEqual(res.status_code, 405, f"GET {url} should return 405")
def test_resize_views_require_is_change(self):
self.client.force_login(self.viewer)
endpoints = [
reverse("instances:resize_disk", args=[self.instance.id]),
reverse("instances:resizevm_cpu", args=[self.instance.id]),
reverse("instances:resize_memory", args=[self.instance.id]),
]
for url in endpoints:
res = self.client.post(url, {})
self.assertEqual(res.status_code, 403, f"Viewer POST {url} should return 403")
self.client.force_login(self.owner)
for url in endpoints:
res = self.client.post(url, {})
self.assertEqual(res.status_code, 403, f"Owner without is_change POST {url} should return 403")
def test_get_instance_fail_closed_for_all_users_on_invalid_perm(self):
with self.assertRaises(PermissionDenied):
get_instance(self.superuser, self.instance.pk, perm_type="unknown_perm")
with self.assertRaises(PermissionDenied):
get_instance(self.owner, self.instance.pk, perm_type="unknown_perm")
def test_get_instance_staff_user_access(self):
User = get_user_model()
staff_user = User.objects.create_user(username="sec_staff", password="password", is_staff=True)
# Staff user without ownership or view_instances permission cannot view instance
with self.assertRaises(Http404):
get_instance(staff_user, self.instance.pk, perm_type="view")
# Staff user with explicit view_instances permission can view instance
view_perm = Permission.objects.get(codename="view_instances")
staff_user.user_permissions.add(view_perm)
staff_user = User.objects.get(pk=staff_user.pk)
inst = get_instance(staff_user, self.instance.pk, perm_type="view")
self.assertEqual(inst.pk, self.instance.pk)
# Staff user with view permission but without ownership cannot perform power, change, or delete operations
for perm in ["power", "change", "delete"]:
with self.assertRaises(PermissionDenied):
get_instance(staff_user, self.instance.pk, perm_type=perm)
def test_migrate_endpoint_requires_post(self):
self.client.force_login(self.superuser)
res = self.client.get(reverse("instances:migrate", args=[self.instance.id]))
self.assertEqual(res.status_code, 405)
def test_post_only_mutation_endpoints(self):
self.client.force_login(self.change_user)
# All mutation endpoints reject GET with 405 Method Not Allowed
endpoints_405 = ["change_options", "update_console", "rootpasswd", "add_public_key"]
for ep in endpoints_405:
res = self.client.get(reverse(f"instances:{ep}", args=[self.instance.id]))
self.assertEqual(res.status_code, 405, f"{ep} allowed GET request instead of 405")
def test_opposite_direction_migrations_no_deadlock(self):
import threading
import time
from instances.utils import migrate_instance
c1 = MagicMock()
c1.id = 101
c1.pk = 101
c1.hostname = "127.0.0.1"
c1.type = 1
c1.login = "root"
c1.password = ""
c2 = MagicMock()
c2.id = 102
c2.pk = 102
c2.hostname = "127.0.0.2"
c2.type = 1
c2.login = "root"
c2.password = ""
inst1 = MagicMock()
inst1.compute = c1
inst1.name = "inst1"
inst1.autostart = False
inst1.proxy = MagicMock()
inst1.uuid = "u1"
inst2 = MagicMock()
inst2.compute = c2
inst2.name = "inst2"
inst2.autostart = False
inst2.proxy = MagicMock()
inst2.uuid = "u2"
barrier = threading.Barrier(2)
errors = []
def worker_1_to_2():
try:
barrier.wait(timeout=5)
migrate_instance(c2, inst1, self.superuser)
except Exception as e:
errors.append(("worker_1_to_2", e))
def worker_2_to_1():
try:
barrier.wait(timeout=5)
migrate_instance(c1, inst2, self.superuser)
except Exception as e:
errors.append(("worker_2_to_1", e))
def slow_moveto(*args, **kwargs):
time.sleep(0.05)
with patch("instances.utils.connection_manager.host_is_up", return_value=True), \
patch("instances.utils.wvmInstances") as mock_instances_cls, \
patch("instances.utils.wvmInstance") as mock_instance_cls, \
patch("instances.utils.transaction.atomic"), \
patch("instances.utils.Instance") as mock_instance_model:
mock_instances_cls.return_value.moveto.side_effect = slow_moveto
mock_instance_model.objects.filter.return_value.first.return_value = None
t1 = threading.Thread(target=worker_1_to_2)
t2 = threading.Thread(target=worker_2_to_1)
t1.start()
t2.start()
t1.join(timeout=10)
t2.join(timeout=10)
self.assertFalse(t1.is_alive(), "Worker 1 to 2 timed out / deadlocked")
self.assertFalse(t2.is_alive(), "Worker 2 to 1 timed out / deadlocked")
self.assertEqual(errors, [], f"Unexpected errors during concurrent migrations: {errors}")
def test_resize_disk_empty_selection_warns_cleanly(self):
self.client.force_login(self.change_user)
with patch("instances.models.wvmInstance") as mock_wvm_cls:
mock_wvm_cls.return_value.get_disk_devices.return_value = []
res = self.client.post(reverse("instances:resize_disk", args=[self.instance.id]), {})
self.assertEqual(res.status_code, 302)
self.assertIn(f"/instances/{self.instance.id}/#resize", res.url)
def test_subresource_mutation_allowed_with_is_change(self):
# change_user has is_change=True, is_delete=False
self.client.force_login(self.change_user)
with patch("instances.models.wvmInstance"), patch("instances.views.wvmStorage"):
# delete_vol uses perm_type="change", so it passes get_instance without 403
res = self.client.post(
reverse("instances:delete_vol", args=[self.instance.id]),
{"storage": "default", "dev": "vda", "path": "/path"},
HTTP_REFERER=f"/instances/{self.instance.id}/",
)
self.assertEqual(res.status_code, 302)
def test_destroy_vm_authorization(self):
# 1. User with is_change=True but is_delete=False gets 403 when trying to destroy VM
self.client.force_login(self.change_user)
res = self.client.post(reverse("instances:destroy", args=[self.instance.id]), {})
self.assertEqual(res.status_code, 403)
# 2. Staff user without ownership or is_delete cannot destroy VM
User = get_user_model()
staff_user = User.objects.create_user(username="staff_destroyer", password="password", is_staff=True)
self.client.force_login(staff_user)
res_staff = self.client.post(reverse("instances:destroy", args=[self.instance.id]), {})
self.assertEqual(res_staff.status_code, 404)
# 2b. Staff user with view_instances permission but is_delete=False gets 403
view_perm = Permission.objects.get(codename="view_instances")
staff_user.user_permissions.add(view_perm)
res_staff_view = self.client.post(reverse("instances:destroy", args=[self.instance.id]), {})
self.assertEqual(res_staff_view.status_code, 403)
# 3. Superuser can destroy VM
self.client.force_login(self.superuser)
with patch("instances.models.wvmInstance") as mock_wvm_cls:
mock_wvm_cls.return_value.get_status.return_value = 5 # shutoff
res = self.client.post(reverse("instances:destroy", args=[self.instance.id]), {})
self.assertEqual(res.status_code, 302)
self.assertFalse(Instance.objects.filter(id=self.instance.id).exists())
def test_migration_deduplicate_instances_logic(self):
import importlib
migration_mod = importlib.import_module("instances.migrations.0012_instance_unique_compute_uuid")
dedup_fn = getattr(migration_mod, "deduplicate_instances")
mock_apps = MagicMock()
mock_instance_model = MagicMock()
mock_userinstance_model = MagicMock()
def get_model(app_label, model_name):
if model_name == "Instance":
return mock_instance_model
elif model_name == "UserInstance":
return mock_userinstance_model
return MagicMock()
mock_apps.get_model = get_model
empty_inst = MagicMock(uuid="")
primary_inst = MagicMock(id=1)
dup_inst = MagicMock(id=2)
def mock_inst_filter(*args, **kwargs):
if args:
# Called for empty_uuid_instances query
return [empty_inst]
# Called with compute_id and uuid
ret = MagicMock()
ret.order_by.return_value = [primary_inst, dup_inst]
return ret
mock_instance_model.objects.filter.side_effect = mock_inst_filter
mock_instance_model.objects.exclude.return_value.exclude.return_value.values.return_value.annotate.return_value.filter.return_value = [
{"compute_id": 1, "uuid": "test-uuid-1234"}
]
primary_ui = MagicMock(is_change=False, is_delete=False, is_vnc=False)
dup_ui = MagicMock(user="user1", is_change=True, is_delete=False, is_vnc=True)
mock_userinstance_model.objects.filter.side_effect = lambda **kwargs: (
[dup_ui] if kwargs.get("instance") == dup_inst else
MagicMock(first=lambda: primary_ui)
)
dedup_fn(mock_apps, None)
self.assertTrue(len(empty_inst.uuid) > 0)
empty_inst.save.assert_called_once_with(update_fields=['uuid'])
dup_inst.delete.assert_called_once()
dup_ui.delete.assert_called_once()
self.assertTrue(primary_ui.is_change)
self.assertTrue(primary_ui.is_vnc)
self.assertFalse(primary_ui.is_delete)
primary_ui.save.assert_called_once()
def test_unique_compute_uuid_db_constraint_enforced(self):
from django.db import IntegrityError
with self.assertRaises(IntegrityError):
Instance.objects.create(
compute=self.compute,
name="duplicate_vm",
uuid=self.instance.uuid,
)

View file

@ -81,6 +81,15 @@ class InstancesTestCase(TestCase):
cls.instance: Instance = Instance.objects.filter(compute=cls.compute).first()
except Exception as e:
cls.instance = None
if getattr(cls, "compute", None):
try:
cls.compute.delete()
except Exception:
pass
try:
super().tearDownClass()
except Exception:
pass
raise unittest.SkipTest(f"Live libvirt host not available for instances tests: {e}")
@classmethod

View file

@ -2,6 +2,7 @@ try:
import crypt_r as crypt
except ImportError:
import crypt
import functools
import json
import os
import re
@ -15,12 +16,14 @@ from admin.decorators import superuser_only
from appsettings.models import AppSettings
from appsettings.settings import app_settings
from computes.models import Compute
from computes.utils import libvirt_compute_lock
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponse, JsonResponse
from django.db.models import prefetch_related_objects
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.http import url_has_allowed_host_and_scheme
@ -48,14 +51,10 @@ from .models import Flavor
def index(request):
instances = None
computes = (
Compute.objects.all()
.order_by("name")
.prefetch_related("instance_set")
.prefetch_related("instance_set__userinstance_set")
)
computes = list(Compute.objects.all().order_by("name"))
for compute in computes:
utils.refr(compute)
prefetch_related_objects(computes, "instance_set__userinstance_set")
if request.user.is_superuser or request.user.has_perm("instances.view_instances"):
instances = Instance.objects.all().prefetch_related("userinstance_set")
@ -341,6 +340,10 @@ def get_instance(user, pk, perm_type="view"):
- 'change': superuser or (UserInstance owner and is_change)
- 'delete': superuser or (UserInstance owner and is_delete)
"""
valid_perms = {"view", "power", "change", "delete"}
if perm_type not in valid_perms:
raise PermissionDenied
instance = get_object_or_404(Instance, pk=pk)
user_instances = user.userinstance_set.filter(instance=instance)
has_owner_rel = user_instances.exists()
@ -365,11 +368,32 @@ def get_instance(user, pk, perm_type="view"):
elif perm_type == "delete":
if not (user_inst and user_inst.is_delete):
raise PermissionDenied
else:
raise PermissionDenied
return instance
def serialize_instance_mutation(func):
"""
Decorator that ensures any view mutating an instance's libvirt state
acquires libvirt_compute_lock for the instance's compute.
Works re-entrantly with inner calls such as refresh_instance_database.
"""
@functools.wraps(func)
def wrapper(request, pk, *args, **kwargs):
try:
inst = Instance.objects.only("compute_id").get(pk=pk)
with libvirt_compute_lock(inst.compute):
return func(request, pk, *args, **kwargs)
except Instance.DoesNotExist:
return func(request, pk, *args, **kwargs)
return wrapper
@require_POST
@serialize_instance_mutation
def poweron(request, pk):
instance = get_instance(request.user, pk, perm_type="power")
if instance.is_template:
@ -386,6 +410,7 @@ def poweron(request, pk):
@require_POST
@serialize_instance_mutation
def powercycle(request, pk):
instance = get_instance(request.user, pk, perm_type="power")
instance.proxy.force_shutdown()
@ -399,6 +424,7 @@ def powercycle(request, pk):
@require_POST
@serialize_instance_mutation
def poweroff(request, pk):
instance = get_instance(request.user, pk, perm_type="power")
instance.proxy.shutdown()
@ -413,6 +439,7 @@ def poweroff(request, pk):
@require_POST
@superuser_only
@serialize_instance_mutation
def suspend(request, pk):
instance = get_instance(request.user, pk)
instance.proxy.suspend()
@ -424,6 +451,7 @@ def suspend(request, pk):
@require_POST
@superuser_only
@serialize_instance_mutation
def resume(request, pk):
instance = get_instance(request.user, pk)
instance.proxy.resume()
@ -434,6 +462,7 @@ def resume(request, pk):
@require_POST
@serialize_instance_mutation
def force_off(request, pk):
instance = get_instance(request.user, pk, perm_type="power")
instance.proxy.force_shutdown()
@ -446,37 +475,39 @@ def force_off(request, pk):
def destroy(request, pk):
instance = get_instance(request.user, pk)
try:
userinstance = instance.userinstance_set.get(user=request.user)
except Exception:
userinstance = UserInstance(is_delete=request.user.is_superuser)
if request.method in ["POST", "DELETE"]:
if not userinstance.is_delete:
raise PermissionDenied
if instance.proxy.get_status() == 1:
instance.proxy.force_shutdown()
instance = get_instance(request.user, pk, perm_type="delete")
with libvirt_compute_lock(instance.compute):
if instance.proxy.get_status() == 1:
instance.proxy.force_shutdown()
if request.POST.get("delete_disk", ""):
snapshots = sorted(
instance.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"]
)
for snapshot in snapshots:
instance.proxy.snapshot_delete(snapshot["name"])
instance.proxy.delete_all_disks()
if request.POST.get("delete_disk", ""):
snapshots = sorted(
instance.proxy.get_snapshot(), reverse=True, key=lambda k: k["date"]
)
for snapshot in snapshots:
instance.proxy.snapshot_delete(snapshot["name"])
instance.proxy.delete_all_disks()
if request.POST.get("delete_nvram", ""):
instance.proxy.delete(VIR_DOMAIN_UNDEFINE_NVRAM)
else:
instance.proxy.delete(VIR_DOMAIN_UNDEFINE_KEEP_NVRAM)
if request.POST.get("delete_nvram", ""):
instance.proxy.delete(VIR_DOMAIN_UNDEFINE_NVRAM)
else:
instance.proxy.delete(VIR_DOMAIN_UNDEFINE_KEEP_NVRAM)
instance.delete()
instance.delete()
addlogmsg(
request.user.username, instance.compute.name, instance.name, _("Destroy")
)
return redirect(reverse("instances:index"))
instance = get_instance(request.user, pk, perm_type="delete")
try:
userinstance = instance.userinstance_set.get(user=request.user)
except Exception:
userinstance = UserInstance(
is_delete=request.user.is_superuser
)
return render(
request,
"instances/destroy_instance_form.html",
@ -487,6 +518,7 @@ def destroy(request, pk):
)
@require_POST
@superuser_only
def migrate(request, pk):
instance = get_instance(request.user, pk)
@ -531,6 +563,8 @@ def migrate(request, pk):
)
@require_POST
@serialize_instance_mutation
def set_root_pass(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
@ -558,11 +592,16 @@ def set_root_pass(request, pk):
else:
msg = _("Please shutdown down your instance and then try again")
messages.error(request, msg)
return redirect(reverse("instances:instance", args=[instance.id]) + "#access")
return get_safe_redirect(
request, default=reverse("instances:instance", args=[instance.id]) + "#access"
)
@require_POST
@serialize_instance_mutation
def add_public_key(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
if request.method == "POST":
sshkeyid = request.POST.get("sshkeyid", "")
publickey = get_object_or_404(UserSSHKey, id=sshkeyid, user=request.user)
@ -593,142 +632,138 @@ def add_public_key(request, pk):
else:
msg = _("Please shutdown down your instance and then try again")
messages.error(request, msg)
return redirect(reverse("instances:instance", args=[instance.id]) + "#access")
return get_safe_redirect(
request, default=reverse("instances:instance", args=[instance.id]) + "#access"
)
@require_POST
@serialize_instance_mutation
def resizevm_cpu(request, pk):
instance = get_instance(request.user, pk)
try:
userinstance = instance.userinstance_set.get(user=request.user)
except Exception:
userinstance = UserInstance(is_change=False)
instance = get_instance(request.user, pk, perm_type="change")
vcpu = instance.proxy.get_vcpu()
if request.method == "POST":
if request.user.is_superuser or request.user.is_staff or userinstance.is_change:
new_vcpu = request.POST.get("vcpu", "")
new_cur_vcpu = request.POST.get("cur_vcpu", "")
quota_msg = utils.check_user_quota(
request.user, 0, int(new_vcpu) - vcpu, 0, 0
)
if not request.user.is_superuser and quota_msg:
msg = _(
"User %(quota_msg)s quota reached, cannot resize CPU of '%(instance_name)s'!"
) % {
"quota_msg": quota_msg,
"instance_name": instance.name,
}
messages.error(request, msg)
else:
cur_vcpu = new_cur_vcpu
vcpu = new_vcpu
instance.proxy.resize_cpu(cur_vcpu, vcpu)
msg = _("CPU is resized: %(old)s to %(new)s") % {
"old": cur_vcpu,
"new": vcpu,
}
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
messages.success(request, msg)
new_vcpu = request.POST.get("vcpu", "")
new_cur_vcpu = request.POST.get("cur_vcpu", "")
quota_msg = utils.check_user_quota(
request.user, 0, int(new_vcpu) - vcpu, 0, 0
)
if not request.user.is_superuser and quota_msg:
msg = _(
"User %(quota_msg)s quota reached, cannot resize CPU of '%(instance_name)s'!"
) % {
"quota_msg": quota_msg,
"instance_name": instance.name,
}
messages.error(request, msg)
else:
cur_vcpu = new_cur_vcpu
vcpu = new_vcpu
instance.proxy.resize_cpu(cur_vcpu, vcpu)
msg = _("CPU is resized: %(old)s to %(new)s") % {
"old": cur_vcpu,
"new": vcpu,
}
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
messages.success(request, msg)
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
@require_POST
@serialize_instance_mutation
def resize_memory(request, pk):
instance = get_instance(request.user, pk)
try:
userinstance = instance.userinstance_set.get(user=request.user)
except Exception:
userinstance = UserInstance(is_change=False)
instance = get_instance(request.user, pk, perm_type="change")
memory = instance.proxy.get_memory()
cur_memory = instance.proxy.get_cur_memory()
if request.method == "POST":
if request.user.is_superuser or request.user.is_staff or userinstance.is_change:
new_memory = request.POST.get("memory", "")
new_memory_custom = request.POST.get("memory_custom", "")
if new_memory_custom:
new_memory = new_memory_custom
new_cur_memory = request.POST.get("cur_memory", "")
new_cur_memory_custom = request.POST.get("cur_memory_custom", "")
if new_cur_memory_custom:
new_cur_memory = new_cur_memory_custom
quota_msg = utils.check_user_quota(
request.user, 0, 0, int(new_memory) - memory, 0
)
if not request.user.is_superuser and quota_msg:
msg = _(
"User %(quota_msg)s quota reached, cannot resize memory of '%(instance_name)s'!"
) % {
"quota_msg": quota_msg,
"instance_name": instance.name,
}
messages.error(request, msg)
else:
instance.proxy.resize_mem(new_cur_memory, new_memory)
msg = _(
"Memory is resized: current/max: %(old_cur)s/%(old_max)s to %(new_cur)s/%(new_max)s"
) % {
"old_cur": cur_memory,
"old_max": memory,
"new_cur": new_cur_memory,
"new_max": new_memory,
}
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
messages.success(request, msg)
new_memory = request.POST.get("memory", "")
new_memory_custom = request.POST.get("memory_custom", "")
if new_memory_custom:
new_memory = new_memory_custom
new_cur_memory = request.POST.get("cur_memory", "")
new_cur_memory_custom = request.POST.get("cur_memory_custom", "")
if new_cur_memory_custom:
new_cur_memory = new_cur_memory_custom
quota_msg = utils.check_user_quota(
request.user, 0, 0, int(new_memory) - memory, 0
)
if not request.user.is_superuser and quota_msg:
msg = _(
"User %(quota_msg)s quota reached, cannot resize memory of '%(instance_name)s'!"
) % {
"quota_msg": quota_msg,
"instance_name": instance.name,
}
messages.error(request, msg)
else:
instance.proxy.resize_mem(new_cur_memory, new_memory)
msg = _(
"Memory is resized: current/max: %(old_cur)s/%(old_max)s to %(new_cur)s/%(new_max)s"
) % {
"old_cur": cur_memory,
"old_max": memory,
"new_cur": new_cur_memory,
"new_max": new_memory,
}
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
messages.success(request, msg)
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
@require_POST
@serialize_instance_mutation
def resize_disk(request, pk):
instance = get_instance(request.user, pk)
try:
userinstance = instance.userinstance_set.get(user=request.user)
except Exception:
userinstance = UserInstance(is_change=False)
instance = get_instance(request.user, pk, perm_type="change")
disks = instance.proxy.get_disk_devices()
if request.method == "POST":
if request.user.is_superuser or request.user.is_staff or userinstance.is_change:
disks_new = list()
for disk in disks:
input_disk_size = (
int(request.POST.get("disk_size_" + disk["dev"], "0")) * 1073741824
)
if input_disk_size > disk["size"] + (64 << 20):
disk["size_new"] = input_disk_size
disks_new.append(disk)
disk_sum = sum([disk["size"] >> 30 for disk in disks_new])
disk_new_sum = sum([disk["size_new"] >> 30 for disk in disks_new])
quota_msg = utils.check_user_quota(
request.user, 0, 0, 0, disk_new_sum - disk_sum
)
if not request.user.is_superuser and quota_msg:
msg = _(
"User %(quota_msg)s quota reached, cannot resize disks of '%(instance_name)s'!"
) % {
"quota_msg": quota_msg,
"instance_name": instance.name,
}
messages.error(request, msg)
else:
instance.proxy.resize_disk(disks_new)
msg = _("Disk is resized: %(dev)s") % {"dev": disk["dev"]}
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
messages.success(request, msg)
disks_new = list()
for disk in disks:
input_disk_size = (
int(request.POST.get("disk_size_" + disk["dev"], "0")) * 1073741824
)
if input_disk_size > disk["size"] + (64 << 20):
disk["size_new"] = input_disk_size
disks_new.append(disk)
if not disks_new:
messages.warning(request, _("No disks were selected or valid for resizing."))
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
disk_sum = sum([disk["size"] >> 30 for disk in disks_new])
disk_new_sum = sum([disk["size_new"] >> 30 for disk in disks_new])
quota_msg = utils.check_user_quota(
request.user, 0, 0, 0, disk_new_sum - disk_sum
)
if not request.user.is_superuser and quota_msg:
msg = _(
"User %(quota_msg)s quota reached, cannot resize disks of '%(instance_name)s'!"
) % {
"quota_msg": quota_msg,
"instance_name": instance.name,
}
messages.error(request, msg)
else:
instance.proxy.resize_disk(disks_new)
devs_str = ", ".join([d["dev"] for d in disks_new])
msg = _("Disk is resized: %(dev)s") % {"dev": devs_str}
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
messages.success(request, msg)
return redirect(reverse("instances:instance", args=[instance.id]) + "#resize")
@require_POST
@serialize_instance_mutation
def add_new_vol(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -795,8 +830,10 @@ def add_new_vol(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def add_existing_vol(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -845,8 +882,10 @@ def add_existing_vol(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def edit_volume(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -919,8 +958,10 @@ def edit_volume(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def delete_vol(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -945,8 +986,10 @@ def delete_vol(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def detach_vol(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -961,8 +1004,10 @@ def detach_vol(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def add_cdrom(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -983,8 +1028,10 @@ def add_cdrom(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def detach_cdrom(request, pk, dev):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -998,8 +1045,10 @@ def detach_cdrom(request, pk, dev):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def unmount_iso(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -1013,8 +1062,10 @@ def unmount_iso(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def mount_iso(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -1028,8 +1079,10 @@ def mount_iso(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#disks")
@require_POST
@serialize_instance_mutation
def snapshot(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -1045,8 +1098,10 @@ def snapshot(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
@require_POST
@serialize_instance_mutation
def delete_snapshot(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -1060,8 +1115,10 @@ def delete_snapshot(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
@require_POST
@serialize_instance_mutation
def revert_snapshot(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -1078,8 +1135,10 @@ def revert_snapshot(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
@require_POST
@serialize_instance_mutation
def create_external_snapshot(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -1108,8 +1167,10 @@ def get_external_snapshots(request, pk):
return external_snapshots
@require_POST
@serialize_instance_mutation
def revert_external_snapshot(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
)
@ -1129,8 +1190,10 @@ def revert_external_snapshot(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
@require_POST
@serialize_instance_mutation
def delete_external_snapshot(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
instance_state = True if instance.proxy.get_status() == 5 else False
allow_admin_or_not_template = (
request.user.is_superuser or request.user.is_staff or not instance.is_template
@ -1153,7 +1216,9 @@ def delete_external_snapshot(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#managesnapshot")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_vcpu(request, pk):
instance = get_instance(request.user, pk)
id = request.POST.get("id", "")
@ -1167,7 +1232,9 @@ def set_vcpu(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#resize")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_vcpu_hotplug(request, pk):
instance = get_instance(request.user, pk)
status = True if request.POST.get("vcpu_hotplug", "False") == "True" else False
@ -1177,7 +1244,9 @@ def set_vcpu_hotplug(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#resize")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_autostart(request, pk):
instance = get_instance(request.user, pk)
instance.proxy.set_autostart(1)
@ -1186,7 +1255,9 @@ def set_autostart(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#boot_opt")
@require_POST
@superuser_only
@serialize_instance_mutation
def unset_autostart(request, pk):
instance = get_instance(request.user, pk)
instance.proxy.set_autostart(0)
@ -1195,7 +1266,9 @@ def unset_autostart(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#boot_opt")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_bootmenu(request, pk):
instance = get_instance(request.user, pk)
instance.proxy.set_bootmenu(1)
@ -1204,7 +1277,9 @@ def set_bootmenu(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#boot_opt")
@require_POST
@superuser_only
@serialize_instance_mutation
def unset_bootmenu(request, pk):
instance = get_instance(request.user, pk)
instance.proxy.set_bootmenu(0)
@ -1213,7 +1288,9 @@ def unset_bootmenu(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#boot_opt")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_bootorder(request, pk):
instance = get_instance(request.user, pk)
bootorder = request.POST.get("bootorder", "")
@ -1239,7 +1316,9 @@ def set_bootorder(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#boot_opt")
@require_POST
@superuser_only
@serialize_instance_mutation
def change_xml(request, pk):
instance = get_instance(request.user, pk)
new_xml = request.POST.get("inst_xml", "")
@ -1250,7 +1329,9 @@ def change_xml(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#xmledit")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_guest_agent(request, pk):
instance = get_instance(request.user, pk)
status = request.POST.get("guest_agent")
@ -1264,7 +1345,9 @@ def set_guest_agent(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#options")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_video_model(request, pk):
instance = get_instance(request.user, pk)
video_model = request.POST.get("video_model", "vga")
@ -1274,7 +1357,9 @@ def set_video_model(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#options")
@require_POST
@superuser_only
@serialize_instance_mutation
def change_network(request, pk):
instance = get_instance(request.user, pk)
@ -1307,7 +1392,9 @@ def change_network(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#network")
@require_POST
@superuser_only
@serialize_instance_mutation
def add_network(request, pk):
instance = get_instance(request.user, pk)
@ -1332,7 +1419,9 @@ def add_network(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#network")
@require_POST
@superuser_only
@serialize_instance_mutation
def delete_network(request, pk):
instance = get_instance(request.user, pk)
mac_address = request.POST.get("delete_network", "")
@ -1343,7 +1432,9 @@ def delete_network(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#network")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_link_state(request, pk):
instance = get_instance(request.user, pk)
@ -1356,7 +1447,9 @@ def set_link_state(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#network")
@require_POST
@superuser_only
@serialize_instance_mutation
def set_qos(request, pk):
instance = get_instance(request.user, pk)
@ -1387,7 +1480,9 @@ def set_qos(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#network")
@require_POST
@superuser_only
@serialize_instance_mutation
def unset_qos(request, pk):
instance = get_instance(request.user, pk)
qos_dir = request.POST.get("qos_direction", "")
@ -1410,6 +1505,7 @@ def unset_qos(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#network")
@require_POST
@superuser_only
def add_owner(request, pk):
instance = get_instance(request.user, pk)
@ -1433,6 +1529,7 @@ def add_owner(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#users")
@require_POST
@superuser_only
def del_owner(request, pk):
instance = get_instance(request.user, pk)
@ -1444,6 +1541,7 @@ def del_owner(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#users")
@require_POST
@permission_required("instances.clone_instances", raise_exception=True)
def clone(request, pk):
instance = get_instance(request.user, pk)
@ -1500,15 +1598,22 @@ def clone(request, pk):
}
messages.error(request, msg)
else:
new_instance = Instance(compute=instance.compute, name=clone_data["name"])
try:
new_uuid = instance.proxy.clone_instance(clone_data)
new_instance.uuid = new_uuid
new_instance.save()
user_instance = UserInstance(
instance_id=new_instance.id, user_id=request.user.id, is_delete=True
)
user_instance.save()
with libvirt_compute_lock(instance.compute):
new_uuid = instance.proxy.clone_instance(clone_data)
new_instance = Instance.objects.get_or_create(
compute=instance.compute,
uuid=new_uuid,
defaults={"name": clone_data["name"]},
)[0]
if new_instance.name != clone_data["name"]:
new_instance.name = clone_data["name"]
new_instance.save(update_fields=["name"])
UserInstance.objects.get_or_create(
instance_id=new_instance.id,
user_id=request.user.id,
defaults={"is_delete": True, "is_change": True, "is_vnc": True},
)
msg = _("Create a clone of '%(instance_name)s'") % {
"instance_name": instance.name
}
@ -1530,14 +1635,16 @@ def clone(request, pk):
return redirect(request.META.get("HTTP_REFERER") + "#clone")
@require_POST
@serialize_instance_mutation
def update_console(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
try:
userinstance = instance.userinstance_set.get(user=request.user)
except Exception:
userinstance = UserInstance(is_vnc=False)
if request.user.is_superuser or request.user.is_staff or userinstance.is_vnc:
if request.user.is_superuser or userinstance.is_vnc:
form = ConsoleForm(request.POST or None)
if form.is_valid():
if (
@ -1589,18 +1696,22 @@ def update_console(request, pk):
request.user.username, instance.compute.name, instance.name, msg
)
return redirect(request.META.get("HTTP_REFERER") + "#vncsettings")
return get_safe_redirect(
request, default=reverse("instances:instance", args=[instance.id]) + "#vncsettings"
)
@require_POST
@serialize_instance_mutation
def change_options(request, pk):
instance = get_instance(request.user, pk)
instance = get_instance(request.user, pk, perm_type="change")
try:
userinstance = instance.userinstance_set.get(user=request.user)
except Exception:
userinstance = UserInstance(is_change=False)
if request.user.is_superuser or request.user.is_staff or userinstance.is_change:
instance.is_template = request.POST.get("is_template", False)
if request.user.is_superuser or userinstance.is_change:
instance.is_template = bool(request.POST.get("is_template", False))
instance.save()
options = {}
@ -1611,7 +1722,9 @@ def change_options(request, pk):
msg = _("Edit options")
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
return redirect(request.META.get("HTTP_REFERER") + "#options")
return get_safe_redirect(
request, default=reverse("instances:instance", args=[instance.id]) + "#options"
)
def getvvfile(request, pk):
@ -1695,9 +1808,10 @@ def create_instance_select_type(request, compute_id):
error_msg = _("A virtual machine with this name already exists")
messages.error(request, error_msg)
else:
conn._defineXML(xml)
utils.refr(compute)
instance = compute.instance_set.get(name=name)
with libvirt_compute_lock(compute):
conn._defineXML(xml)
utils.refresh_instance_database(compute)
instance = compute.instance_set.get(name=name)
return redirect(reverse("instances:instance", args=[instance.id]))
return render(request, "create_instance_w1.html", locals())
@ -1919,33 +2033,36 @@ def create_instance(request, compute_id, arch, machine):
uuid = util.randomUUID()
try:
conn.create_instance(
name=data["name"],
memory=data["memory"],
vcpu=data["vcpu"],
vcpu_mode=data["vcpu_mode"],
uuid=uuid,
arch=arch,
machine=machine,
firmware=firmware,
volumes=volume_list,
networks=data["networks"],
virtio=data["virtio"],
listener_addr=data["listener_addr"],
nwfilter=data["nwfilter"],
net_model=data["net_model"],
graphics=data["graphics"],
video=data["video"],
console_pass=data["console_pass"],
mac=data["mac"],
qemu_ga=data["qemu_ga"],
add_cdrom=data["add_cdrom"],
add_input=data["add_input"],
)
create_instance = Instance(
compute_id=compute_id, name=data["name"], uuid=uuid
)
create_instance.save()
with libvirt_compute_lock(compute):
conn.create_instance(
name=data["name"],
memory=data["memory"],
vcpu=data["vcpu"],
vcpu_mode=data["vcpu_mode"],
uuid=uuid,
arch=arch,
machine=machine,
firmware=firmware,
volumes=volume_list,
networks=data["networks"],
virtio=data["virtio"],
listener_addr=data["listener_addr"],
nwfilter=data["nwfilter"],
net_model=data["net_model"],
graphics=data["graphics"],
video=data["video"],
console_pass=data["console_pass"],
mac=data["mac"],
qemu_ga=data["qemu_ga"],
add_cdrom=data["add_cdrom"],
add_input=data["add_input"],
)
create_instance = Instance.objects.get_or_create(
compute_id=compute_id, uuid=uuid, defaults={"name": data["name"]}
)[0]
if create_instance.name != data["name"]:
create_instance.name = data["name"]
create_instance.save(update_fields=["name"])
msg = _("Instance is created")
messages.success(request, msg)
addlogmsg(