mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
The Resize tab's CPU, Memory and Disk panes were horizontal forms with bold host figures. The owner chose the ledger layout from three rendered options: each section with its heading, the VM's figures and the host's on the left, its fields on the right, and one line on what applies now and what at the next start. Codex reviewed resizing end to end; these are fixed: - A vCPU turned on or off while the VM runs is kept at its next start: libvirt was asked without flags, which changes the running VM only. - A paused VM offers to turn vCPUs on only: turning one off needs the guest, which does not run. - A smaller disk size is refused, and so is the rest of the form, before anything grows; it was skipped without a word. - vCPUs and memory below 1 are refused. - The memory fields are numbers filled with the VM's values: the "Custom value" box, its script and the preset list are gone; an empty custom field silently used the preset. - Units and words: MiB, not MB; a disk's field is its new size, with its size now above it; the messages say what the VM gets.
1153 lines
40 KiB
Python
1153 lines
40 KiB
Python
import os
|
|
import re
|
|
import unittest
|
|
|
|
from accounts.models import UserAttributes, UserInstance, UserSSHKey
|
|
from appsettings.models import AppSettings
|
|
from computes.models import Compute
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.models import Permission
|
|
from django.http.response import Http404
|
|
from django.shortcuts import reverse
|
|
from django.test import RequestFactory, TestCase
|
|
from vrtManager.create import wvmCreate
|
|
from vrtManager.util import randomUUID
|
|
|
|
from operations.models import Operation
|
|
|
|
from . import livetest
|
|
from .test_create_instance import run
|
|
from instances.models import Flavor, Instance
|
|
from instances.utils import refr
|
|
|
|
TEST_COMPUTE_HOST = os.environ.get("TEST_LIBVIRT_HOST", "localhost")
|
|
TEST_COMPUTE_LOGIN = os.environ.get("TEST_LIBVIRT_LOGIN", "")
|
|
TEST_COMPUTE_PASSWORD = os.environ.get("TEST_LIBVIRT_PASSWORD", "")
|
|
TEST_COMPUTE_TYPE = int(os.environ.get("TEST_LIBVIRT_TYPE", 4))
|
|
|
|
|
|
class InstancesTestCase(TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if not livetest.enabled():
|
|
raise unittest.SkipTest("Set TEST_LIBVIRT_HOST to run the live libvirt tests")
|
|
super().setUpClass()
|
|
|
|
# Add users for testing purposes
|
|
User = get_user_model()
|
|
cls.admin_user = User.objects.filter(is_superuser=True).first() or User.objects.get(username="admin")
|
|
cls.test_user = User.objects.create(username="test-user")
|
|
UserAttributes.objects.create(
|
|
user=cls.test_user,
|
|
max_instances=1,
|
|
max_cpus=1,
|
|
max_memory=128,
|
|
max_disk_size=1,
|
|
)
|
|
permission = Permission.objects.get(codename="clone_instances")
|
|
cls.test_user.user_permissions.add(permission)
|
|
|
|
# Add compute
|
|
cls.compute = Compute.objects.create(
|
|
name="instances-test-compute",
|
|
hostname=TEST_COMPUTE_HOST,
|
|
login=TEST_COMPUTE_LOGIN,
|
|
password=TEST_COMPUTE_PASSWORD,
|
|
details="test",
|
|
type=TEST_COMPUTE_TYPE,
|
|
)
|
|
|
|
try:
|
|
cls.connection = wvmCreate(
|
|
cls.compute.hostname,
|
|
cls.compute.login,
|
|
cls.compute.password,
|
|
cls.compute.type,
|
|
)
|
|
|
|
# Remove leftovers of an interrupted run, then record what
|
|
# else is on the host so tearDownClass can prove it is untouched.
|
|
livetest.cleanup(cls.connection.wvm)
|
|
livetest.ensure_pool(cls.connection.wvm)
|
|
cls.inventory = livetest.inventory(cls.connection.wvm)
|
|
|
|
# Add disks for testing
|
|
cls.connection.create_volume(
|
|
livetest.POOL,
|
|
"wvc-test-volume",
|
|
1,
|
|
"qcow2",
|
|
False,
|
|
0,
|
|
0,
|
|
)
|
|
# XML for testing vm
|
|
with open("conf/test-vm.xml", "r") as f:
|
|
cls.xml = f.read()
|
|
|
|
# Create testing vm from XML
|
|
cls.connection._defineXML(cls.xml)
|
|
refr(cls.compute)
|
|
# The compute may hold other VMs; never pick one of them.
|
|
cls.instance: Instance = Instance.objects.get(compute=cls.compute, name="wvc-test-vm")
|
|
except Exception as e:
|
|
cls.instance = None
|
|
if getattr(cls, "connection", None):
|
|
try:
|
|
livetest.remove_pool(cls.connection.wvm)
|
|
except Exception:
|
|
pass
|
|
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
|
|
def tearDownClass(cls):
|
|
conn = cls.connection.wvm
|
|
try:
|
|
livetest.remove_pool(conn)
|
|
finally:
|
|
cls.compute.delete()
|
|
super().tearDownClass()
|
|
domains, volumes = livetest.inventory(conn)
|
|
if (domains, volumes) != cls.inventory:
|
|
raise AssertionError(
|
|
"Live tests changed objects outside the test namespace: "
|
|
f"domains {cls.inventory[0] ^ domains}, volumes {cls.inventory[1] ^ volumes}"
|
|
)
|
|
|
|
def setUp(self):
|
|
self.client.login(request=RequestFactory().get("/"), username="admin", password="admin")
|
|
self.rsa_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC6OOdbfv27QVnSC6sKxGaHb6YFc+3gxCkyVR3cTSXE/n5BEGf8aOgBpepULWa1RZfxYHY14PlKULDygdXSdrrR2kNSwoKz/Oo4d+3EE92L7ocl1+djZbptzgWgtw1OseLwbFik+iKlIdqPsH+IUQvX7yV545ZQtAP8Qj1R+uCqkw== test@test"
|
|
|
|
def test_index(self):
|
|
response = self.client.get(reverse("instances:index"))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
self.client.force_login(self.test_user)
|
|
response = self.client.get(reverse("instances:index"))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_create_select_type(self):
|
|
response = self.client.get(
|
|
reverse("instances:create_instance_select_type", args=[self.compute.id])
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_instance_page(self):
|
|
response = self.client.get(
|
|
reverse("instances:instance", args=[self.instance.id])
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
self.client.force_login(self.test_user)
|
|
response = self.client.get(
|
|
reverse("instances:instance", args=[self.instance.id])
|
|
)
|
|
self.assertRaises(Http404)
|
|
|
|
# def test_create_volume(self):
|
|
# response = self.client.post(
|
|
# reverse('create_volume', args=[self.compute.id, 'default']),
|
|
# {
|
|
# 'name': 'test',
|
|
# 'format': 'qcow2',
|
|
# 'size': '1',
|
|
# 'meta_prealloc': False,
|
|
# },
|
|
# )
|
|
# self.assertRedirects(response, reverse('storage', args=[self.compute.id, 'default']))
|
|
|
|
def test_create_destroy_instance(self):
|
|
# Create
|
|
response = self.client.get(
|
|
reverse(
|
|
"instances:create_instance", args=[self.compute.id, "x86_64", "q35"]
|
|
)
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
response = self.client.post(
|
|
reverse(
|
|
"instances:create_instance", args=[self.compute.id, "x86_64", "q35"]
|
|
),
|
|
{
|
|
"name": "wvc-test-new",
|
|
"firmware": "BIOS",
|
|
"vcpu": 1,
|
|
"vcpu_mode": "host-model",
|
|
"memory": 128,
|
|
"device0": "disk",
|
|
"bus0": "virtio",
|
|
"images": "wvc-test-volume.qcow2",
|
|
"storage-control": livetest.POOL,
|
|
"image-control": "wvc-test.qcow2",
|
|
"networks": "default",
|
|
"network-control": "default",
|
|
"cache_mode": "directsync",
|
|
"nwfilter": "",
|
|
"graphics": "vnc",
|
|
"video": "vga",
|
|
"listener_addr": "0.0.0.0",
|
|
"qemu_ga": False,
|
|
"virtio": True,
|
|
"add_cdrom": "None",
|
|
"add_input": "None",
|
|
"create": True,
|
|
"submission_key": "key-create-destroy",
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
run(Operation.objects.get(kind="instance.create"))
|
|
|
|
instance_qs: Instance = Instance.objects.filter(name="wvc-test-new")
|
|
self.assertEqual(len(instance_qs), 1)
|
|
|
|
instance = instance_qs[0]
|
|
|
|
# Destroy
|
|
response = self.client.get(reverse("instances:destroy", args=[instance.id]))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:destroy", args=[instance.id]),
|
|
{}, # do not destroy disk image
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertRedirects(response, reverse("instances:index"))
|
|
|
|
def test_create_from_xml(self):
|
|
uuid = randomUUID()
|
|
xml = self.xml.replace("wvc-test-vm", "wvc-test-vm-xml")
|
|
xml = re.sub(r"\s?<uuid>.*?</uuid>", f"<uuid>{uuid}</uuid>", xml)
|
|
response = self.client.post(
|
|
reverse("instances:create_instance_select_type", args=[self.compute.id]),
|
|
{
|
|
"create_xml": True,
|
|
"dom_xml": xml,
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
xml_instance_qs: Instance = Instance.objects.filter(name="wvc-test-vm-xml")
|
|
self.assertEqual(len(xml_instance_qs), 1)
|
|
|
|
xml_instance = xml_instance_qs[0]
|
|
|
|
# destroy started instance to maximize coverage
|
|
xml_instance.proxy.start()
|
|
|
|
response = self.client.post(
|
|
reverse("instances:destroy", args=[xml_instance.id]),
|
|
{}, # do not delete disk image
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertRedirects(response, reverse("instances:index"))
|
|
|
|
def test_resize_cpu(self):
|
|
self.assertEqual(self.instance.vcpu, 1)
|
|
self.assertEqual(self.instance.cur_vcpu, 1)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:resizevm_cpu", args=[self.instance.id]),
|
|
{
|
|
"vcpu": 4,
|
|
"cur_vcpu": 2,
|
|
},
|
|
)
|
|
self.assertRedirects(
|
|
response, reverse("instances:instance", args=[self.instance.id]) + "#resize"
|
|
)
|
|
|
|
# reset cached properties
|
|
del self.instance.vcpu
|
|
del self.instance.cur_vcpu
|
|
|
|
self.assertEqual(self.instance.vcpu, 4)
|
|
self.assertEqual(self.instance.cur_vcpu, 2)
|
|
|
|
def test_resize_cpu_with_quota(self):
|
|
# test for non admin user with quotas
|
|
vcpu = self.instance.vcpu
|
|
cur_vcpu = self.instance.cur_vcpu
|
|
|
|
UserInstance.objects.create(
|
|
user=self.test_user, instance=self.instance, is_change=True
|
|
)
|
|
|
|
self.client.force_login(self.test_user)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:resizevm_cpu", args=[self.instance.id]),
|
|
{
|
|
"vcpu": 4,
|
|
"cur_vcpu": 2,
|
|
},
|
|
)
|
|
self.assertRedirects(
|
|
response, reverse("instances:instance", args=[self.instance.id]) + "#resize"
|
|
)
|
|
|
|
del self.instance.vcpu
|
|
del self.instance.cur_vcpu
|
|
|
|
# no changes as user reached quota
|
|
self.assertEqual(self.instance.vcpu, vcpu)
|
|
self.assertEqual(self.instance.cur_vcpu, cur_vcpu)
|
|
|
|
def test_resize_memory(self):
|
|
self.assertEqual(self.instance.memory, 128)
|
|
self.assertEqual(self.instance.cur_memory, 128)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:resize_memory", args=[self.instance.id]),
|
|
{"memory": 512, "cur_memory": 256},
|
|
)
|
|
self.assertRedirects(
|
|
response, reverse("instances:instance", args=[self.instance.id]) + "#resize"
|
|
)
|
|
|
|
del self.instance.memory
|
|
del self.instance.cur_memory
|
|
self.assertEqual(self.instance.memory, 512)
|
|
self.assertEqual(self.instance.cur_memory, 256)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:resize_memory", args=[self.instance.id]),
|
|
{"memory": 500, "cur_memory": 200},
|
|
)
|
|
self.assertRedirects(
|
|
response, reverse("instances:instance", args=[self.instance.id]) + "#resize"
|
|
)
|
|
|
|
del self.instance.memory
|
|
del self.instance.cur_memory
|
|
|
|
self.assertEqual(self.instance.memory, 500)
|
|
self.assertEqual(self.instance.cur_memory, 200)
|
|
|
|
def test_resize_memory_with_quota(self):
|
|
# test for non admin user with quotas
|
|
memory = self.instance.memory
|
|
cur_memory = self.instance.cur_memory
|
|
|
|
UserInstance.objects.create(
|
|
user=self.test_user, instance=self.instance, is_change=True
|
|
)
|
|
|
|
self.client.force_login(self.test_user)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:resize_memory", args=[self.instance.id]),
|
|
{"memory": 512, "cur_memory": 256},
|
|
)
|
|
self.assertRedirects(
|
|
response, reverse("instances:instance", args=[self.instance.id]) + "#resize"
|
|
)
|
|
|
|
del self.instance.memory
|
|
del self.instance.cur_memory
|
|
|
|
# no changes as user reached quota
|
|
self.assertEqual(self.instance.memory, memory)
|
|
self.assertEqual(self.instance.cur_memory, cur_memory)
|
|
|
|
def test_resize_disk(self):
|
|
self.assertEqual(self.instance.disks[0]["size"], 1024**3)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:resize_disk", args=[self.instance.id]),
|
|
{
|
|
"disk_size_vda": 2,
|
|
},
|
|
)
|
|
self.assertRedirects(
|
|
response, reverse("instances:instance", args=[self.instance.id]) + "#resize"
|
|
)
|
|
|
|
del self.instance.disks
|
|
self.assertEqual(self.instance.disks[0]["size"], 2 * 1024**3)
|
|
|
|
def test_resize_disk_with_quota(self):
|
|
# test for non admin user with quotas
|
|
disk_size = self.instance.disks[0]["size"]
|
|
UserInstance.objects.create(
|
|
user=self.test_user, instance=self.instance, is_change=True
|
|
)
|
|
|
|
self.client.force_login(self.test_user)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:resize_disk", args=[self.instance.id]),
|
|
{
|
|
"disk_size_vda": 3,
|
|
},
|
|
)
|
|
self.assertRedirects(
|
|
response, reverse("instances:instance", args=[self.instance.id]) + "#resize"
|
|
)
|
|
|
|
# no changes as user reached quota
|
|
del self.instance.disks
|
|
self.assertEqual(self.instance.disks[0]["size"], disk_size)
|
|
|
|
def test_add_delete_new_volume(self):
|
|
self.assertEqual(len(self.instance.disks), 1)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:add_new_vol", args=[self.instance.id]),
|
|
{
|
|
"storage": livetest.POOL,
|
|
"name": "wvc-test-volume-2",
|
|
"size": 1,
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.disks
|
|
self.assertEqual(len(self.instance.disks), 2)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:delete_vol", args=[self.instance.id]),
|
|
{
|
|
"storage": livetest.POOL,
|
|
"dev": "vdb",
|
|
"name": "wvc-test-volume-2.qcow2",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.disks
|
|
self.assertEqual(len(self.instance.disks), 1)
|
|
|
|
def test_detach_attach_volume(self):
|
|
# detach volume
|
|
response = self.client.post(
|
|
reverse("instances:detach_vol", args=[self.instance.id]),
|
|
{
|
|
"dev": "vda",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.disks
|
|
self.assertEqual(len(self.instance.disks), 0)
|
|
|
|
# reattach volume
|
|
response = self.client.post(
|
|
reverse("instances:add_existing_vol", args=[self.instance.id]),
|
|
{
|
|
"selected_storage": livetest.POOL,
|
|
"vols": "wvc-test-volume.qcow2",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.disks
|
|
self.assertEqual(len(self.instance.disks), 1)
|
|
|
|
def test_edit_volume(self):
|
|
response = self.client.post(
|
|
reverse("instances:edit_volume", args=[self.instance.id]),
|
|
{
|
|
"vol_path": f"{livetest.POOL_PATH}/wvc-test-volume.qcow2",
|
|
# 'vol_shareable': False,
|
|
# 'vol_readonly': False,
|
|
"vol_bus": "virtio",
|
|
"vol_bus_old": "virtio",
|
|
"vol_format": "qcow2",
|
|
"dev": "vda",
|
|
"edit_volume": True,
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_attach_detach_cdrom(self):
|
|
self.assertEqual(len(self.instance.media), 1)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:add_cdrom", args=[self.instance.id]),
|
|
{
|
|
"bus": "sata",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.media
|
|
self.assertEqual(len(self.instance.media), 2)
|
|
|
|
# create dummy iso
|
|
# with tempfile.NamedTemporaryFile() as f:
|
|
# f.write(b'\x00' * 1024**2)
|
|
|
|
# response = self.client.post(
|
|
# reverse('storage', args=[instance.compute.id, 'default']),
|
|
# {
|
|
# 'iso_upload': True,
|
|
# 'file': f
|
|
# },
|
|
# )
|
|
|
|
# detach CD-ROM drive
|
|
response = self.client.post(
|
|
reverse("instances:detach_cdrom", args=[self.instance.id, "sda"]),
|
|
{},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.media
|
|
self.assertEqual(len(self.instance.media), 1)
|
|
|
|
def test_snapshots(self):
|
|
self.assertEqual(len(self.instance.snapshots), 0)
|
|
|
|
self.instance.proxy.create_snapshot("test-snapshot")
|
|
|
|
for cached in ("snapshots", "internal_snapshots", "external_snapshots"):
|
|
self.instance.__dict__.pop(cached, None)
|
|
self.assertEqual(len(self.instance.snapshots), 1)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:delete_snapshot", args=[self.instance.id]),
|
|
{
|
|
"name": "test-snapshot",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
for cached in ("snapshots", "internal_snapshots", "external_snapshots"):
|
|
self.instance.__dict__.pop(cached, None)
|
|
self.assertEqual(len(self.instance.snapshots), 0)
|
|
|
|
def test_autostart(self):
|
|
self.assertEqual(self.instance.autostart, 0)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:set_autostart", args=[self.instance.id]),
|
|
{},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.autostart
|
|
self.assertEqual(self.instance.autostart, 1)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:unset_autostart", args=[self.instance.id]),
|
|
{},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.autostart
|
|
self.assertEqual(self.instance.autostart, 0)
|
|
|
|
def test_bootmenu(self):
|
|
self.assertEqual(self.instance.bootmenu, True)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:unset_bootmenu", args=[self.instance.id]),
|
|
{},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.bootmenu
|
|
self.assertEqual(self.instance.bootmenu, False)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:set_bootmenu", args=[self.instance.id]),
|
|
{},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.bootmenu
|
|
self.assertEqual(self.instance.bootmenu, True)
|
|
|
|
def test_guest_agent(self):
|
|
self.assertEqual(self.instance.guest_agent, False)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:set_guest_agent", args=[self.instance.id]),
|
|
{"guest_agent": True},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.guest_agent
|
|
self.assertEqual(self.instance.guest_agent, True)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:set_guest_agent", args=[self.instance.id]),
|
|
{"guest_agent": False},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.guest_agent
|
|
self.assertEqual(self.instance.guest_agent, False)
|
|
|
|
def test_video_model(self):
|
|
self.assertEqual(self.instance.video_model, "vga")
|
|
|
|
response = self.client.post(
|
|
reverse("instances:set_video_model", args=[self.instance.id]),
|
|
{"video_model": "virtio"},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.video_model
|
|
self.assertEqual(self.instance.video_model, "virtio")
|
|
|
|
def test_owner(self):
|
|
self.assertEqual(UserInstance.objects.count(), 0)
|
|
response = self.client.post(
|
|
reverse("instances:add_owner", args=[self.instance.id]),
|
|
{"user_id": self.admin_user.id},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(UserInstance.objects.count(), 1)
|
|
|
|
user_instance: UserInstance = UserInstance.objects.get(id=1)
|
|
self.assertEqual(user_instance.instance_id, self.instance.id)
|
|
self.assertEqual(user_instance.user_id, self.admin_user.id)
|
|
|
|
# test when no multiple owners allowed
|
|
setting = AppSettings.objects.get(key="ALLOW_INSTANCE_MULTIPLE_OWNER")
|
|
setting.value = "False"
|
|
setting.save()
|
|
|
|
response = self.client.post(
|
|
reverse("instances:add_owner", args=[self.instance.id]),
|
|
{"user_id": self.test_user.id},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(UserInstance.objects.count(), 1)
|
|
|
|
response = self.client.post(reverse("accounts:user_instance_delete", args=[user_instance.id]))
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(UserInstance.objects.count(), 0)
|
|
|
|
def test_clone(self):
|
|
instance_count = Instance.objects.count()
|
|
response = self.client.post(
|
|
reverse("instances:clone", args=[self.instance.id]),
|
|
{
|
|
"name": "wvc-test-vm-clone",
|
|
"clone-net-mac-0": "de:ad:be:ef:de:ad",
|
|
"disk-vda": "wvc-test-clone.img",
|
|
"clone-title": "",
|
|
"clone-description": "",
|
|
"clone": "",
|
|
"submission_key": "key-clone",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
run(Operation.objects.get(kind="instance.clone"))
|
|
self.assertEqual(Instance.objects.count(), instance_count + 1)
|
|
|
|
clone_qs = Instance.objects.filter(name="wvc-test-vm-clone")
|
|
self.assertEqual(len(clone_qs), 1)
|
|
clone = clone_qs[0]
|
|
|
|
self.assertEqual(clone.proxy.get_net_devices()[0]["mac"], "de:ad:be:ef:de:ad")
|
|
|
|
clone.proxy.create_snapshot("test")
|
|
|
|
response = self.client.post(
|
|
reverse("instances:destroy", args=[clone.id]),
|
|
{"delete_disk": True, "delete_nvram": True},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertRedirects(response, reverse("instances:index"))
|
|
self.assertEqual(Instance.objects.count(), instance_count)
|
|
|
|
def test_clone_with_quota(self):
|
|
# test for non admin user with quotas
|
|
instance_count = Instance.objects.count()
|
|
|
|
# Cloning needs change permission on the source VM
|
|
UserInstance.objects.create(user=self.test_user, instance=self.instance, is_change=True)
|
|
|
|
self.client.force_login(self.test_user)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:clone", args=[self.instance.id]),
|
|
{
|
|
"name": "wvc-test-vm-clone",
|
|
"clone-net-mac-0": "de:ad:be:ef:de:ad",
|
|
"disk-vda": "wvc-test-clone.img",
|
|
"clone-title": "",
|
|
"clone-description": "",
|
|
"clone": "",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
# no new instances created as user reached quota
|
|
self.assertEqual(Instance.objects.count(), instance_count)
|
|
|
|
def test_clone_errors(self):
|
|
instance_count = Instance.objects.count()
|
|
|
|
# duplicate name
|
|
response = self.client.post(
|
|
reverse("instances:clone", args=[self.instance.id]),
|
|
{
|
|
"name": "wvc-test-vm",
|
|
"clone-net-mac-0": "de:ad:be:ef:de:ad",
|
|
"disk-vda": "wvc-test.img",
|
|
"clone-title": "",
|
|
"clone-description": "",
|
|
"clone": "",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Instance.objects.count(), instance_count)
|
|
|
|
# wrong name
|
|
response = self.client.post(
|
|
reverse("instances:clone", args=[self.instance.id]),
|
|
{
|
|
"name": "!@#$",
|
|
"clone-net-mac-0": "de:ad:be:ef:de:ad",
|
|
"disk-vda": "!@#$.img",
|
|
"clone-title": "",
|
|
"clone-description": "",
|
|
"clone": "",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Instance.objects.count(), instance_count)
|
|
|
|
# wrong mac
|
|
response = self.client.post(
|
|
reverse("instances:clone", args=[self.instance.id]),
|
|
{
|
|
"name": "wvc-test-vm-clone",
|
|
"clone-net-mac-0": "gh:ad:be:ef:de:ad",
|
|
"disk-vda": "wvc-test-clone.img",
|
|
"clone-title": "",
|
|
"clone-description": "",
|
|
"clone": "",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Instance.objects.count(), instance_count)
|
|
|
|
def test_console(self):
|
|
response = self.client.post(
|
|
reverse("instances:update_console", args=[self.instance.id]),
|
|
{"listen_on": "0.0.0.0", "password_action": "keep", "keymap": "auto"},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_status(self):
|
|
response = self.client.get(reverse("instances:status", args=[self.instance.id]))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_stats(self):
|
|
response = self.client.get(reverse("instances:stats", args=[self.instance.id]))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_guess_mac_address(self):
|
|
response = self.client.get(
|
|
reverse("instances:guess_mac_address", args=[self.instance.name])
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_random_mac_address(self):
|
|
response = self.client.get(reverse("instances:random_mac_address"))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_guess_clone_name(self):
|
|
response = self.client.get(reverse("instances:guess_clone_name"))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_sshkeys(self):
|
|
UserSSHKey.objects.create(
|
|
keyname="keyname", keypublic=self.rsa_key, user=self.test_user
|
|
)
|
|
UserInstance.objects.create(user=self.test_user, instance=self.instance)
|
|
|
|
response = self.client.get(
|
|
reverse("instances:sshkeys", args=[self.instance.id])
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
response = self.client.get(
|
|
reverse("instances:sshkeys", args=[self.instance.id]) + "?plain=true"
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_start_template(self):
|
|
# starting templates must fail
|
|
self.assertEqual(self.instance.status, 5)
|
|
|
|
self.instance.is_template = True
|
|
self.instance.save()
|
|
|
|
response = self.client.post(
|
|
reverse("instances:poweron", args=[self.instance.id]),
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.status
|
|
self.assertEqual(self.instance.status, 5)
|
|
|
|
self.instance.is_template = False
|
|
self.instance.save()
|
|
|
|
def test_power(self):
|
|
# poweron
|
|
self.assertEqual(self.instance.status, 5)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:poweron", args=[self.instance.id]),
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.status
|
|
|
|
self.assertEqual(self.instance.status, 1)
|
|
|
|
# suspend
|
|
response = self.client.post(
|
|
reverse("instances:suspend", args=[self.instance.id]),
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.status
|
|
self.assertEqual(self.instance.status, 3)
|
|
|
|
# resume
|
|
response = self.client.post(
|
|
reverse("instances:resume", args=[self.instance.id]),
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.status
|
|
self.assertEqual(self.instance.status, 1)
|
|
|
|
# poweroff
|
|
response = self.client.post(
|
|
reverse("instances:poweroff", args=[self.instance.id]),
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
# as no OS is installed ACPI won't work
|
|
del self.instance.status
|
|
self.assertEqual(self.instance.status, 1)
|
|
|
|
# powercycle
|
|
response = self.client.post(
|
|
reverse("instances:powercycle", args=[self.instance.id]),
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.status
|
|
self.assertEqual(self.instance.status, 1)
|
|
|
|
# force_off
|
|
response = self.client.post(
|
|
reverse("instances:force_off", args=[self.instance.id]),
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.status
|
|
self.assertEqual(self.instance.status, 5)
|
|
|
|
def test_vv_file(self):
|
|
response = self.client.get(
|
|
reverse("instances:getvvfile", args=[self.instance.id])
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_vcpu_hotplug(self):
|
|
response = self.client.post(
|
|
reverse("instances:set_vcpu_hotplug", args=[self.instance.id]),
|
|
{"vcpu_hotplug": "True"},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_change_network(self):
|
|
self.assertEqual(self.instance.networks[0]["mac"], "52:54:00:a2:3c:e7")
|
|
response = self.client.post(
|
|
reverse("instances:change_network", args=[self.instance.id]),
|
|
{
|
|
"net-old-mac-0": "52:54:00:a2:3c:e7",
|
|
"net-mac-0": "52:54:00:a2:3c:e8",
|
|
"net-source-0": "net:default",
|
|
"net-nwfilter-0": "",
|
|
"net-model-0": "virtio",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.networks
|
|
self.assertEqual(len(self.instance.networks), 1)
|
|
self.assertEqual(self.instance.networks[0]["mac"], "52:54:00:a2:3c:e8")
|
|
|
|
# The second NIC's form changes only the second NIC
|
|
self.client.post(
|
|
reverse("instances:add_network", args=[self.instance.id]),
|
|
{"add-net-mac": "52:54:00:a2:3c:e9", "add-net-network": "net:default"},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.client.post(
|
|
reverse("instances:change_network", args=[self.instance.id]),
|
|
{
|
|
"net-old-mac-1": "52:54:00:a2:3c:e9",
|
|
"net-mac-1": "52:54:00:a2:3c:ea",
|
|
"net-source-1": "net:default",
|
|
"net-nwfilter-1": "",
|
|
"net-model-1": "e1000",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
del self.instance.networks
|
|
self.assertEqual([n["mac"] for n in self.instance.networks], ["52:54:00:a2:3c:e8", "52:54:00:a2:3c:ea"])
|
|
self.assertEqual(self.instance.networks[1]["model"], "e1000")
|
|
|
|
# Restore the shared test VM
|
|
self.client.post(
|
|
reverse("instances:delete_network", args=[self.instance.id]),
|
|
{"delete_network": "52:54:00:a2:3c:ea"},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.client.post(
|
|
reverse("instances:change_network", args=[self.instance.id]),
|
|
{
|
|
"net-old-mac-0": "52:54:00:a2:3c:e8",
|
|
"net-mac-0": "52:54:00:a2:3c:e7",
|
|
"net-source-0": "net:default",
|
|
"net-model-0": "virtio",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
del self.instance.networks
|
|
self.assertEqual([n["mac"] for n in self.instance.networks], ["52:54:00:a2:3c:e7"])
|
|
|
|
def test_add_delete_network(self):
|
|
self.assertEqual(len(self.instance.networks), 1)
|
|
net_mac = self.instance.networks[0]["mac"]
|
|
response = self.client.post(
|
|
reverse("instances:add_network", args=[self.instance.id]),
|
|
{
|
|
"add-net-mac": "52:54:00:a2:3c:e9",
|
|
"add-net-network": "net:default",
|
|
"add_net-nwfilter": "",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.networks
|
|
self.assertEqual(len(self.instance.networks), 2)
|
|
self.assertEqual(self.instance.networks[1]["mac"], "52:54:00:a2:3c:e9")
|
|
|
|
response = self.client.post(
|
|
reverse("instances:delete_network", args=[self.instance.id]),
|
|
{
|
|
"delete_network": "52:54:00:a2:3c:e9",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.networks
|
|
self.assertEqual(len(self.instance.networks), 1)
|
|
self.assertEqual(self.instance.networks[0]["mac"], net_mac)
|
|
|
|
def test_set_link_state(self):
|
|
self.assertEqual(self.instance.networks[0]["state"], "up")
|
|
response = self.client.post(
|
|
reverse("instances:set_link_state", args=[self.instance.id]),
|
|
{
|
|
"mac": self.instance.networks[0]["mac"],
|
|
"set_link_state": "up",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.networks
|
|
self.assertEqual(self.instance.networks[0]["state"], "down")
|
|
|
|
def test_set_unset_qos(self):
|
|
self.assertFalse(self.instance.config_networks[0]["inbound"])
|
|
net_mac = self.instance.networks[0]["mac"]
|
|
response = self.client.post(
|
|
reverse("instances:set_qos", args=[self.instance.id]),
|
|
{
|
|
"net-mac-0": net_mac,
|
|
"qos_direction": "inbound",
|
|
"qos_average": 1,
|
|
"qos_peak": 1,
|
|
"qos_burst": 1,
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.config_networks
|
|
self.assertEqual(self.instance.config_networks[0]["inbound"]["average"], "1")
|
|
|
|
response = self.client.post(
|
|
reverse("instances:unset_qos", args=[self.instance.id]),
|
|
{
|
|
"net-mac": net_mac,
|
|
"qos_direction": "inbound",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
del self.instance.config_networks
|
|
self.assertFalse(self.instance.config_networks[0]["inbound"])
|
|
|
|
# test on running instance
|
|
# self.instance.proxy.start()
|
|
# response = self.client.post(
|
|
# reverse('instances:set_qos', args=[self.instance.id]),
|
|
# {
|
|
# 'net-mac-0': net_mac,
|
|
# 'qos_direction': 'inbound',
|
|
# 'qos_average': 1,
|
|
# 'qos_peak': 1,
|
|
# 'qos_burst': 1,
|
|
# },
|
|
# HTTP_REFERER=reverse('index'),
|
|
# )
|
|
# self.assertEqual(response.status_code, 302)
|
|
# self.instance.proxy.force_shutdown()
|
|
|
|
def test_change_options(self):
|
|
self.assertEqual(self.instance.title, "")
|
|
self.assertEqual(self.instance.description, "")
|
|
|
|
response = self.client.post(
|
|
reverse("instances:change_options", args=[self.instance.id]),
|
|
{
|
|
"title": "test-vm-title",
|
|
"description": "test-vm description",
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
del self.instance.title
|
|
del self.instance.description
|
|
|
|
self.assertEqual(self.instance.title, "test-vm-title")
|
|
self.assertEqual(self.instance.description, "test-vm description")
|
|
|
|
def test_flavors(self):
|
|
response = self.client.get(reverse("instances:flavor_create"))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:flavor_create"),
|
|
{
|
|
"label": "test_flavor",
|
|
"memory": 256,
|
|
"vcpu": 1,
|
|
"disk": 10,
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
id = Flavor.objects.last().id
|
|
|
|
response = self.client.get(reverse("instances:flavor_update", args=[id]))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:flavor_update", args=[id]),
|
|
{
|
|
"label": "test_flavor_",
|
|
"memory": 256,
|
|
"vcpu": 1,
|
|
"disk": 10,
|
|
},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
response = self.client.get(reverse("instances:flavor_delete", args=[id]))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
response = self.client.post(
|
|
reverse("instances:flavor_delete", args=[id]),
|
|
{},
|
|
HTTP_REFERER=reverse("index"),
|
|
)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
# def donot_test_instance(self):
|
|
# compute: Compute = Compute.objects.get(pk=1)
|
|
# user: User = User.objects.get(pk=1)
|
|
|
|
# # delete started instance with disks
|
|
# self.instance.proxy.start()
|
|
# del self.instance.status
|
|
# self.assertEqual(self.instance.status, 1)
|
|
|
|
# # create volume
|
|
# response = self.client.post(
|
|
# reverse('create_volume', args=[compute.id, 'default']),
|
|
# {
|
|
# 'name': 'test3',
|
|
# 'format': 'qcow2',
|
|
# 'size': '1',
|
|
# 'meta_prealloc': False,
|
|
# },
|
|
# )
|
|
# self.assertRedirects(response, reverse('storage', args=[compute.id, 'default']))
|
|
|
|
# # delete volume
|
|
# response = self.client.post(
|
|
# reverse('instances:delete_vol', args=[instance.id]),
|
|
# {
|
|
# 'storage': 'default',
|
|
# 'dev': 'vdb',
|
|
# 'name': 'test3.qcow2',
|
|
# },
|
|
# HTTP_REFERER=reverse('index'),
|
|
# )
|
|
# self.assertEqual(response.status_code, 302)
|
|
|
|
# , list(response.context['messages'])[0]
|