mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
fix(instances): a paused VM's memory is resized as a running VM's
Only a running VM counted as active, so a paused one (or one blocked, PM-suspended or shutting down) had its maximum and current memory written for the next boot and nothing applied live. Every active VM now has its current memory changed live and in its definition and keeps its maximum; a paused guest takes the new size once resumed. The quota check follows the same rule. Checked on a real guest: paused, shrunk, resumed, then grown back.
This commit is contained in:
parent
7b5c2cd057
commit
251ddbbec1
7 changed files with 55 additions and 9 deletions
|
|
@ -539,6 +539,7 @@ A rollback leaves the clone on a detached HEAD at the restored commit; the updat
|
|||
- An ISO uploads to an SSH compute about 3.5 times faster (SFTP writes no longer wait for each 32 KiB); a failed write still fails the upload.
|
||||
- The VM page shows each NIC's own QoS and each CD-ROM's own pool (they showed the previous device's), and a NIC without a source or model no longer breaks it.
|
||||
- A volume of an LVM or iSCSI pool can be attached to a VM, and bridge and direct NICs can be boot devices. The NIC model `default` lets libvirt choose, and `rt18139` is `rtl8139`: a VM with either did not start; a migration corrects the default NIC type setting.
|
||||
- A paused VM's memory is resized as a running VM's: the current memory changes and the guest takes it once resumed, the maximum stays (both only reached the VM at its next boot).
|
||||
- Cloning a UEFI VM no longer defines a storage pool on the host's NVRAM directory; the clone gets its own copy of the VM's UEFI variables.
|
||||
- The console .vv file finds the VM by its UUID; without a listen address or password it gives 127.0.0.1 and an empty password instead of "None".
|
||||
- A running VM without a guest agent shows the IPv4 address the host learned by ARP (the lookup always failed before).
|
||||
|
|
|
|||
|
|
@ -380,3 +380,22 @@ class LiveGuestTestCase(TestCase):
|
|||
self.assertEqual(dom.state()[0], libvirt.VIR_DOMAIN_RUNNING)
|
||||
self.wait_agent(dom)
|
||||
self.assert_guest_matches_config(dom)
|
||||
|
||||
def test_a_paused_guest_gets_its_new_memory_once_resumed(self):
|
||||
dom, inst = self.boot("gmem")
|
||||
|
||||
def balloon_mib():
|
||||
return dom.memoryStats().get("actual", 0) >> 10
|
||||
|
||||
def config_mib(tag):
|
||||
return int(etree.fromstring(dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE)).findtext(tag)) >> 10
|
||||
|
||||
for cur in (1536, 2048): # down, then back up to the maximum
|
||||
self.post("suspend", inst)
|
||||
# the posted maximum is ignored: an active VM keeps its own
|
||||
self.post("resize_memory", inst, {"memory": "4096", "cur_memory": str(cur)})
|
||||
self.assertEqual(dom.state()[0], libvirt.VIR_DOMAIN_PAUSED)
|
||||
self.assertEqual((config_mib("currentMemory"), config_mib("memory")), (cur, 2048))
|
||||
self.post("resume", inst)
|
||||
self.assertTrue(wait_for(lambda: balloon_mib() == cur, 60), f"balloon at {balloon_mib()} MiB, not {cur}")
|
||||
self.wait_agent(dom)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class MissingFieldTestCase(TestCase):
|
|||
self.assert_reported(self.post("resizevm_cpu", {"vcpu": "²", "cur_vcpu": "1"}))
|
||||
|
||||
def test_resize_memory_with_a_unicode_digit(self):
|
||||
self.assert_reported(self.post("resize_memory", {"memory_custom": "²", "cur_memory": "512"}))
|
||||
self.assert_reported(self.post("resize_memory", {"memory": "512", "cur_memory_custom": "²"}))
|
||||
|
||||
def test_set_qos_with_non_numeric_rate(self):
|
||||
self.assert_reported(self.post("set_qos", {"qos_direction": "inbound", "net-mac-0": "52:54:00:00:00:01", "qos_average": "fast"}))
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from django.test import TestCase
|
|||
from django.urls import reverse
|
||||
from instances.models import Instance
|
||||
from instances.utils import QUOTA_UNVERIFIED, check_user_quota
|
||||
from libvirt import VIR_DOMAIN_RUNNING
|
||||
from libvirt import VIR_DOMAIN_PAUSED, VIR_DOMAIN_RUNNING
|
||||
from vrtManager.util import OperationError
|
||||
|
||||
|
||||
|
|
@ -131,6 +131,7 @@ class QuotaViewTests(TestCase):
|
|||
wvm.return_value.get_memory.return_value = 2048
|
||||
wvm.return_value.get_cur_memory.return_value = 2048
|
||||
wvm.return_value.get_status.return_value = status
|
||||
wvm.return_value.instance.isActive.return_value = status != 5
|
||||
response = self.client.post(reverse(view, args=[self.vm.id]), data)
|
||||
messages = [str(m) for m in get_messages(response.wsgi_request)]
|
||||
return messages, check, wvm.return_value
|
||||
|
|
@ -150,6 +151,11 @@ class QuotaViewTests(TestCase):
|
|||
# resize_mem would write it, and it must not be the unchecked 8192
|
||||
proxy.resize_mem.assert_called_once_with("1024", 2048)
|
||||
|
||||
def test_paused_vm_memory_charges_no_maximum_change(self):
|
||||
_, check, proxy = self.post("instances:resize_memory", {"memory": 8192, "cur_memory": 1024}, status=VIR_DOMAIN_PAUSED)
|
||||
self.assertEqual(check.call_args.args[1:], (0, 0, 0, 0))
|
||||
proxy.resize_mem.assert_called_once_with("1024", 2048)
|
||||
|
||||
def test_shut_off_vm_memory_charges_the_maximum_change(self):
|
||||
_, check, _ = self.post("instances:resize_memory", {"memory": 4096, "cur_memory": 1024}, status=5)
|
||||
self.assertEqual(check.call_args.args[1:], (0, 0, 2048, 0))
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ from libvirt import (VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,
|
|||
VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA,
|
||||
VIR_DOMAIN_UNDEFINE_NVRAM,
|
||||
VIR_DOMAIN_START_PAUSED,
|
||||
VIR_DOMAIN_RUNNING,
|
||||
libvirtError)
|
||||
from logs.views import addlogmsg
|
||||
from webvirtcloud.middleware import error_text
|
||||
|
|
@ -884,10 +883,10 @@ def resize_memory(request, pk):
|
|||
new_cur_memory_custom = request.POST.get("cur_memory_custom", "")
|
||||
if new_cur_memory_custom:
|
||||
new_cur_memory = new_cur_memory_custom
|
||||
# The quota charges the maximum. A running VM keeps it (resize_mem changes
|
||||
# The quota charges the maximum. An active VM keeps it (resize_mem changes
|
||||
# only the current memory there); pass the current maximum, so a VM that
|
||||
# shuts down meanwhile does not get an unchecked one.
|
||||
if instance.proxy.get_status() == VIR_DOMAIN_RUNNING:
|
||||
if instance.proxy.instance.isActive():
|
||||
new_memory = memory
|
||||
if not _is_int(new_memory) or not _is_int(new_cur_memory):
|
||||
messages.error(request, _("Enter a whole number for memory"))
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ try:
|
|||
VIR_DOMAIN_AFFECT_LIVE,
|
||||
VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT,
|
||||
VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP,
|
||||
VIR_DOMAIN_RUNNING,
|
||||
VIR_DOMAIN_XML_SECURE,
|
||||
VIR_DOMAIN_XML_INACTIVE,
|
||||
VIR_DOMAIN_XML_MIGRATABLE,
|
||||
|
|
@ -1431,8 +1430,9 @@ class wvmInstance(wvmConnect):
|
|||
"""
|
||||
memory = int(memory) * 1024
|
||||
cur_memory = int(cur_memory) * 1024
|
||||
# if dom is running change only ram
|
||||
if self.get_status() == VIR_DOMAIN_RUNNING:
|
||||
# an active VM (running, paused, ...) changes only its current memory;
|
||||
# a paused guest's balloon driver applies it once it is resumed
|
||||
if self.instance.isActive():
|
||||
self.set_memory(cur_memory, VIR_DOMAIN_AFFECT_LIVE)
|
||||
self.set_memory(cur_memory, VIR_DOMAIN_AFFECT_CONFIG)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Device edits of a VM: each definition is changed as it describes the
|
||||
device, every NIC type can be a boot device, posted values stay values, and
|
||||
definition changes go through _defineXML."""
|
||||
definition changes go through _defineXML. An active VM's memory is resized
|
||||
by balloon, a stopped one's in its definition."""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
|
|
@ -106,5 +107,25 @@ class DeviceEditsTestCase(unittest.TestCase):
|
|||
self.assertIsNone(etree.fromstring(xml).find("model"))
|
||||
|
||||
|
||||
class ResizeMemTestCase(unittest.TestCase):
|
||||
def test_an_active_vm_changes_only_its_current_memory(self):
|
||||
# a paused VM is active too: its guest applies the balloon once resumed
|
||||
p = proxy(active=True)
|
||||
p.resize_mem(96, 160)
|
||||
self.assertEqual(
|
||||
[c.args for c in p.instance.setMemoryFlags.call_args_list],
|
||||
[(96 * 1024, VIR_DOMAIN_AFFECT_LIVE), (96 * 1024, VIR_DOMAIN_AFFECT_CONFIG)],
|
||||
)
|
||||
p._defineXML.assert_not_called()
|
||||
|
||||
def test_a_stopped_vm_gets_both_values_in_its_definition(self):
|
||||
p = proxy(active=False)
|
||||
p._XMLDesc = lambda flags: "<domain><memory>131072</memory><currentMemory>131072</currentMemory></domain>"
|
||||
p.resize_mem(96, 160)
|
||||
tree = defined(p)
|
||||
self.assertEqual((tree.findtext("memory"), tree.findtext("currentMemory")), (str(160 * 1024), str(96 * 1024)))
|
||||
p.instance.setMemoryFlags.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue