Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP3:GA
salt.24749
virt-uefi-fix-backport-312.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File virt-uefi-fix-backport-312.patch of Package salt.24749
From c5e38fe2d29cc18b4cca8819ec128622c14bf4ee Mon Sep 17 00:00:00 2001 From: Cedric Bosdonnat <cbosdonnat@suse.com> Date: Mon, 8 Feb 2021 14:23:21 +0100 Subject: [PATCH] virt UEFI fix backport (#312) * virt: serial and console fixes While reworking the tests, I figured out the serial and console update one wasn't quite right and it has shown errors in the code too. * Convert the virt test_update() test to pytest and split it Convert the huge test_update() function to pytest. While at it, also split it into smaller more manageable test functions. * virt: fix update of efi=True Libvirt uses efi=True as a flag to look for the proper UEFI loader and nvram template. This means that on a subsenquent state apply efi=True will trigger a definition update even if not needed. In case efi=True is provided, don't udpate if loader and nvram values are already set. * virt: remove useless try/except * Reverse all asserts in code introduced by PR#59189 Asserts need to have the actual value on the left hand side and the expected one on the right hand side. Reverting these in the newly converted code. In order to ease backporting to openSUSE package, this commit will remain separate. --- changelog/59188.fixed | 1 + salt/modules/virt.py | 89 +- tests/pytests/unit/modules/virt/conftest.py | 33 +- .../pytests/unit/modules/virt/test_domain.py | 1033 ++++++++++++++- .../pytests/unit/modules/virt/test_helpers.py | 32 + tests/unit/modules/test_virt.py | 1112 ----------------- 6 files changed, 1116 insertions(+), 1184 deletions(-) create mode 100644 changelog/59188.fixed diff --git a/changelog/59188.fixed b/changelog/59188.fixed new file mode 100644 index 0000000000..1382f9f07d --- /dev/null +++ b/changelog/59188.fixed @@ -0,0 +1 @@ +virt.update doesn't update the definition if efi=True and a loader is already set diff --git a/salt/modules/virt.py b/salt/modules/virt.py index 6d48239a8f..a47003c08c 100644 --- a/salt/modules/virt.py +++ b/salt/modules/virt.py @@ -134,23 +134,20 @@ import sys import time import jinja2.exceptions - import salt.utils.data import salt.utils.files import salt.utils.json import salt.utils.path import salt.utils.stringutils import salt.utils.templates +import salt.utils.virt import salt.utils.xmlutil as xmlutil import salt.utils.yaml -from salt._compat import ipaddress -from salt._compat import ElementTree -from salt._compat import saxutils +from salt._compat import ElementTree, ipaddress, saxutils from salt.exceptions import CommandExecutionError, SaltInvocationError from salt.ext import six from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin from salt.ext.six.moves.urllib.parse import urlparse, urlunparse -from salt.utils.virt import check_remote, download_remote try: import libvirt # pylint: disable=import-error @@ -1462,7 +1459,9 @@ def _disk_profile(conn, profile, hypervisor, disks, vm_name): ) # Transform the list to remove one level of dictionary and add the name as a property - disklist = [dict(d, name=name) for disk in disklist for name, d in six.iteritems(disk)] + disklist = [ + dict(d, name=name) for disk in disklist for name, d in six.iteritems(disk) + ] # Merge with the user-provided disks definitions if disks: @@ -1741,25 +1740,24 @@ def _handle_remote_boot_params(orig_boot): {"kernel", "initrd", "cmdline", "loader", "nvram"}, ] - try: - if keys in cases: - for key in keys: - if key == "efi" and type(orig_boot.get(key)) == bool: - new_boot[key] = orig_boot.get(key) - elif orig_boot.get(key) is not None and check_remote( - orig_boot.get(key) - ): - if saltinst_dir is None: - os.makedirs(CACHE_DIR) - saltinst_dir = CACHE_DIR - new_boot[key] = download_remote(orig_boot.get(key), saltinst_dir) - return new_boot - else: - raise SaltInvocationError( - "Invalid boot parameters,It has to follow this combination: [(kernel, initrd) or/and cmdline] or/and [(loader, nvram) or efi]" - ) - except Exception as err: # pylint: disable=broad-except - raise err + if keys in cases: + for key in keys: + if key == "efi" and type(orig_boot.get(key)) == bool: + new_boot[key] = orig_boot.get(key) + elif orig_boot.get(key) is not None and salt.utils.virt.check_remote( + orig_boot.get(key) + ): + if saltinst_dir is None: + os.makedirs(CACHE_DIR) + saltinst_dir = CACHE_DIR + new_boot[key] = salt.utils.virt.download_remote( + orig_boot.get(key), saltinst_dir + ) + return new_boot + else: + raise SaltInvocationError( + "Invalid boot parameters,It has to follow this combination: [(kernel, initrd) or/and cmdline] or/and [(loader, nvram) or efi]" + ) def _handle_efi_param(boot, desc): @@ -1782,10 +1780,11 @@ def _handle_efi_param(boot, desc): elif type(efi_value) == bool and os_attrib == {}: if efi_value is True and parent_tag.find("loader") is None: parent_tag.set("firmware", "efi") + return True if efi_value is False and parent_tag.find("loader") is not None: parent_tag.remove(parent_tag.find("loader")) parent_tag.remove(parent_tag.find("nvram")) - return True + return True elif type(efi_value) != bool: raise SaltInvocationError("Invalid efi value") return False @@ -2999,7 +2998,7 @@ def _serial_or_concole_equal(old, new): return _filter_serial_or_concole(old) == _filter_serial_or_concole(new) -def _diff_serial_list(old, new): +def _diff_serial_lists(old, new): """ Compare serial definitions to extract the changes @@ -3009,7 +3008,7 @@ def _diff_serial_list(old, new): return _diff_lists(old, new, _serial_or_concole_equal) -def _diff_console_list(old, new): +def _diff_console_lists(old, new): """ Compare console definitions to extract the changes @@ -3285,7 +3284,7 @@ def update( boot, boot_dev, numatune, - serial=serials, + serials=serials, consoles=consoles, stop_on_reboot=stop_on_reboot, **kwargs @@ -3674,12 +3673,18 @@ def update( # Update the XML definition with the new disks and diff changes devices_node = desc.find("devices") - parameters = { - "disk": ["disks", "disk_profile"], - "interface": ["interfaces", "nic_profile"], - "graphics": ["graphics"], - "serial": ["serial"], - "console": ["console"], + func_locals = locals() + + def _skip_update(names): + return all(func_locals.get(n) is None for n in names) + + to_skip = { + "disk": _skip_update(["disks", "disk_profile"]), + "interface": _skip_update(["interfaces", "nic_profile"]), + "graphics": _skip_update(["graphics"]), + "serial": _skip_update(["serials"]), + "console": _skip_update(["consoles"]), + "hostdev": _skip_update(["host_devices"]), } changes = {} for dev_type in parameters: @@ -3801,9 +3806,7 @@ def update( # Attaching device if source_file: - ElementTree.SubElement( - updated_disk, "source", file=source_file - ) + ElementTree.SubElement(updated_disk, "source", file=source_file) changes["disk"]["new"] = new_disks @@ -4525,9 +4528,7 @@ def get_profiles(hypervisor=None, **kwargs): virtconf = __salt__["config.get"]("virt", {}) for typ in ["disk", "nic"]: _func = getattr(sys.modules[__name__], "_{}_profile".format(typ)) - ret[typ] = { - "default": _func("default", hypervisor) - } + ret[typ] = {"default": _func("default", hypervisor)} if typ in virtconf: ret.setdefault(typ, {}) for prf in virtconf[typ]: @@ -7884,7 +7885,11 @@ def volume_infos(pool=None, volume=None, **kwargs): } for pool_obj in pools } - return {pool_name: volumes for (pool_name, volumes) in six.iteritems(vols) if volumes} + return { + pool_name: volumes + for (pool_name, volumes) in six.iteritems(vols) + if volumes + } except libvirt.libvirtError as err: log.debug("Silenced libvirt error: %s", six.text_type(err)) finally: diff --git a/tests/pytests/unit/modules/virt/conftest.py b/tests/pytests/unit/modules/virt/conftest.py index ec56bdff24..d4915dc672 100644 --- a/tests/pytests/unit/modules/virt/conftest.py +++ b/tests/pytests/unit/modules/virt/conftest.py @@ -68,10 +68,24 @@ def setup_loader(request): @pytest.fixture def make_mock_vm(): - def _make_mock_vm(xml_def): + def _make_mock_vm(xml_def=None, running=False, inactive_def=None): mocked_conn = virt.libvirt.openAuth.return_value - doc = ET.fromstring(xml_def) + desc = xml_def + if not desc: + desc = """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <on_reboot>restart</on_reboot> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + </os> + </domain> + """ + doc = ET.fromstring(desc) name = doc.find("name").text os_type = "hvm" os_type_node = doc.find("os/type") @@ -86,7 +100,12 @@ def make_mock_vm(): mocked_conn.lookupByName = MappedResultMock() mocked_conn.lookupByName.add(name) domain_mock = mocked_conn.lookupByName(name) - domain_mock.XMLDesc.return_value = xml_def + + domain_mock.XMLDesc = MappedResultMock() + domain_mock.XMLDesc.add(0, desc) + domain_mock.XMLDesc.add( + virt.libvirt.VIR_DOMAIN_XML_INACTIVE, inactive_def or desc + ) domain_mock.OSType.return_value = os_type # Return state as shutdown @@ -102,6 +121,8 @@ def make_mock_vm(): domain_mock.attachDevice.return_value = 0 domain_mock.detachDevice.return_value = 0 + domain_mock.setMemoryFlags.return_value = 0 + domain_mock.setVcpusFlags.return_value = 0 return domain_mock @@ -110,7 +131,7 @@ def make_mock_vm(): @pytest.fixture def make_mock_storage_pool(): - def _make_mock_storage_pool(name, type, volumes): + def _make_mock_storage_pool(name, type, volumes, source=None): mocked_conn = virt.libvirt.openAuth.return_value # Append the pool name to the list of known mocked pools @@ -127,8 +148,8 @@ def make_mock_storage_pool(): # Configure the pool mocked_conn.storagePoolLookupByName.add(name) mocked_pool = mocked_conn.storagePoolLookupByName(name) - source = "" - if type == "disk": + source_def = source + if not source and type == "disk": source = "<device path='/dev/{}'/>".format(name) pool_path = "/path/to/{}".format(name) mocked_pool.XMLDesc.return_value = """ diff --git a/tests/pytests/unit/modules/virt/test_domain.py b/tests/pytests/unit/modules/virt/test_domain.py index e35406e1e1..f1599ced84 100644 --- a/tests/pytests/unit/modules/virt/test_domain.py +++ b/tests/pytests/unit/modules/virt/test_domain.py @@ -1,8 +1,20 @@ +import os.path + +import pytest import salt.modules.virt as virt +import salt.syspaths +import salt.utils.xmlutil as xmlutil from salt._compat import ElementTree as ET +from salt.exceptions import SaltInvocationError from tests.support.mock import MagicMock, patch -from .test_helpers import append_to_XMLDesc +from .conftest import loader_modules_config +from .test_helpers import append_to_XMLDesc, assert_called, assertEqualUnit, strip_xml + + +@pytest.fixture +def configure_loader_modules(): + return loader_modules_config() def test_update_xen_disk_volumes(make_mock_vm, make_mock_storage_pool): @@ -46,8 +58,8 @@ def test_update_xen_disk_volumes(make_mock_vm, make_mock_storage_pool): ) assert ret["definition"] - define_mock = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(define_mock.call_args[0][0]) + virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) assert "block" == setxml.find(".//disk[3]").get("type") assert "/path/to/vdb/vdb1" == setxml.find(".//disk[3]/source").get("dev") @@ -500,8 +512,8 @@ def test_update_stop_on_reboot_reset(make_mock_vm): ret = virt.update("my_vm") assert ret["definition"] - define_mock = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(define_mock.call_args[0][0]) + virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) assert "restart" == setxml.find("./on_reboot").text @@ -524,8 +536,8 @@ def test_update_stop_on_reboot(make_mock_vm): ret = virt.update("my_vm", stop_on_reboot=True) assert ret["definition"] - define_mock = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(define_mock.call_args[0][0]) + virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) assert "destroy" == setxml.find("./on_reboot").text @@ -537,8 +549,8 @@ def test_init_no_stop_on_reboot(make_capabilities): with patch.dict(virt.os.__dict__, {"chmod": MagicMock(), "makedirs": MagicMock()}): with patch.dict(virt.__salt__, {"cmd.run": MagicMock()}): virt.init("test_vm", 2, 2048, start=False) - define_mock = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(define_mock.call_args[0][0]) + virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) assert "restart" == setxml.find("./on_reboot").text @@ -550,8 +562,8 @@ def test_init_stop_on_reboot(make_capabilities): with patch.dict(virt.os.__dict__, {"chmod": MagicMock(), "makedirs": MagicMock()}): with patch.dict(virt.__salt__, {"cmd.run": MagicMock()}): virt.init("test_vm", 2, 2048, stop_on_reboot=True, start=False) - define_mock = virt.libvirt.openAuth().defineXML - setxml = ET.fromstring(define_mock.call_args[0][0]) + virt.libvirt.openAuth().defineXML = virt.libvirt.openAuth().defineXML + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) assert "destroy" == setxml.find("./on_reboot").text # Update ret = virt.update( @@ -570,21 +582,994 @@ def test_init_stop_on_reboot(make_capabilities): }, }, ) + assert not ret.get("definition") + assert not ret.get("interface").get("attached") + assert not ret.get("interface").get("detached") + define_mock = virt.libvirt.openAuth().defineXML + define_mock.assert_not_called() + domain_mock.attachDevice.assert_not_called() + domain_mock.detachDevice.assert_not_called() + + +def test_update_no_param(make_mock_vm): + """ + Test virt.update(), no parameter passed + """ + domain_mock = make_mock_vm() + ret = virt.update("my_vm") + assert not ret["definition"] + assert not ret.get("mem") + assert not ret.get("cpu") + + +def test_update_cpu_and_mem(make_mock_vm): + """ + Test virt.update(), update both cpu and memory + """ + domain_mock = make_mock_vm() + ret = virt.update("my_vm", mem=2048, cpu=2) assert ret["definition"] + assert ret["mem"] + assert ret["cpu"] setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) - assert "timezone" == setxml.find("clock").get("offset") - assert "CEST" == setxml.find("clock").get("timezone") - assert {"rtc", "hpet"} == {t.get("name") for t in setxml.findall("clock/timer")} - assert "catchup" == setxml.find("clock/timer[@name='rtc']").get("tickpolicy") - assert "wall" == setxml.find("clock/timer[@name='rtc']").get("track") - assert {"slew": "4636", "threshold": "123", "limit": "2342"} == setxml.find( - "clock/timer[@name='rtc']/catchup" - ).attrib - assert "yes" == setxml.find("clock/timer[@name='hpet']").get("present") + assert setxml.find("vcpu").text == "2" + assert setxml.find("memory").text == "2147483648" + assert domain_mock.setMemoryFlags.call_args[0][0] == 2048 * 1024 + assert domain_mock.setVcpusFlags.call_args[0][0] == 2 + + +def test_update_cpu_simple(make_mock_vm): + """ + Test virt.update(), simple cpu count update + """ + domain_mock = make_mock_vm() + ret = virt.update("my_vm", cpu=2) + assert ret["definition"] + assert ret["cpu"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("vcpu").text == "2" + assert domain_mock.setVcpusFlags.call_args[0][0] == 2 + + +def test_update_add_cpu_topology(make_mock_vm): + """ + Test virt.update(), add cpu topology settings + """ + domain_mock = make_mock_vm() + ret = virt.update( + "my_vm", + cpu={ + "placement": "static", + "cpuset": "0-11", + "current": 5, + "maximum": 12, + "vcpus": { + "0": {"enabled": True, "hotpluggable": False, "order": 1}, + "1": {"enabled": False, "hotpluggable": True}, + }, + "mode": "custom", + "match": "exact", + "check": "full", + "model": { + "name": "coreduo", + "fallback": "allow", + "vendor_id": "Genuine20201", + }, + "vendor": "Intel", + "topology": {"sockets": 1, "cores": 12, "threads": 1}, + "cache": {"mode": "emulate", "level": 3}, + "features": {"lahf": "optional", "pcid": "disable"}, + "numa": { + "0": { + "cpus": "0-3", + "memory": "1g", + "discard": True, + "distances": {0: 10, 1: 21, 2: 31, 3: 41}, + }, + "1": { + "cpus": "4-6", + "memory": "0.5g", + "discard": False, + "memAccess": "shared", + "distances": {0: 21, 1: 10, 2: 15, 3: 30}, + }, + }, + }, + ) + assert ret["definition"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + + assert setxml.find("vcpu").text == "12" + assert setxml.find("vcpu").get("placement") == "static" + assert setxml.find("vcpu").get("cpuset") == "0,1,2,3,4,5,6,7,8,9,10,11" + assert setxml.find("vcpu").get("current") == "5" + + assert setxml.find("./vcpus/vcpu/[@id='0']").get("id") == "0" + assert setxml.find("./vcpus/vcpu/[@id='0']").get("enabled") == "yes" + assert setxml.find("./vcpus/vcpu/[@id='0']").get("hotpluggable") == "no" + assert setxml.find("./vcpus/vcpu/[@id='0']").get("order") == "1" + assert setxml.find("./vcpus/vcpu/[@id='1']").get("id") == "1" + assert setxml.find("./vcpus/vcpu/[@id='1']").get("enabled") == "no" + assert setxml.find("./vcpus/vcpu/[@id='1']").get("hotpluggable") == "yes" + assert setxml.find("./vcpus/vcpu/[@id='1']").get("order") is None + + assert setxml.find("cpu").get("mode") == "custom" + assert setxml.find("cpu").get("match") == "exact" + assert setxml.find("cpu").get("check") == "full" + + assert setxml.find("cpu/model").get("vendor_id") == "Genuine20201" + assert setxml.find("cpu/model").get("fallback") == "allow" + assert setxml.find("cpu/model").text == "coreduo" + + assert setxml.find("cpu/vendor").text == "Intel" + + assert setxml.find("cpu/topology").get("sockets") == "1" + assert setxml.find("cpu/topology").get("cores") == "12" + assert setxml.find("cpu/topology").get("threads") == "1" + + assert setxml.find("cpu/cache").get("level") == "3" + assert setxml.find("cpu/cache").get("mode") == "emulate" + + assert setxml.find("./cpu/feature[@name='pcid']").get("policy") == "disable" + assert setxml.find("./cpu/feature[@name='lahf']").get("policy") == "optional" + + assert setxml.find("./cpu/numa/cell/[@id='0']").get("cpus") == "0,1,2,3" + assert setxml.find("./cpu/numa/cell/[@id='0']").get("memory") == str(1024 ** 3) + assert setxml.find("./cpu/numa/cell/[@id='0']").get("unit") == "bytes" + assert setxml.find("./cpu/numa/cell/[@id='0']").get("discard") == "yes" + assert ( + setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='0']").get( + "value" + ) + == "10" + ) + assert ( + setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='1']").get( + "value" + ) + == "21" + ) + assert ( + setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='2']").get( + "value" + ) + == "31" + ) + assert ( + setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='3']").get( + "value" + ) + == "41" + ) + assert setxml.find("./cpu/numa/cell/[@id='1']").get("cpus") == "4,5,6" + assert setxml.find("./cpu/numa/cell/[@id='1']").get("memory") == str( + int(1024 ** 3 / 2) + ) + assert setxml.find("./cpu/numa/cell/[@id='1']").get("unit") == "bytes" + assert setxml.find("./cpu/numa/cell/[@id='1']").get("discard") == "no" + assert setxml.find("./cpu/numa/cell/[@id='1']").get("memAccess") == "shared" + assert ( + setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='0']").get( + "value" + ) + == "21" + ) + assert ( + setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='1']").get( + "value" + ) + == "10" + ) + assert ( + setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='2']").get( + "value" + ) + == "15" + ) + assert ( + setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='3']").get( + "value" + ) + == "30" + ) + + +@pytest.mark.parametrize("boot_dev", ["hd", "cdrom network hd"]) +def test_update_bootdev_unchanged(make_mock_vm, boot_dev): + """ + Test virt.update(), unchanged boot devices case + """ + domain_mock = make_mock_vm( + """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <on_reboot>restart</on_reboot> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + <boot dev="hd"/> + </os> + </domain> + """ + ) + ret = virt.update("my_vm", boot_dev=boot_dev) + assert (boot_dev != "hd") == ret["definition"] + if boot_dev == "hd": + virt.libvirt.openAuth().defineXML.assert_not_called() + else: + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert [node.get("dev") for node in setxml.findall("os/boot")] == [ + "cdrom", + "network", + "hd", + ] + + +def test_update_boot_kernel_paths(make_mock_vm): + """ + Test virt.update(), change boot with kernel/initrd path and kernel params + """ + domain_mock = make_mock_vm() + ret = virt.update( + "my_vm", + boot={ + "kernel": "/root/f8-i386-vmlinuz", + "initrd": "/root/f8-i386-initrd", + "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/", + }, + ) + assert ret["definition"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("os/kernel").text == "/root/f8-i386-vmlinuz" + assert setxml.find("os/initrd").text == "/root/f8-i386-initrd" + assert ( + setxml.find("os/cmdline").text + == "console=ttyS0 ks=http://example.com/f8-i386/os/" + ) + + +def test_update_boot_uefi_paths(make_mock_vm): + """ + Test virt.update(), add boot with uefi loader and nvram paths + """ + domain_mock = make_mock_vm() + + ret = virt.update( + "my_vm", + boot={ + "loader": "/usr/share/OVMF/OVMF_CODE.fd", + "nvram": "/usr/share/OVMF/OVMF_VARS.ms.fd", + }, + ) + + assert ret["definition"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("os/loader").text == "/usr/share/OVMF/OVMF_CODE.fd" + assert setxml.find("os/loader").get("readonly") == "yes" + assert setxml.find("os/loader").get("type") == "pflash" + assert setxml.find("os/nvram").get("template") == "/usr/share/OVMF/OVMF_VARS.ms.fd" + + +def test_update_boot_uefi_auto(make_mock_vm): + """ + Test virt.update(), change boot with efi value (automatic discovery of loader) + """ + domain_mock = make_mock_vm() + + ret = virt.update("my_vm", boot={"efi": True}) + assert ret["definition"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("os").get("firmware") == "efi" + + +def test_update_boot_uefi_auto_nochange(make_mock_vm): + """ + Test virt.update(), change boot with efi value and no change. + libvirt converts the efi=True value into a loader and nvram config with path. + """ + domain_mock = make_mock_vm( + """ + <domain type='kvm' id='1'> + <name>my_vm</name> + <uuid>27434df0-706d-4603-8ad7-5a88d19a3417</uuid> + <memory unit='KiB'>524288</memory> + <currentMemory unit='KiB'>524288</currentMemory> + <vcpu placement='static'>1</vcpu> + <resource> + <partition>/machine</partition> + </resource> + <os> + <type arch='x86_64' machine='pc-i440fx-4.2'>hvm</type> + <loader readonly='yes' type='pflash'>/usr/share/qemu/edk2-x86_64-code.fd</loader> + <nvram template='/usr/share/qemu/edk2-i386-vars.fd'>/var/lib/libvirt/qemu/nvram/vm01_VARS.fd</nvram> + </os> + <on_reboot>restart</on_reboot> + </domain> + """ + ) + + ret = virt.update("my_vm", boot={"efi": True}) + assert not ret["definition"] + virt.libvirt.openAuth().defineXML.assert_not_called() + + +def test_update_boot_invalid(make_mock_vm): + """ + Test virt.update(), change boot, invalid values + """ + domain_mock = make_mock_vm() + + with pytest.raises(SaltInvocationError): + virt.update( + "my_vm", + boot={ + "loader": "/usr/share/OVMF/OVMF_CODE.fd", + "initrd": "/root/f8-i386-initrd", + }, + ) + + with pytest.raises(SaltInvocationError): + virt.update("my_vm", boot={"efi": "Not a boolean value"}) + + +def test_update_add_memtune(make_mock_vm): + """ + Test virt.update(), add memory tune config case + """ + domain_mock = make_mock_vm() + ret = virt.update( + "my_vm", + mem={ + "soft_limit": "0.5g", + "hard_limit": "1024", + "swap_hard_limit": "2048m", + "min_guarantee": "1 g", + }, + ) + + assert ret["definition"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assertEqualUnit(setxml.find("memtune/soft_limit"), int(0.5 * 1024 ** 3), "bytes") + assertEqualUnit(setxml.find("memtune/hard_limit"), 1024 * 1024 ** 2, "bytes") + assertEqualUnit(setxml.find("memtune/swap_hard_limit"), 2048 * 1024 ** 2, "bytes") + assertEqualUnit(setxml.find("memtune/min_guarantee"), 1 * 1024 ** 3, "bytes") + + +def test_update_add_memtune_invalid_unit(make_mock_vm): + """ + Test virt.update(), add invalid unit to memory tuning config + """ + domain_mock = make_mock_vm() + + with pytest.raises(SaltInvocationError): + virt.update("my_vm", mem={"soft_limit": "2HB"}) + + with pytest.raises(SaltInvocationError): + virt.update("my_vm", mem={"soft_limit": "3.4.MB"}) + + +def test_update_add_numatune(make_mock_vm): + """ + Test virt.update(), add numatune config case + """ + domain_mock = make_mock_vm() + ret = virt.update( + "my_vm", + numatune={ + "memory": {"mode": "strict", "nodeset": 1}, + "memnodes": { + 0: {"mode": "strict", "nodeset": 1}, + 1: {"mode": "preferred", "nodeset": 2}, + }, + }, + ) + + assert ret["definition"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("numatune/memory").get("mode") == "strict" + assert setxml.find("numatune/memory").get("nodeset") == "1" + assert setxml.find("./numatune/memnode/[@cellid='0']").get("mode") == "strict" + assert setxml.find("./numatune/memnode/[@cellid='0']").get("nodeset") == "1" + assert setxml.find("./numatune/memnode/[@cellid='1']").get("mode") == "preferred" + assert setxml.find("./numatune/memnode/[@cellid='1']").get("nodeset") == "2" + + +def test_update_mem_simple(make_mock_vm): + """ + Test virt.update(), simple memory amount change + """ + domain_mock = make_mock_vm() + ret = virt.update("my_vm", mem=2048) + assert ret["definition"] + assert ret["mem"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("memory").text == str(2048 * 1024 ** 2) + assert setxml.find("memory").get("unit") == "bytes" + assert domain_mock.setMemoryFlags.call_args[0][0] == 2048 * 1024 + + +def test_update_mem(make_mock_vm): + """ + Test virt.update(), advanced memory amounts changes + """ + domain_mock = make_mock_vm() + + ret = virt.update( + "my_vm", mem={"boot": "0.5g", "current": "2g", "max": "1g", "slots": 12}, + ) + assert ret["definition"] + assert ret["mem"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("memory").get("unit") == "bytes" + assert setxml.find("memory").text == str(int(0.5 * 1024 ** 3)) + assert setxml.find("maxMemory").text == str(1 * 1024 ** 3) + assert setxml.find("currentMemory").text == str(2 * 1024 ** 3) + + +def test_update_add_mem_backing(make_mock_vm): + """ + Test virt.update(), add memory backing case + """ + domain_mock = make_mock_vm() + ret = virt.update( + "my_vm", + mem={ + "hugepages": [ + {"nodeset": "1-5,^4", "size": "1g"}, + {"nodeset": "4", "size": "2g"}, + ], + "nosharepages": True, + "locked": True, + "source": "file", + "access": "shared", + "allocation": "immediate", + "discard": True, + }, + ) - # Revert to UTC - ret = virt.update("my_vm", clock={"utc": True, "adjustment": None, "timers": None}) assert ret["definition"] setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) - assert {"offset": "utc"} == setxml.find("clock").attrib - assert setxml.find("clock/timer") is None + assert { + p.get("nodeset"): {"size": p.get("size"), "unit": p.get("unit")} + for p in setxml.findall("memoryBacking/hugepages/page") + } == { + "1,2,3,5": {"size": str(1024 ** 3), "unit": "bytes"}, + "4": {"size": str(2 * 1024 ** 3), "unit": "bytes"}, + } + assert setxml.find("./memoryBacking/nosharepages") is not None + assert setxml.find("./memoryBacking/nosharepages").text is None + assert setxml.find("./memoryBacking/nosharepages").keys() == [] + assert setxml.find("./memoryBacking/locked") is not None + assert setxml.find("./memoryBacking/locked").text is None + assert setxml.find("./memoryBacking/locked").keys() == [] + assert setxml.find("./memoryBacking/source").attrib["type"] == "file" + assert setxml.find("./memoryBacking/access").attrib["mode"] == "shared" + assert setxml.find("./memoryBacking/discard") is not None + + +def test_update_add_iothreads(make_mock_vm): + """ + Test virt.update(), add iothreads + """ + domain_mock = make_mock_vm() + ret = virt.update("my_vm", cpu={"iothreads": 5}) + assert ret["definition"] + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("iothreads").text == "5" + + +def test_update_add_cputune(make_mock_vm): + """ + Test virt.update(), adding CPU tuning parameters + """ + domain_mock = make_mock_vm() + cputune = { + "shares": 2048, + "period": 122000, + "quota": -1, + "global_period": 1000000, + "global_quota": -3, + "emulator_period": 1200000, + "emulator_quota": -10, + "iothread_period": 133000, + "iothread_quota": -1, + "vcpupin": {0: "1-4,^2", 1: "0,1", 2: "2,3", 3: "0,4"}, + "emulatorpin": "1-3", + "iothreadpin": {1: "5-6", 2: "7-8"}, + "vcpusched": [ + {"scheduler": "fifo", "priority": 1, "vcpus": "0"}, + {"scheduler": "fifo", "priotity": 2, "vcpus": "1"}, + {"scheduler": "idle", "priotity": 3, "vcpus": "2"}, + ], + "iothreadsched": [{"scheduler": "batch", "iothreads": "7"}], + "cachetune": { + "0-3": { + 0: {"level": 3, "type": "both", "size": 3}, + 1: {"level": 3, "type": "both", "size": 3}, + "monitor": {1: 3, "0-3": 3}, + }, + "4-5": {"monitor": {4: 3, 5: 2}}, + }, + "memorytune": {"0-2": {0: 60}, "3-4": {0: 50, 1: 70}}, + } + assert virt.update("my_vm", cpu={"tuning": cputune}) == { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + } + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("cputune/shares").text == "2048" + assert setxml.find("cputune/period").text == "122000" + assert setxml.find("cputune/quota").text == "-1" + assert setxml.find("cputune/global_period").text == "1000000" + assert setxml.find("cputune/global_quota").text == "-3" + assert setxml.find("cputune/emulator_period").text == "1200000" + assert setxml.find("cputune/emulator_quota").text == "-10" + assert setxml.find("cputune/iothread_period").text == "133000" + assert setxml.find("cputune/iothread_quota").text == "-1" + assert setxml.find("cputune/vcpupin[@vcpu='0']").get("cpuset") == "1,3,4" + assert setxml.find("cputune/vcpupin[@vcpu='1']").get("cpuset") == "0,1" + assert setxml.find("cputune/vcpupin[@vcpu='2']").get("cpuset") == "2,3" + assert setxml.find("cputune/vcpupin[@vcpu='3']").get("cpuset") == "0,4" + assert setxml.find("cputune/emulatorpin").get("cpuset") == "1,2,3" + assert setxml.find("cputune/iothreadpin[@iothread='1']").get("cpuset") == "5,6" + assert setxml.find("cputune/iothreadpin[@iothread='2']").get("cpuset") == "7,8" + assert setxml.find("cputune/vcpusched[@vcpus='0']").get("priority") == "1" + assert setxml.find("cputune/vcpusched[@vcpus='0']").get("scheduler") == "fifo" + assert setxml.find("cputune/iothreadsched").get("iothreads") == "7" + assert setxml.find("cputune/iothreadsched").get("scheduler") == "batch" + assert ( + setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']").get("level") + == "3" + ) + assert ( + setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']").get("type") + == "both" + ) + assert ( + setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']/monitor[@vcpus='1']").get( + "level" + ) + == "3" + ) + assert ( + setxml.find("./cputune/cachetune[@vcpus='4,5']/monitor[@vcpus='4']").get( + "level" + ) + == "3" + ) + assert ( + setxml.find("./cputune/cachetune[@vcpus='4,5']/monitor[@vcpus='5']").get( + "level" + ) + == "2" + ) + assert ( + setxml.find("./cputune/memorytune[@vcpus='0,1,2']/node[@id='0']").get( + "bandwidth" + ) + == "60" + ) + assert ( + setxml.find("./cputune/memorytune[@vcpus='3,4']/node[@id='0']").get("bandwidth") + == "50" + ) + assert ( + setxml.find("./cputune/memorytune[@vcpus='3,4']/node[@id='1']").get("bandwidth") + == "70" + ) + + +def test_update_graphics(make_mock_vm): + """ + Test virt.update(), graphics update case + """ + domain_mock = make_mock_vm( + """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <on_reboot>restart</on_reboot> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + </os> + <devices> + <graphics type='spice' listen='127.0.0.1' autoport='yes'> + <listen type='address' address='127.0.0.1'/> + </graphics> + </devices> + </domain> + """ + ) + assert virt.update("my_vm", graphics={"type": "vnc"}) == { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + } + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("devices/graphics").get("type") == "vnc" + + +def test_update_console(make_mock_vm): + """ + Test virt.update(), console and serial devices update case + """ + domain_mock = make_mock_vm( + """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <on_reboot>restart</on_reboot> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + </os> + <devices> + <serial type='pty'/> + <console type='pty'/> + </devices> + </domain> + """ + ) + + assert virt.update( + "my_vm", serials=[{"type": "tcp"}], consoles=[{"type": "tcp"}] + ) == { + "definition": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + } + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("devices/serial").attrib["type"] == "tcp" + assert setxml.find("devices/console").attrib["type"] == "tcp" + + +def test_update_disks(make_mock_vm): + """ + Test virt.udpate() with disk device changes + """ + root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") + xml_def = """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <on_reboot>restart</on_reboot> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + </os> + <devices> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='{}{}my_vm_system.qcow2'/> + <backingStore/> + <target dev='vda' bus='virtio'/> + <alias name='virtio-disk0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> + </disk> + <disk type="network" device="disk"> + <driver name='raw' type='qcow2'/> + <source protocol='rbd' name='libvirt-pool/my_vm_data2'> + <host name='ses2.tf.local'/> + </source> + <target dev='vdc' bus='virtio'/> + <alias name='virtio-disk2'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/> + </disk> + </devices> + </domain> + """.format( + root_dir, os.sep + ) + domain_mock = make_mock_vm(xml_def) + + mock_chmod = MagicMock() + mock_run = MagicMock() + with patch.dict(os.__dict__, {"chmod": mock_chmod, "makedirs": MagicMock()}): + with patch.dict(virt.__salt__, {"cmd.run": mock_run}): + ret = virt.update( + "my_vm", + disk_profile="default", + disks=[ + { + "name": "cddrive", + "device": "cdrom", + "source_file": None, + "model": "ide", + }, + {"name": "added", "size": 2048, "io": "threads"}, + ], + ) + added_disk_path = os.path.join( + virt.__salt__["config.get"]("virt:images"), "my_vm_added.qcow2" + ) + assert mock_run.call_args[0][ + 0 + ] == 'qemu-img create -f qcow2 "{}" 2048M'.format(added_disk_path) + assert mock_chmod.call_args[0][0] == added_disk_path + assert [ + ET.fromstring(disk).find("source").get("file") + if str(disk).find("<source") > -1 + else None + for disk in ret["disk"]["attached"] + ] == [None, os.path.join(root_dir, "my_vm_added.qcow2")] + + assert [ + ET.fromstring(disk).find("source").get("volume") + or ET.fromstring(disk).find("source").get("name") + for disk in ret["disk"]["detached"] + ] == ["libvirt-pool/my_vm_data2"] + assert domain_mock.attachDevice.call_count == 2 + assert domain_mock.detachDevice.call_count == 1 + + setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) + assert setxml.find("devices/disk[3]/driver").get("io") == "threads" + + +def test_update_nics(make_mock_vm): + """ + Test virt.update() with NIC device changes + """ + domain_mock = make_mock_vm( + """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <on_reboot>restart</on_reboot> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + </os> + <devices> + <interface type='network'> + <mac address='52:54:00:39:02:b1'/> + <source network='default' bridge='virbr0'/> + <target dev='vnet0'/> + <model type='virtio'/> + <alias name='net0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </interface> + <interface type='network'> + <mac address='52:54:00:39:02:b2'/> + <source network='oldnet' bridge='virbr1'/> + <target dev='vnet1'/> + <model type='virtio'/> + <alias name='net1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x1'/> + </interface> + </devices> + </domain> + """ + ) + mock_config = salt.utils.yaml.safe_load( + """ + virt: + nic: + myprofile: + - network: default + name: eth0 + """ + ) + with patch.dict(salt.modules.config.__opts__, mock_config): + ret = virt.update( + "my_vm", + nic_profile="myprofile", + interfaces=[ + { + "name": "eth0", + "type": "network", + "source": "default", + "mac": "52:54:00:39:02:b1", + }, + {"name": "eth1", "type": "network", "source": "newnet"}, + ], + ) + assert [ + ET.fromstring(nic).find("source").get("network") + for nic in ret["interface"]["attached"] + ] == ["newnet"] + assert [ + ET.fromstring(nic).find("source").get("network") + for nic in ret["interface"]["detached"] + ] == ["oldnet"] + domain_mock.attachDevice.assert_called_once() + domain_mock.detachDevice.assert_called_once() + + +def test_update_remove_disks_nics(make_mock_vm): + """ + Test virt.update() when removing nics and disks even if that may sound silly + """ + root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") + xml_def = """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <on_reboot>restart</on_reboot> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + </os> + <devices> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='{}{}my_vm_system.qcow2'/> + <backingStore/> + <target dev='vda' bus='virtio'/> + <alias name='virtio-disk0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> + </disk> + <interface type='network'> + <mac address='52:54:00:39:02:b1'/> + <source network='default' bridge='virbr0'/> + <target dev='vnet0'/> + <model type='virtio'/> + <alias name='net0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </interface> + </devices> + </domain> + """.format( + root_dir, os.sep + ) + domain_mock = make_mock_vm(xml_def) + + ret = virt.update( + "my_vm", nic_profile=None, interfaces=[], disk_profile=None, disks=[] + ) + assert ret["interface"].get("attached", []) == [] + assert len(ret["interface"]["detached"]) == 1 + assert ret["disk"].get("attached", []) == [] + assert len(ret["disk"]["detached"]) == 1 + + domain_mock.attachDevice.assert_not_called() + assert domain_mock.detachDevice.call_count == 2 + + +def test_update_no_change(make_mock_vm, make_mock_storage_pool): + """ + Test virt.update() with no change + """ + root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") + xml_def = """ + <domain type='kvm' id='7'> + <name>my_vm</name> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>1048576</currentMemory> + <vcpu placement='auto'>1</vcpu> + <on_reboot>restart</on_reboot> + <os> + <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> + <boot dev="hd"/> + </os> + <devices> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='{}{}my_vm_system.qcow2'/> + <backingStore/> + <target dev='vda' bus='virtio'/> + <alias name='virtio-disk0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> + </disk> + <disk type='volume' device='disk'> + <driver name='qemu' type='qcow2'/> + <source pool='default' volume='my_vm_data'/> + <backingStore/> + <target dev='vdb' bus='virtio'/> + <alias name='virtio-disk1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x1'/> + </disk> + <disk type="network" device="disk"> + <driver name='raw' type='qcow2'/> + <source protocol='rbd' name='libvirt-pool/my_vm_data2'> + <host name='ses2.tf.local'/> + <host name='ses3.tf.local' port='1234'/> + <auth username='libvirt'> + <secret type='ceph' usage='pool_test-rbd'/> + </auth> + </source> + <target dev='vdc' bus='virtio'/> + <alias name='virtio-disk2'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/> + </disk> + <interface type='network'> + <mac address='52:54:00:39:02:b1'/> + <source network='default' bridge='virbr0'/> + <target dev='vnet0'/> + <model type='virtio'/> + <alias name='net0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </interface> + <graphics type='spice' listen='127.0.0.1' autoport='yes'> + <listen type='address' address='127.0.0.1'/> + </graphics> + <video> + <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/> + <alias name='video0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </video> + <serial type='pty'/> + <console type='pty'/> + </devices> + </domain> + """.format( + root_dir, os.sep + ) + domain_mock = make_mock_vm(xml_def) + + make_mock_storage_pool("default", "dir", ["my_vm_data"]) + make_mock_storage_pool( + "test-rbd", + "rbd", + ["my_vm_data2"], + source=""" + <host name='ses2.tf.local'/> + <host name='ses3.tf.local' port='1234'/> + <name>libvirt-pool</name> + <auth type='ceph' username='libvirt'> + <secret usage='pool_test-rbd'/> + </auth> + """, + ) + assert virt.update( + "my_vm", + cpu=1, + mem=1024, + disk_profile="default", + disks=[ + {"name": "data", "size": 2048, "pool": "default"}, + {"name": "data2", "size": 4096, "pool": "test-rbd", "format": "raw"}, + ], + nic_profile="myprofile", + interfaces=[ + { + "name": "eth0", + "type": "network", + "source": "default", + "mac": "52:54:00:39:02:b1", + }, + ], + graphics={ + "type": "spice", + "listen": {"type": "address", "address": "127.0.0.1"}, + }, + ) == { + "definition": False, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + } + + +def test_update_failure(make_mock_vm): + """ + Test virt.update() with errors + """ + domain_mock = make_mock_vm() + virt.libvirt.openAuth().defineXML.side_effect = virt.libvirt.libvirtError( + "Test error" + ) + with pytest.raises(virt.libvirt.libvirtError): + virt.update("my_vm", mem=2048) + + # Failed single update failure case + virt.libvirt.openAuth().defineXML = MagicMock(return_value=True) + domain_mock.setMemoryFlags.side_effect = virt.libvirt.libvirtError( + "Failed to live change memory" + ) + + domain_mock.setVcpusFlags.return_value = 0 + assert virt.update("my_vm", cpu=4, mem=2048) == { + "definition": True, + "errors": ["Failed to live change memory"], + "cpu": True, + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + } diff --git a/tests/pytests/unit/modules/virt/test_helpers.py b/tests/pytests/unit/modules/virt/test_helpers.py index f64aee2821..01deb4f9cc 100644 --- a/tests/pytests/unit/modules/virt/test_helpers.py +++ b/tests/pytests/unit/modules/virt/test_helpers.py @@ -9,3 +9,35 @@ def append_to_XMLDesc(mocked, fragment): xml_fragment = ET.fromstring(fragment) xml_doc.append(xml_fragment) mocked.XMLDesc.return_value = ET.tostring(xml_doc) + + +def assert_xml_equals(expected, actual): + """ + Assert that two ElementTree nodes are equal + """ + assert xmlutil.to_dict(xmlutil.strip_spaces(expected), True) == xmlutil.to_dict( + xmlutil.strip_spaces(actual), True + ) + + +def strip_xml(xml_str): + """ + Remove all spaces and formatting from an XML string + """ + return ET.tostring(xmlutil.strip_spaces(ET.fromstring(xml_str))) + + +def assert_called(mock, condition): + """ + Assert that the mock has been called if not in test mode, and vice-versa. + I know it's a simple XOR, but makes the tests easier to read + """ + assert not condition and not mock.called or condition and mock.called + + +def assertEqualUnit(actual, expected, unit="KiB"): + """ + Assert that two ElementTree nodes have the same value and unit + """ + assert actual.get("unit") == unit + assert actual.text == str(expected) diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py index 1a9b17699b..910b3b014a 100644 --- a/tests/unit/modules/test_virt.py +++ b/tests/unit/modules/test_virt.py @@ -14,14 +14,12 @@ import salt.config import salt.modules.config as config import salt.modules.virt as virt import salt.syspaths - import salt.utils.yaml from salt._compat import ElementTree as ET from salt.exceptions import CommandExecutionError, SaltInvocationError # pylint: disable=import-error from salt.ext.six.moves import range # pylint: disable=redefined-builtin - from tests.support.mixins import LoaderModuleMockMixin from tests.support.mock import MagicMock, patch from tests.support.unit import TestCase @@ -2271,1116 +2269,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): stream_mock.finish.assert_called_once() vol_mock.upload.assert_called_once_with(stream_mock, 0, 0, 0) - def test_update(self): - """ - Test virt.update() - """ - root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images") - xml = """ - <domain type='kvm' id='7'> - <name>my_vm</name> - <memory unit='KiB'>1048576</memory> - <currentMemory unit='KiB'>1048576</currentMemory> - <vcpu placement='auto'>1</vcpu> - <on_reboot>restart</on_reboot> - <os> - <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type> - <boot dev="hd"/> - </os> - <devices> - <disk type='file' device='disk'> - <driver name='qemu' type='qcow2'/> - <source file='{}{}my_vm_system.qcow2'/> - <backingStore/> - <target dev='vda' bus='virtio'/> - <alias name='virtio-disk0'/> - <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/> - </disk> - <disk type='volume' device='disk'> - <driver name='qemu' type='qcow2'/> - <source pool='default' volume='my_vm_data'/> - <backingStore/> - <target dev='vdb' bus='virtio'/> - <alias name='virtio-disk1'/> - <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x1'/> - </disk> - <disk type="network" device="disk"> - <driver name='raw' type='qcow2'/> - <source protocol='rbd' name='libvirt-pool/my_vm_data2'> - <host name='ses2.tf.local'/> - <host name='ses3.tf.local' port='1234'/> - <auth username='libvirt'> - <secret type='ceph' usage='pool_test-rbd'/> - </auth> - </source> - <target dev='vdc' bus='virtio'/> - <alias name='virtio-disk2'/> - <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/> - </disk> - <interface type='network'> - <mac address='52:54:00:39:02:b1'/> - <source network='default' bridge='virbr0'/> - <target dev='vnet0'/> - <model type='virtio'/> - <alias name='net0'/> - <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> - </interface> - <interface type='network'> - <mac address='52:54:00:39:02:b2'/> - <source network='oldnet' bridge='virbr1'/> - <target dev='vnet1'/> - <model type='virtio'/> - <alias name='net1'/> - <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x1'/> - </interface> - <graphics type='spice' listen='127.0.0.1' autoport='yes'> - <listen type='address' address='127.0.0.1'/> - </graphics> - <video> - <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/> - <alias name='video0'/> - <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> - </video> - <serial type='pty'/> - <console type='pty'/> - </devices> - </domain> - """.format( - root_dir, os.sep - ) - domain_mock = self.set_mock_vm("my_vm", xml) - domain_mock.OSType = MagicMock(return_value="hvm") - define_mock = MagicMock(return_value=True) - self.mock_conn.defineXML = define_mock - - # No parameter passed case - self.assertEqual( - { - "definition": False, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm"), - ) - - # mem + cpu case - define_mock.reset_mock() - domain_mock.setMemoryFlags.return_value = 0 - domain_mock.setVcpusFlags.return_value = 0 - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - "mem": True, - "cpu": True, - }, - virt.update("my_vm", mem=2048, cpu=2), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual("2", setxml.find("vcpu").text) - self.assertEqual("2147483648", setxml.find("memory").text) - self.assertEqual(2048 * 1024, domain_mock.setMemoryFlags.call_args[0][0]) - - # Same parameters passed than in default virt.defined state case - self.assertEqual( - { - "definition": False, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update( - "my_vm", - cpu=None, - mem=None, - disk_profile=None, - disks=None, - nic_profile=None, - interfaces=None, - graphics=None, - live=True, - connection=None, - username=None, - password=None, - boot=None, - numatune=None, - ), - ) - - # test cpu passed as an integer case - setvcpus_mock = MagicMock(return_value=0) - domain_mock.setVcpusFlags = setvcpus_mock - self.assertEqual( - { - "definition": True, - "cpu": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=2), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("vcpu").text, "2") - self.assertEqual(setvcpus_mock.call_args[0][0], 2) - define_mock.reset_mock() - - # test updating vcpu attribute - vcpu = { - "placement": "static", - "cpuset": "0-11", - "current": 5, - "maximum": 12, - } - self.assertEqual( - { - "definition": True, - "cpu": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=vcpu), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("vcpu").text, "12") - self.assertEqual(setxml.find("vcpu").attrib["placement"], "static") - self.assertEqual( - setxml.find("vcpu").attrib["cpuset"], "0,1,2,3,4,5,6,7,8,9,10,11" - ) - self.assertEqual(setxml.find("vcpu").attrib["current"], "5") - - # test adding vcpus elements - vcpus = { - "vcpus": { - "0": {"enabled": True, "hotpluggable": False, "order": 1}, - "1": {"enabled": False, "hotpluggable": True}, - } - } - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=vcpus), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("./vcpus/vcpu/[@id='0']").attrib["id"], "0") - self.assertEqual(setxml.find("./vcpus/vcpu/[@id='0']").attrib["enabled"], "yes") - self.assertEqual( - setxml.find("./vcpus/vcpu/[@id='0']").attrib["hotpluggable"], "no" - ) - self.assertEqual(setxml.find("./vcpus/vcpu/[@id='0']").attrib["order"], "1") - self.assertEqual(setxml.find("./vcpus/vcpu/[@id='1']").attrib["id"], "1") - self.assertEqual(setxml.find("./vcpus/vcpu/[@id='1']").attrib["enabled"], "no") - self.assertEqual( - setxml.find("./vcpus/vcpu/[@id='1']").attrib["hotpluggable"], "yes" - ) - self.assertEqual( - setxml.find("./vcpus/vcpu/[@id='1']").attrib.get("order"), None - ) - - # test adding cpu attribute - cpu_atr = {"mode": "custom", "match": "exact", "check": "full"} - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=cpu_atr), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("cpu").attrib["mode"], "custom") - self.assertEqual(setxml.find("cpu").attrib["match"], "exact") - self.assertEqual(setxml.find("cpu").attrib["check"], "full") - - # test adding cpu model - cpu_model = { - "model": { - "name": "coreduo", - "fallback": "allow", - "vendor_id": "Genuine20201", - } - } - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=cpu_model), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - setxml.find("cpu").find("model").attrib.get("vendor_id"), "Genuine20201" - ) - self.assertEqual( - setxml.find("cpu").find("model").attrib.get("fallback"), "allow" - ) - self.assertEqual(setxml.find("cpu").find("model").text, "coreduo") - - # test adding cpu vendor - cpu_vendor = {"vendor": "Intel"} - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=cpu_vendor), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("cpu").find("vendor").text, "Intel") - - # test adding cpu topology - cpu_topology = {"topology": {"sockets": 1, "cores": 12, "threads": 1}} - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=cpu_topology), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("cpu").find("topology").attrib.get("sockets"), "1") - self.assertEqual(setxml.find("cpu").find("topology").attrib.get("cores"), "12") - self.assertEqual(setxml.find("cpu").find("topology").attrib.get("threads"), "1") - - # test adding cache - cpu_cache = {"cache": {"mode": "emulate", "level": 3}} - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=cpu_cache), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("cpu").find("cache").attrib.get("level"), "3") - self.assertEqual(setxml.find("cpu").find("cache").attrib.get("mode"), "emulate") - - # test adding feature - cpu_feature = {"features": {"lahf": "optional", "pcid": "disable"}} - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=cpu_feature), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - setxml.find("./cpu/feature[@name='pcid']").attrib.get("policy"), "disable" - ) - self.assertEqual( - setxml.find("./cpu/feature[@name='lahf']").attrib.get("policy"), "optional" - ) - - # test adding numa cell - numa_cell = { - "numa": { - "0": { - "cpus": "0-3", - "memory": "1g", - "discard": True, - "distances": {0: 10, 1: 21, 2: 31, 3: 41}, - }, - "1": { - "cpus": "4-6", - "memory": "0.5g", - "discard": False, - "memAccess": "shared", - "distances": {0: 21, 1: 10, 2: 15, 3: 30}, - }, - } - } - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=numa_cell), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='0']").attrib["cpus"], "0,1,2,3" - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='0']").attrib["memory"], str(1024 ** 3) - ) - self.assertEqual(setxml.find("./cpu/numa/cell/[@id='0']").get("unit"), "bytes") - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='0']").attrib["discard"], "yes" - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='0']").attrib[ - "value" - ], - "10", - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='1']").attrib[ - "value" - ], - "21", - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='2']").attrib[ - "value" - ], - "31", - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='3']").attrib[ - "value" - ], - "41", - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']").attrib["cpus"], "4,5,6" - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']").attrib["memory"], - str(int(1024 ** 3 / 2)), - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']").get("unit"), "bytes", - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']").attrib["discard"], "no" - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']").attrib["memAccess"], "shared" - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='0']").attrib[ - "value" - ], - "21", - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='1']").attrib[ - "value" - ], - "10", - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='2']").attrib[ - "value" - ], - "15", - ) - self.assertEqual( - setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='3']").attrib[ - "value" - ], - "30", - ) - - # Update boot parameter case - boot = { - "kernel": "/root/f8-i386-vmlinuz", - "initrd": "/root/f8-i386-initrd", - "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/", - } - - # Update boot devices case - define_mock.reset_mock() - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", boot_dev="cdrom network hd"), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - ["cdrom", "network", "hd"], - [node.get("dev") for node in setxml.findall("os/boot")], - ) - - # Update unchanged boot devices case - define_mock.reset_mock() - self.assertEqual( - { - "definition": False, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", boot_dev="hd"), - ) - define_mock.assert_not_called() - - # Update with boot parameter case - define_mock.reset_mock() - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", boot=boot), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("os").find("kernel").text, "/root/f8-i386-vmlinuz") - self.assertEqual(setxml.find("os").find("initrd").text, "/root/f8-i386-initrd") - self.assertEqual( - setxml.find("os").find("cmdline").text, - "console=ttyS0 ks=http://example.com/f8-i386/os/", - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("os").find("kernel").text, "/root/f8-i386-vmlinuz") - self.assertEqual(setxml.find("os").find("initrd").text, "/root/f8-i386-initrd") - self.assertEqual( - setxml.find("os").find("cmdline").text, - "console=ttyS0 ks=http://example.com/f8-i386/os/", - ) - - boot_uefi = { - "loader": "/usr/share/OVMF/OVMF_CODE.fd", - "nvram": "/usr/share/OVMF/OVMF_VARS.ms.fd", - } - - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", boot=boot_uefi), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - setxml.find("os").find("loader").text, "/usr/share/OVMF/OVMF_CODE.fd" - ) - self.assertEqual(setxml.find("os").find("loader").attrib.get("readonly"), "yes") - self.assertEqual(setxml.find("os").find("loader").attrib["type"], "pflash") - self.assertEqual( - setxml.find("os").find("nvram").attrib["template"], - "/usr/share/OVMF/OVMF_VARS.ms.fd", - ) - - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", boot={"efi": True}), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("os").attrib.get("firmware"), "efi") - - invalid_boot = { - "loader": "/usr/share/OVMF/OVMF_CODE.fd", - "initrd": "/root/f8-i386-initrd", - } - - with self.assertRaises(SaltInvocationError): - virt.update("my_vm", boot=invalid_boot) - - with self.assertRaises(SaltInvocationError): - virt.update("my_vm", boot={"efi": "Not a boolean value"}) - - # Update memtune parameter case - memtune = { - "soft_limit": "0.5g", - "hard_limit": "1024", - "swap_hard_limit": "2048m", - "min_guarantee": "1 g", - } - - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=memtune), - ) - - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqualUnit( - setxml.find("memtune").find("soft_limit"), int(0.5 * 1024 ** 3), "bytes" - ) - self.assertEqualUnit( - setxml.find("memtune").find("hard_limit"), 1024 * 1024 ** 2, "bytes" - ) - self.assertEqualUnit( - setxml.find("memtune").find("swap_hard_limit"), 2048 * 1024 ** 2, "bytes" - ) - self.assertEqualUnit( - setxml.find("memtune").find("min_guarantee"), 1 * 1024 ** 3, "bytes" - ) - - invalid_unit = {"soft_limit": "2HB"} - - with self.assertRaises(SaltInvocationError): - virt.update("my_vm", mem=invalid_unit) - - invalid_number = { - "soft_limit": "3.4.MB", - } - - with self.assertRaises(SaltInvocationError): - virt.update("my_vm", mem=invalid_number) - - # Update numatune case - numatune = { - "memory": {"mode": "strict", "nodeset": 1}, - "memnodes": { - 0: {"mode": "strict", "nodeset": 1}, - 1: {"mode": "preferred", "nodeset": 2}, - }, - } - - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", numatune=numatune), - ) - - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - setxml.find("numatune").find("memory").attrib.get("mode"), "strict" - ) - - self.assertEqual( - setxml.find("numatune").find("memory").attrib.get("nodeset"), "1" - ) - - self.assertEqual( - setxml.find("./numatune/memnode/[@cellid='0']").attrib.get("mode"), "strict" - ) - - self.assertEqual( - setxml.find("./numatune/memnode/[@cellid='0']").attrib.get("nodeset"), "1" - ) - - self.assertEqual( - setxml.find("./numatune/memnode/[@cellid='1']").attrib.get("mode"), - "preferred", - ) - - self.assertEqual( - setxml.find("./numatune/memnode/[@cellid='1']").attrib.get("nodeset"), "2" - ) - - # Update memory case - setmem_mock = MagicMock(return_value=0) - domain_mock.setMemoryFlags = setmem_mock - - self.assertEqual( - { - "definition": True, - "mem": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=2048), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("memory").text, str(2048 * 1024 ** 2)) - self.assertEqual(setxml.find("memory").get("unit"), "bytes") - self.assertEqual(setmem_mock.call_args[0][0], 2048 * 1024) - - mem_dict = {"boot": "0.5g", "current": "2g", "max": "1g", "slots": 12} - self.assertEqual( - { - "definition": True, - "mem": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=mem_dict), - ) - - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("memory").get("unit"), "bytes") - self.assertEqual(setxml.find("memory").text, str(int(0.5 * 1024 ** 3))) - self.assertEqual(setxml.find("maxMemory").text, str(1 * 1024 ** 3)) - self.assertEqual(setxml.find("currentMemory").text, str(2 * 1024 ** 3)) - - max_slot_reverse = { - "slots": "10", - "max": "3096m", - } - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=max_slot_reverse), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("maxMemory").text, str(3096 * 1024 ** 2)) - self.assertEqual(setxml.find("maxMemory").attrib.get("slots"), "10") - - # update memory backing case - mem_back = { - "hugepages": [ - {"nodeset": "1-5,^4", "size": "1g"}, - {"nodeset": "4", "size": "2g"}, - ], - "nosharepages": True, - "locked": True, - "source": "file", - "access": "shared", - "allocation": "immediate", - "discard": True, - } - - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=mem_back), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertDictEqual( - { - p.get("nodeset"): {"size": p.get("size"), "unit": p.get("unit")} - for p in setxml.findall("memoryBacking/hugepages/page") - }, - { - "1,2,3,5": {"size": str(1024 ** 3), "unit": "bytes"}, - "4": {"size": str(2 * 1024 ** 3), "unit": "bytes"}, - }, - ) - self.assertNotEqual(setxml.find("./memoryBacking/nosharepages"), None) - self.assertIsNone(setxml.find("./memoryBacking/nosharepages").text) - self.assertEqual([], setxml.find("./memoryBacking/nosharepages").keys()) - self.assertNotEqual(setxml.find("./memoryBacking/locked"), None) - self.assertIsNone(setxml.find("./memoryBacking/locked").text) - self.assertEqual([], setxml.find("./memoryBacking/locked").keys()) - self.assertEqual(setxml.find("./memoryBacking/source").attrib["type"], "file") - self.assertEqual(setxml.find("./memoryBacking/access").attrib["mode"], "shared") - self.assertNotEqual(setxml.find("./memoryBacking/discard"), None) - - # test adding iothreads - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu={"iothreads": 5}), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("iothreads").text, "5") - - # test adding cpu tune parameters - cputune = { - "shares": 2048, - "period": 122000, - "quota": -1, - "global_period": 1000000, - "global_quota": -3, - "emulator_period": 1200000, - "emulator_quota": -10, - "iothread_period": 133000, - "iothread_quota": -1, - "vcpupin": {0: "1-4,^2", 1: "0,1", 2: "2,3", 3: "0,4"}, - "emulatorpin": "1-3", - "iothreadpin": {1: "5-6", 2: "7-8"}, - "vcpusched": [ - {"scheduler": "fifo", "priority": 1, "vcpus": "0"}, - {"scheduler": "fifo", "priotity": 2, "vcpus": "1"}, - {"scheduler": "idle", "priotity": 3, "vcpus": "2"}, - ], - "iothreadsched": [{"scheduler": "batch", "iothreads": "7"}], - "cachetune": { - "0-3": { - 0: {"level": 3, "type": "both", "size": 3}, - 1: {"level": 3, "type": "both", "size": 3}, - "monitor": {1: 3, "0-3": 3}, - }, - "4-5": {"monitor": {4: 3, 5: 2}}, - }, - "memorytune": {"0-2": {0: 60}, "3-4": {0: 50, 1: 70}}, - } - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu={"tuning": cputune}), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("cputune").find("shares").text, "2048") - self.assertEqual(setxml.find("cputune").find("period").text, "122000") - self.assertEqual(setxml.find("cputune").find("quota").text, "-1") - self.assertEqual(setxml.find("cputune").find("global_period").text, "1000000") - self.assertEqual(setxml.find("cputune").find("global_quota").text, "-3") - self.assertEqual(setxml.find("cputune").find("emulator_period").text, "1200000") - self.assertEqual(setxml.find("cputune").find("emulator_quota").text, "-10") - self.assertEqual(setxml.find("cputune").find("iothread_period").text, "133000") - self.assertEqual(setxml.find("cputune").find("iothread_quota").text, "-1") - self.assertEqual( - setxml.find("cputune").find("vcpupin[@vcpu='0']").attrib.get("cpuset"), - "1,3,4", - ) - self.assertEqual( - setxml.find("cputune").find("vcpupin[@vcpu='1']").attrib.get("cpuset"), - "0,1", - ) - self.assertEqual( - setxml.find("cputune").find("vcpupin[@vcpu='2']").attrib.get("cpuset"), - "2,3", - ) - self.assertEqual( - setxml.find("cputune").find("vcpupin[@vcpu='3']").attrib.get("cpuset"), - "0,4", - ) - self.assertEqual( - setxml.find("cputune").find("emulatorpin").attrib.get("cpuset"), "1,2,3" - ) - self.assertEqual( - setxml.find("cputune") - .find("iothreadpin[@iothread='1']") - .attrib.get("cpuset"), - "5,6", - ) - self.assertEqual( - setxml.find("cputune") - .find("iothreadpin[@iothread='2']") - .attrib.get("cpuset"), - "7,8", - ) - self.assertEqual( - setxml.find("cputune").find("vcpusched[@vcpus='0']").attrib.get("priority"), - "1", - ) - self.assertEqual( - setxml.find("cputune") - .find("vcpusched[@vcpus='0']") - .attrib.get("scheduler"), - "fifo", - ) - self.assertEqual( - setxml.find("cputune").find("iothreadsched").attrib.get("iothreads"), "7" - ) - self.assertEqual( - setxml.find("cputune").find("iothreadsched").attrib.get("scheduler"), - "batch", - ) - self.assertIsNotNone(setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']")) - self.assertEqual( - setxml.find( - "./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']" - ).attrib.get("level"), - "3", - ) - self.assertEqual( - setxml.find( - "./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']" - ).attrib.get("type"), - "both", - ) - self.assertEqual( - setxml.find( - "./cputune/cachetune[@vcpus='0,1,2,3']/monitor[@vcpus='1']" - ).attrib.get("level"), - "3", - ) - self.assertNotEqual( - setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']/monitor[@vcpus='1']"), - None, - ) - self.assertNotEqual( - setxml.find("./cputune/cachetune[@vcpus='4,5']").attrib.get("vcpus"), None - ) - self.assertEqual( - setxml.find("./cputune/cachetune[@vcpus='4,5']/cache[@id='0']"), None - ) - self.assertEqual( - setxml.find( - "./cputune/cachetune[@vcpus='4,5']/monitor[@vcpus='4']" - ).attrib.get("level"), - "3", - ) - self.assertEqual( - setxml.find( - "./cputune/cachetune[@vcpus='4,5']/monitor[@vcpus='5']" - ).attrib.get("level"), - "2", - ) - self.assertNotEqual(setxml.find("./cputune/memorytune[@vcpus='0,1,2']"), None) - self.assertEqual( - setxml.find( - "./cputune/memorytune[@vcpus='0,1,2']/node[@id='0']" - ).attrib.get("bandwidth"), - "60", - ) - self.assertNotEqual(setxml.find("./cputune/memorytune[@vcpus='3,4']"), None) - self.assertEqual( - setxml.find("./cputune/memorytune[@vcpus='3,4']/node[@id='0']").attrib.get( - "bandwidth" - ), - "50", - ) - self.assertEqual( - setxml.find("./cputune/memorytune[@vcpus='3,4']/node[@id='1']").attrib.get( - "bandwidth" - ), - "70", - ) - - # Update disks case - devattach_mock = MagicMock(return_value=0) - devdetach_mock = MagicMock(return_value=0) - domain_mock.attachDevice = devattach_mock - domain_mock.detachDevice = devdetach_mock - mock_chmod = MagicMock() - mock_run = MagicMock() - with patch.dict( - os.__dict__, {"chmod": mock_chmod, "makedirs": MagicMock()} - ): # pylint: disable=no-member - with patch.dict( - virt.__salt__, {"cmd.run": mock_run} - ): # pylint: disable=no-member - ret = virt.update( - "my_vm", - disk_profile="default", - disks=[ - { - "name": "cddrive", - "device": "cdrom", - "source_file": None, - "model": "ide", - }, - {"name": "added", "size": 2048, "iothreads": True}, - ], - ) - added_disk_path = os.path.join( - virt.__salt__["config.get"]("virt:images"), "my_vm_added.qcow2" - ) # pylint: disable=no-member - self.assertEqual( - mock_run.call_args[0][0], - 'qemu-img create -f qcow2 "{}" 2048M'.format(added_disk_path), - ) - self.assertEqual(mock_chmod.call_args[0][0], added_disk_path) - self.assertListEqual( - [None, os.path.join(root_dir, "my_vm_added.qcow2")], - [ - ET.fromstring(disk).find("source").get("file") - if str(disk).find("<source") > -1 - else None - for disk in ret["disk"]["attached"] - ], - ) - - self.assertListEqual( - ["my_vm_data", "libvirt-pool/my_vm_data2"], - [ - ET.fromstring(disk).find("source").get("volume") - or ET.fromstring(disk).find("source").get("name") - for disk in ret["disk"]["detached"] - ], - ) - self.assertEqual(devattach_mock.call_count, 2) - self.assertEqual(devdetach_mock.call_count, 2) - - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual( - "threads", setxml.find("devices/disk[3]/driver").get("io") - ) - - # Update nics case - yaml_config = """ - virt: - nic: - myprofile: - - network: default - name: eth0 - """ - mock_config = salt.utils.yaml.safe_load(yaml_config) - devattach_mock.reset_mock() - devdetach_mock.reset_mock() - with patch.dict( - salt.modules.config.__opts__, mock_config # pylint: disable=no-member - ): - ret = virt.update( - "my_vm", - nic_profile="myprofile", - interfaces=[ - { - "name": "eth0", - "type": "network", - "source": "default", - "mac": "52:54:00:39:02:b1", - }, - {"name": "eth1", "type": "network", "source": "newnet"}, - ], - ) - self.assertEqual( - ["newnet"], - [ - ET.fromstring(nic).find("source").get("network") - for nic in ret["interface"]["attached"] - ], - ) - self.assertEqual( - ["oldnet"], - [ - ET.fromstring(nic).find("source").get("network") - for nic in ret["interface"]["detached"] - ], - ) - devattach_mock.assert_called_once() - devdetach_mock.assert_called_once() - - # Remove nics case - devattach_mock.reset_mock() - devdetach_mock.reset_mock() - ret = virt.update("my_vm", nic_profile=None, interfaces=[]) - self.assertEqual([], ret["interface"]["attached"]) - self.assertEqual(2, len(ret["interface"]["detached"])) - devattach_mock.assert_not_called() - devdetach_mock.assert_called() - - # Remove disks case (yeah, it surely is silly) - devattach_mock.reset_mock() - devdetach_mock.reset_mock() - ret = virt.update("my_vm", disk_profile=None, disks=[]) - self.assertEqual([], ret["disk"]["attached"]) - self.assertEqual(3, len(ret["disk"]["detached"])) - devattach_mock.assert_not_called() - devdetach_mock.assert_called() - - # Graphics change test case - self.assertEqual( - { - "definition": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", graphics={"type": "vnc"}), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual("vnc", setxml.find("devices/graphics").get("type")) - - # Serial and console test case - self.assertEqual( - { - "definition": False, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", serials=[{"type": "tcp"}], consoles=[{"type": "tcp"}]), - ) - setxml = ET.fromstring(define_mock.call_args[0][0]) - self.assertEqual(setxml.find("devices/serial").attrib["type"], "pty") - self.assertEqual(setxml.find("devices/console").attrib["type"], "pty") - - # Update with no diff case - pool_mock = MagicMock() - default_pool_desc = "<pool type='dir'></pool>" - rbd_pool_desc = """ - <pool type='rbd'> - <name>test-rbd</name> - <source> - <host name='ses2.tf.local'/> - <host name='ses3.tf.local' port='1234'/> - <name>libvirt-pool</name> - <auth type='ceph' username='libvirt'> - <secret usage='pool_test-rbd'/> - </auth> - </source> - </pool> - """ - pool_mock.XMLDesc.side_effect = [ - default_pool_desc, - rbd_pool_desc, - default_pool_desc, - rbd_pool_desc, - ] - self.mock_conn.storagePoolLookupByName.return_value = pool_mock - self.mock_conn.listStoragePools.return_value = ["test-rbd", "default"] - self.assertEqual( - { - "definition": False, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update( - "my_vm", - cpu=1, - mem=1024, - disk_profile="default", - disks=[ - {"name": "data", "size": 2048, "pool": "default"}, - { - "name": "data2", - "size": 4096, - "pool": "test-rbd", - "format": "raw", - }, - ], - nic_profile="myprofile", - interfaces=[ - { - "name": "eth0", - "type": "network", - "source": "default", - "mac": "52:54:00:39:02:b1", - }, - {"name": "eth1", "type": "network", "source": "oldnet"}, - ], - graphics={ - "type": "spice", - "listen": {"type": "address", "address": "127.0.0.1"}, - }, - ), - ) - - # Failed XML description update case - self.mock_conn.defineXML.side_effect = self.mock_libvirt.libvirtError( - "Test error" - ) - setmem_mock.reset_mock() - with self.assertRaises(self.mock_libvirt.libvirtError): - virt.update("my_vm", mem=2048) - - # Failed single update failure case - self.mock_conn.defineXML = MagicMock(return_value=True) - setmem_mock.side_effect = self.mock_libvirt.libvirtError( - "Failed to live change memory" - ) - self.assertEqual( - { - "definition": True, - "errors": ["Failed to live change memory"], - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", mem=2048), - ) - - # Failed multiple updates failure case - self.assertEqual( - { - "definition": True, - "errors": ["Failed to live change memory"], - "cpu": True, - "disk": {"attached": [], "detached": [], "updated": []}, - "interface": {"attached": [], "detached": []}, - }, - virt.update("my_vm", cpu=4, mem=2048), - ) - def test_update_backing_store(self): """ Test updating a disk with a backing store -- 2.30.0
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor