From c0058217c79a022e756a78ad88c82905d33cb4822f825ac24e224b54c67ca0ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Fri, 12 Feb 2021 09:49:47 +0000 Subject: [PATCH] osc copypac from project:systemsmanagement:saltstack:testing package:salt revision:382 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=184 --- ...icevmc-dns-srv-records-backports-314.patch | 1334 +++++++++ _lastrevision | 2 +- ...n-unexpected-cmd-output-at-listing-p.patch | 130 + ...s-when-multiple-conditions-bsc-11808.patch | 287 ++ open-suse-3002.2-xen-grub-316.patch | 211 ++ salt.changes | 30 + salt.spec | 16 + virt-uefi-fix-backport-312.patch | 2449 +++++++++++++++++ 8 files changed, 4458 insertions(+), 1 deletion(-) create mode 100644 3002.2-xen-spicevmc-dns-srv-records-backports-314.patch create mode 100644 do-not-crash-when-unexpected-cmd-output-at-listing-p.patch create mode 100644 fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch create mode 100644 open-suse-3002.2-xen-grub-316.patch create mode 100644 virt-uefi-fix-backport-312.patch diff --git a/3002.2-xen-spicevmc-dns-srv-records-backports-314.patch b/3002.2-xen-spicevmc-dns-srv-records-backports-314.patch new file mode 100644 index 0000000..a27a4a2 --- /dev/null +++ b/3002.2-xen-spicevmc-dns-srv-records-backports-314.patch @@ -0,0 +1,1334 @@ +From 0b90e8db82991daf19dd76ebccd651ffcffdecb1 Mon Sep 17 00:00:00 2001 +From: Cedric Bosdonnat +Date: Mon, 8 Feb 2021 16:42:47 +0100 +Subject: [PATCH] 3002.2 Xen spicevmc, DNS SRV records backports (#314) + +* Fix virtual network generated DNS XML for SRV records + +libvirt network's srv element doesn't take a `name` property but a +`service` one. + +* Add missing property in virt.network_define dns srv doc + +* virt: convert spice generation tests to pytests + +* virt: don't add spicevmc channel to Xen VMs + +Xen fully virtualized VMs with spicevmc channel fail to start, so better +not write it out in such cases. + +* virt: inverse the remaining asserts in pytests +--- + changelog/59416.fixed | 1 + + salt/modules/virt.py | 2 +- + salt/templates/virt/libvirt_domain.jinja | 2 +- + salt/templates/virt/libvirt_network.jinja | 2 +- + .../pytests/unit/modules/virt/test_domain.py | 176 ++++++++++++------ + .../pytests/unit/modules/virt/test_helpers.py | 6 +- + tests/pytests/unit/modules/virt/test_host.py | 4 +- + .../pytests/unit/modules/virt/test_network.py | 61 +++--- + tests/pytests/unit/states/virt/test_domain.py | 144 +++++++------- + .../pytests/unit/states/virt/test_network.py | 88 ++++----- + tests/unit/modules/test_virt.py | 59 ------ + 11 files changed, 281 insertions(+), 264 deletions(-) + create mode 100644 changelog/59416.fixed + +diff --git a/changelog/59416.fixed b/changelog/59416.fixed +new file mode 100644 +index 0000000000..820124e99a +--- /dev/null ++++ b/changelog/59416.fixed +@@ -0,0 +1 @@ ++Don't create spicevmc channel for Xen virtual machines +diff --git a/salt/modules/virt.py b/salt/modules/virt.py +index 7da35445a3..da132630dd 100644 +--- a/salt/modules/virt.py ++++ b/salt/modules/virt.py +@@ -7108,7 +7108,7 @@ def network_define( + srvs: + List of SRV DNS entries. + Each entry is a dictionary with the mandatory ``name`` and ``protocol`` keys. +- Entries can also have ``target``, ``port``, ``priority`` and ``weight`` optional properties. ++ Entries can also have ``target``, ``port``, ``priority``, ``domain`` and ``weight`` optional properties. + + CLI Example: + +diff --git a/salt/templates/virt/libvirt_domain.jinja b/salt/templates/virt/libvirt_domain.jinja +index 4603dfd8de..6772b0db56 100644 +--- a/salt/templates/virt/libvirt_domain.jinja ++++ b/salt/templates/virt/libvirt_domain.jinja +@@ -285,7 +285,7 @@ + autoport='{{ yesno(not graphics.port and not graphics.tls_port) }}'> + + +- {%- if graphics.type == "spice" %} ++ {%- if graphics.type == "spice" and hypervisor in ["qemu", "kvm"] %} + + + +diff --git a/salt/templates/virt/libvirt_network.jinja b/salt/templates/virt/libvirt_network.jinja +index ab14408712..98bd6567b8 100644 +--- a/salt/templates/virt/libvirt_network.jinja ++++ b/salt/templates/virt/libvirt_network.jinja +@@ -65,7 +65,7 @@ + + {%- endfor %} + {%- for srv in dns.srvs %} +- + """ + ) +- assert expected_xml == strip_xml( +- ET.tostring(setxml.find("./devices/hostdev")) ++ assert ( ++ strip_xml(ET.tostring(setxml.find("./devices/hostdev"))) == expected_xml + ) + + +@@ -684,8 +689,8 @@ def test_init_hostdev_pci(make_capabilities, make_mock_device): + + """ + ) +- assert expected_xml == strip_xml( +- ET.tostring(setxml.find("./devices/hostdev")) ++ assert ( ++ strip_xml(ET.tostring(setxml.find("./devices/hostdev"))) == expected_xml + ) + + +@@ -858,11 +863,11 @@ def test_update_hostdev_changes(running, live, make_mock_device, make_mock_vm, t + ET.tostring(xmlutil.strip_spaces(node)) + for node in set_xml.findall("./devices/hostdev") + ] +- assert [usb_device_xml] == actual_hostdevs ++ assert actual_hostdevs == [usb_device_xml] + + if not test and live: + attach_xml = strip_xml(domain_mock.attachDevice.call_args[0][0]) +- assert usb_device_xml == attach_xml ++ assert attach_xml == usb_device_xml + + pci_device_xml = strip_xml( + """ +@@ -875,7 +880,7 @@ def test_update_hostdev_changes(running, live, make_mock_device, make_mock_vm, t + """ + ) + detach_xml = strip_xml(domain_mock.detachDevice.call_args[0][0]) +- assert pci_device_xml == detach_xml ++ assert detach_xml == pci_device_xml + else: + domain_mock.attachDevice.assert_not_called() + domain_mock.detachDevice.assert_not_called() +@@ -932,14 +937,16 @@ def test_diff_nics(): + """ + ).findall("interface") + ret = virt._diff_interface_lists(old_nics, new_nics) +- assert ["52:54:00:39:02:b1"] == [ +- nic.find("mac").get("address") for nic in ret["unchanged"] ++ assert [nic.find("mac").get("address") for nic in ret["unchanged"]] == [ ++ "52:54:00:39:02:b1" + ] +- assert ["52:54:00:39:02:b2", "52:54:00:39:02:b4"] == [ +- nic.find("mac").get("address") for nic in ret["new"] ++ assert [nic.find("mac").get("address") for nic in ret["new"]] == [ ++ "52:54:00:39:02:b2", ++ "52:54:00:39:02:b4", + ] +- assert ["52:54:00:39:02:b2", "52:54:00:39:02:b3"] == [ +- nic.find("mac").get("address") for nic in ret["deleted"] ++ assert [nic.find("mac").get("address") for nic in ret["deleted"]] == [ ++ "52:54:00:39:02:b2", ++ "52:54:00:39:02:b3", + ] + + +@@ -986,8 +993,9 @@ def test_diff_nics_live_nochange(): + """ + ) + ret = virt._diff_interface_lists(old_nics, new_nics) +- assert ["52:54:00:03:02:15", "52:54:00:ea:2e:89"] == [ +- nic.find("mac").get("address") for nic in ret["unchanged"] ++ assert [nic.find("mac").get("address") for nic in ret["unchanged"]] == [ ++ "52:54:00:03:02:15", ++ "52:54:00:ea:2e:89", + ] + + +@@ -1270,7 +1278,7 @@ def test_update_bootdev_unchanged(make_mock_vm, boot_dev): + """ + ) + ret = virt.update("my_vm", boot_dev=boot_dev) +- assert (boot_dev != "hd") == ret["definition"] ++ assert ret["definition"] == (boot_dev != "hd") + if boot_dev == "hd": + virt.libvirt.openAuth().defineXML.assert_not_called() + else: +@@ -2048,3 +2056,65 @@ def test_update_failure(make_mock_vm): + "disk": {"attached": [], "detached": [], "updated": []}, + "interface": {"attached": [], "detached": []}, + } ++ ++@pytest.mark.parametrize("hypervisor", ["kvm", "xen"]) ++def test_gen_xml_spice_default(hypervisor): ++ """ ++ Test virt._gen_xml() with default spice graphics device ++ """ ++ xml_data = virt._gen_xml( ++ virt.libvirt.openAuth.return_value, ++ "hello", ++ 1, ++ 512, ++ {}, ++ {}, ++ hypervisor, ++ "hvm", ++ "x86_64", ++ graphics={"type": "spice"}, ++ ) ++ root = ET.fromstring(xml_data) ++ assert root.find("devices/graphics").attrib["type"] == "spice" ++ assert root.find("devices/graphics").attrib["autoport"] == "yes" ++ assert root.find("devices/graphics").attrib["listen"] == "0.0.0.0" ++ assert root.find("devices/graphics/listen").attrib["type"] == "address" ++ assert root.find("devices/graphics/listen").attrib["address"] == "0.0.0.0" ++ if hypervisor == "kvm": ++ assert ( ++ root.find(".//channel[@type='spicevmc']/target").get("name") ++ == "com.redhat.spice.0" ++ ) ++ else: ++ assert root.find(".//channel[@type='spicevmc']") is None ++ ++ ++def test_gen_xml_spice(): ++ """ ++ Test virt._gen_xml() with spice graphics device ++ """ ++ xml_data = virt._gen_xml( ++ virt.libvirt.openAuth.return_value, ++ "hello", ++ 1, ++ 512, ++ {}, ++ {}, ++ "kvm", ++ "hvm", ++ "x86_64", ++ graphics={ ++ "type": "spice", ++ "port": 1234, ++ "tls_port": 5678, ++ "listen": {"type": "none"}, ++ }, ++ ) ++ root = ET.fromstring(xml_data) ++ assert root.find("devices/graphics").attrib["type"] == "spice" ++ assert root.find("devices/graphics").attrib["autoport"] == "no" ++ assert root.find("devices/graphics").attrib["port"] == "1234" ++ assert root.find("devices/graphics").attrib["tlsPort"] == "5678" ++ assert "listen" not in root.find("devices/graphics").attrib ++ assert root.find("devices/graphics/listen").attrib["type"] == "none" ++ assert "address" not in root.find("devices/graphics/listen").attrib +diff --git a/tests/pytests/unit/modules/virt/test_helpers.py b/tests/pytests/unit/modules/virt/test_helpers.py +index 70f5a8a31f..30c4a66b7d 100644 +--- a/tests/pytests/unit/modules/virt/test_helpers.py ++++ b/tests/pytests/unit/modules/virt/test_helpers.py +@@ -12,12 +12,12 @@ def append_to_XMLDesc(mocked, fragment): + mocked.XMLDesc.return_value = ET.tostring(xml_doc) + + +-def assert_xml_equals(expected, actual): ++def assert_xml_equals(actual, expected): + """ + Assert that two ElementTree nodes are equal + """ +- assert xmlutil.to_dict(xmlutil.strip_spaces(expected), True) == xmlutil.to_dict( +- xmlutil.strip_spaces(actual), True ++ assert xmlutil.to_dict(xmlutil.strip_spaces(actual), True) == xmlutil.to_dict( ++ xmlutil.strip_spaces(expected), True + ) + + +diff --git a/tests/pytests/unit/modules/virt/test_host.py b/tests/pytests/unit/modules/virt/test_host.py +index 555deb23bb..6c9ac79337 100644 +--- a/tests/pytests/unit/modules/virt/test_host.py ++++ b/tests/pytests/unit/modules/virt/test_host.py +@@ -173,7 +173,7 @@ def test_node_devices(make_mock_device): + ] + virt.libvirt.openAuth().listAllDevices.return_value = mock_devs + +- assert [ ++ assert virt.node_devices() == [ + { + "name": "pci_1002_71c4", + "caps": "pci", +@@ -216,4 +216,4 @@ def test_node_devices(make_mock_device): + "state": "down", + "device name": "pci_0000_02_10_7", + }, +- ] == virt.node_devices() ++ ] +diff --git a/tests/pytests/unit/modules/virt/test_network.py b/tests/pytests/unit/modules/virt/test_network.py +index e7e544c580..52aadc9519 100644 +--- a/tests/pytests/unit/modules/virt/test_network.py ++++ b/tests/pytests/unit/modules/virt/test_network.py +@@ -18,10 +18,10 @@ def test_gen_xml(): + """ + xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch") + root = ET.fromstring(xml_data) +- assert "network" == root.find("name").text +- assert "main" == root.find("bridge").attrib["name"] +- assert "bridge" == root.find("forward").attrib["mode"] +- assert "openvswitch" == root.find("virtualport").attrib["type"] ++ assert root.find("name").text == "network" ++ assert root.find("bridge").attrib["name"] == "main" ++ assert root.find("forward").attrib["mode"] == "bridge" ++ assert root.find("virtualport").attrib["type"] == "openvswitch" + + + def test_gen_xml_nat(): +@@ -68,9 +68,9 @@ def test_gen_xml_nat(): + mtu=9000, + ) + root = ET.fromstring(xml_data) +- assert "network" == root.find("name").text +- assert "main" == root.find("bridge").attrib["name"] +- assert "nat" == root.find("forward").attrib["mode"] ++ assert root.find("name").text == "network" ++ assert root.find("bridge").attrib["name"] == "main" ++ assert root.find("forward").attrib["mode"] == "nat" + expected_ipv4 = ET.fromstring( + """ + +@@ -84,7 +84,7 @@ def test_gen_xml_nat(): + + """ + ) +- assert_xml_equals(expected_ipv4, root.find("./ip[@address='192.168.2.1']")) ++ assert_xml_equals(root.find("./ip[@address='192.168.2.1']"), expected_ipv4) + + expected_ipv6 = ET.fromstring( + """ +@@ -96,7 +96,7 @@ def test_gen_xml_nat(): + + """ + ) +- assert_xml_equals(expected_ipv6, root.find("./ip[@address='2001:db8:ca2:2::1']")) ++ assert_xml_equals(root.find("./ip[@address='2001:db8:ca2:2::1']"), expected_ipv6) + + actual_nat = ET.tostring(xmlutil.strip_spaces(root.find("./forward/nat"))) + expected_nat = strip_xml( +@@ -107,10 +107,10 @@ def test_gen_xml_nat(): + + """ + ) +- assert expected_nat == actual_nat ++ assert actual_nat == expected_nat + +- assert {"name": "acme.lab", "localOnly": "yes"} == root.find("./domain").attrib +- assert "9000" == root.find("mtu").get("size") ++ assert root.find("./domain").attrib == {"name": "acme.lab", "localOnly": "yes"} ++ assert root.find("mtu").get("size") == "9000" + + + def test_gen_xml_dns(): +@@ -166,12 +166,12 @@ def test_gen_xml_dns(): + mirror.acme.lab + test.acme.lab + +- +- ++ ++ + + """ + ) +- assert_xml_equals(expected_xml, root.find("./dns")) ++ assert_xml_equals(root.find("./dns"), expected_xml) + + + def test_gen_xml_isolated(): +@@ -190,9 +190,11 @@ def test_gen_xml_passthrough_interfaces(): + "network", "virbr0", "passthrough", None, interfaces="eth10 eth11 eth12", + ) + root = ET.fromstring(xml_data) +- assert "passthrough" == root.find("forward").get("mode") +- assert ["eth10", "eth11", "eth12"] == [ +- n.get("dev") for n in root.findall("forward/interface") ++ assert root.find("forward").get("mode") == "passthrough" ++ assert [n.get("dev") for n in root.findall("forward/interface")] == [ ++ "eth10", ++ "eth11", ++ "eth12", + ] + + +@@ -212,7 +214,7 @@ def test_gen_xml_hostdev_addresses(): + + """ + ) +- assert_xml_equals(expected_forward, root.find("./forward")) ++ assert_xml_equals(root.find("./forward"), expected_forward) + + + def test_gen_xml_hostdev_pf(): +@@ -231,7 +233,7 @@ def test_gen_xml_hostdev_pf(): + """ + ) + actual_forward = ET.tostring(xmlutil.strip_spaces(root.find("./forward"))) +- assert expected_forward == actual_forward ++ assert actual_forward == expected_forward + + + def test_gen_xml_openvswitch(): +@@ -267,7 +269,7 @@ def test_gen_xml_openvswitch(): + + """ + ) +- assert_xml_equals(expected_xml, ET.fromstring(xml_data)) ++ assert_xml_equals(ET.fromstring(xml_data), expected_xml) + + + @pytest.mark.parametrize( +@@ -307,7 +309,7 @@ def test_define(make_mock_network, autostart, start): + """ + ) + define_mock = virt.libvirt.openAuth().networkDefineXML +- assert expected_xml == strip_xml(define_mock.call_args[0][0]) ++ assert strip_xml(define_mock.call_args[0][0]) == expected_xml + + if autostart: + mock_network.setAutostart.assert_called_with(1) +@@ -416,7 +418,7 @@ def test_update_nat_change(make_mock_network, test): + + """ + ) +- assert expected_xml == strip_xml(define_mock.call_args[0][0]) ++ assert strip_xml(define_mock.call_args[0][0]) == expected_xml + + + @pytest.mark.parametrize("change", [True, False], ids=["changed", "unchanged"]) +@@ -437,11 +439,14 @@ def test_update_hostdev_pf(make_mock_network, change): + + """ + ) +- assert change == virt.network_update( +- "test-hostdev", +- None, +- "hostdev", +- physical_function="eth0" if not change else "eth1", ++ assert ( ++ virt.network_update( ++ "test-hostdev", ++ None, ++ "hostdev", ++ physical_function="eth0" if not change else "eth1", ++ ) ++ == change + ) + define_mock = virt.libvirt.openAuth().networkDefineXML + if change: +diff --git a/tests/pytests/unit/states/virt/test_domain.py b/tests/pytests/unit/states/virt/test_domain.py +index a4ae8c0694..c705785bf5 100644 +--- a/tests/pytests/unit/states/virt/test_domain.py ++++ b/tests/pytests/unit/states/virt/test_domain.py +@@ -21,14 +21,14 @@ def test_defined_no_change(test): + "virt.init": init_mock, + }, + ): +- assert { ++ assert virt.defined("myvm") == { + "name": "myvm", + "changes": {"myvm": {"definition": False}}, + "result": True, + "comment": "Domain myvm unchanged", +- } == virt.defined("myvm") ++ } + init_mock.assert_not_called() +- assert [domain_update_call("myvm", test=test)] == update_mock.call_args_list ++ assert update_mock.call_args_list == [domain_update_call("myvm", test=test)] + + + def test_defined_new_with_connection(test): +@@ -72,12 +72,7 @@ def test_defined_new_with_connection(test): + {"type": "tcp", "port": 22223, "protocol": "telnet"}, + {"type": "pty"}, + ] +- assert { +- "name": "myvm", +- "result": True if not test else None, +- "changes": {"myvm": {"definition": True}}, +- "comment": "Domain myvm defined", +- } == virt.defined( ++ assert virt.defined( + "myvm", + cpu=2, + mem=2048, +@@ -103,7 +98,12 @@ def test_defined_new_with_connection(test): + serials=serials, + consoles=consoles, + host_devices=["pci_0000_00_17_0"], +- ) ++ ) == { ++ "name": "myvm", ++ "result": True if not test else None, ++ "changes": {"myvm": {"definition": True}}, ++ "comment": "Domain myvm defined", ++ } + if not test: + init_mock.assert_called_with( + "myvm", +@@ -160,16 +160,16 @@ def test_defined_update(test): + "initrd": "/root/f8-i386-initrd", + "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/", + } +- assert { ++ assert virt.defined("myvm", cpu=2, boot=boot,) == { + "name": "myvm", + "changes": {"myvm": {"definition": True, "cpu": True}}, + "result": True if not test else None, + "comment": "Domain myvm updated", +- } == virt.defined("myvm", cpu=2, boot=boot,) ++ } + init_mock.assert_not_called() +- assert [ ++ assert update_mock.call_args_list == [ + domain_update_call("myvm", cpu=2, test=test, boot=boot) +- ] == update_mock.call_args_list ++ ] + + + def test_defined_update_error(test): +@@ -189,7 +189,7 @@ def test_defined_update_error(test): + "virt.init": init_mock, + }, + ): +- assert { ++ assert virt.defined("myvm", cpu=2, boot_dev="cdrom hd") == { + "name": "myvm", + "changes": { + "myvm": { +@@ -200,7 +200,7 @@ def test_defined_update_error(test): + }, + "result": True if not test else None, + "comment": "Domain myvm updated with live update(s) failures", +- } == virt.defined("myvm", cpu=2, boot_dev="cdrom hd") ++ } + init_mock.assert_not_called() + update_mock.assert_called_with( + "myvm", +@@ -245,16 +245,16 @@ def test_defined_update_definition_error(test): + "virt.init": init_mock, + }, + ): +- assert { ++ assert virt.defined("myvm", cpu=2) == { + "name": "myvm", + "changes": {}, + "result": False, + "comment": "error message", +- } == virt.defined("myvm", cpu=2) ++ } + init_mock.assert_not_called() +- assert [ ++ assert update_mock.call_args_list == [ + domain_update_call("myvm", cpu=2, test=test) +- ] == update_mock.call_args_list ++ ] + + + @pytest.mark.parametrize("running", ["running", "shutdown"]) +@@ -279,12 +279,12 @@ def test_running_no_change(test, running): + if running == "shutdown": + changes["started"] = True + comment = "Domain myvm started" +- assert { ++ assert virt.running("myvm") == { + "name": "myvm", + "result": True, + "changes": {"myvm": changes}, + "comment": comment, +- } == virt.running("myvm") ++ } + if running == "shutdown" and not test: + start_mock.assert_called() + else: +@@ -326,12 +326,7 @@ def test_running_define(test): + "listen": {"type": "address", "address": "192.168.0.1"}, + } + +- assert { +- "name": "myvm", +- "result": True if not test else None, +- "changes": {"myvm": {"definition": True, "started": True}}, +- "comment": "Domain myvm defined and started", +- } == virt.running( ++ assert virt.running( + "myvm", + cpu=2, + mem=2048, +@@ -353,7 +348,12 @@ def test_running_define(test): + connection="someconnection", + username="libvirtuser", + password="supersecret", +- ) ++ ) == { ++ "name": "myvm", ++ "result": True if not test else None, ++ "changes": {"myvm": {"definition": True, "started": True}}, ++ "comment": "Domain myvm defined and started", ++ } + if not test: + init_mock.assert_called_with( + "myvm", +@@ -412,12 +412,12 @@ def test_running_start_error(): + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): +- assert { ++ assert virt.running("myvm") == { + "name": "myvm", + "changes": {"myvm": {"definition": False}}, + "result": False, + "comment": "libvirt error msg", +- } == virt.running("myvm") ++ } + + + @pytest.mark.parametrize("running", ["running", "shutdown"]) +@@ -441,14 +441,14 @@ def test_running_update(test, running): + changes = {"myvm": {"definition": True, "cpu": True}} + if running == "shutdown": + changes["myvm"]["started"] = True +- assert { ++ assert virt.running("myvm", cpu=2) == { + "name": "myvm", + "changes": changes, + "result": True if not test else None, + "comment": "Domain myvm updated" + if running == "running" + else "Domain myvm updated and started", +- } == virt.running("myvm", cpu=2) ++ } + if running == "shutdown" and not test: + start_mock.assert_called() + else: +@@ -470,12 +470,12 @@ def test_running_definition_error(): + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): +- assert { ++ assert virt.running("myvm", cpu=3) == { + "name": "myvm", + "changes": {}, + "result": False, + "comment": "error message", +- } == virt.running("myvm", cpu=3) ++ } + + + def test_running_update_error(): +@@ -494,7 +494,7 @@ def test_running_update_error(): + "virt.list_domains": MagicMock(return_value=["myvm"]), + }, + ): +- assert { ++ assert virt.running("myvm", cpu=2) == { + "name": "myvm", + "changes": { + "myvm": { +@@ -505,7 +505,7 @@ def test_running_update_error(): + }, + "result": True, + "comment": "Domain myvm updated with live update(s) failures", +- } == virt.running("myvm", cpu=2) ++ } + update_mock.assert_called_with( + "myvm", + cpu=2, +@@ -552,14 +552,14 @@ def test_stopped(test, running): + if running == "running": + changes = {"stopped": [{"domain": "myvm", "shutdown": True}]} + comment = "Machine has been shut down" +- assert { ++ assert virt.stopped( ++ "myvm", connection="myconnection", username="user", password="secret", ++ ) == { + "name": "myvm", + "changes": changes, + "comment": comment, + "result": True if not test or running == "shutdown" else None, +- } == virt.stopped( +- "myvm", connection="myconnection", username="user", password="secret", +- ) ++ } + if not test and running == "running": + shutdown_mock.assert_called_with( + "myvm", +@@ -586,12 +586,12 @@ def test_stopped_error(): + ), + }, + ): +- assert { ++ assert virt.stopped("myvm") == { + "name": "myvm", + "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, + "result": False, + "comment": "No changes had happened", +- } == virt.stopped("myvm") ++ } + + + def test_stopped_not_existing(test): +@@ -603,12 +603,12 @@ def test_stopped_not_existing(test): + with patch.dict( + virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}, + ): +- assert { ++ assert virt.stopped("myvm") == { + "name": "myvm", + "changes": {}, + "comment": "No changes had happened", + "result": False, +- } == virt.stopped("myvm") ++ } + + + @pytest.mark.parametrize("running", ["running", "shutdown"]) +@@ -631,14 +631,14 @@ def test_powered_off(test, running): + if running == "running": + changes = {"unpowered": [{"domain": "myvm", "stop": True}]} + comment = "Machine has been powered off" +- assert { ++ assert virt.powered_off( ++ "myvm", connection="myconnection", username="user", password="secret", ++ ) == { + "name": "myvm", + "result": True if not test or running == "shutdown" else None, + "changes": changes, + "comment": comment, +- } == virt.powered_off( +- "myvm", connection="myconnection", username="user", password="secret", +- ) ++ } + if not test and running == "running": + stop_mock.assert_called_with( + "myvm", +@@ -666,12 +666,12 @@ def test_powered_off_error(): + ), + }, + ): +- assert { ++ assert virt.powered_off("myvm") == { + "name": "myvm", + "result": False, + "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, + "comment": "No changes had happened", +- } == virt.powered_off("myvm") ++ } + + + def test_powered_off_not_existing(): +@@ -686,12 +686,12 @@ def test_powered_off_not_existing(): + ret.update( + {"changes": {}, "result": False, "comment": "No changes had happened"} + ) +- assert { ++ assert virt.powered_off("myvm") == { + "name": "myvm", + "changes": {}, + "result": False, + "comment": "No changes had happened", +- } == virt.powered_off("myvm") ++ } + + + def test_snapshot(test): +@@ -707,18 +707,18 @@ def test_snapshot(test): + "virt.snapshot": snapshot_mock, + }, + ): +- assert { +- "name": "myvm", +- "result": True if not test else None, +- "changes": {"saved": [{"domain": "myvm", "snapshot": True}]}, +- "comment": "Snapshot has been taken", +- } == virt.snapshot( ++ assert virt.snapshot( + "myvm", + suffix="snap", + connection="myconnection", + username="user", + password="secret", +- ) ++ ) == { ++ "name": "myvm", ++ "result": True if not test else None, ++ "changes": {"saved": [{"domain": "myvm", "snapshot": True}]}, ++ "comment": "Snapshot has been taken", ++ } + if not test: + snapshot_mock.assert_called_with( + "myvm", +@@ -745,12 +745,12 @@ def test_snapshot_error(): + ), + }, + ): +- assert { ++ assert virt.snapshot("myvm") == { + "name": "myvm", + "result": False, + "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, + "comment": "No changes had happened", +- } == virt.snapshot("myvm") ++ } + + + def test_snapshot_not_existing(test): +@@ -761,12 +761,12 @@ def test_snapshot_not_existing(test): + with patch.dict( + virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])} + ): +- assert { ++ assert virt.snapshot("myvm") == { + "name": "myvm", + "changes": {}, + "result": False, + "comment": "No changes had happened", +- } == virt.snapshot("myvm") ++ } + + + def test_rebooted(test): +@@ -782,14 +782,14 @@ def test_rebooted(test): + "virt.reboot": reboot_mock, + }, + ): +- assert { ++ assert virt.rebooted( ++ "myvm", connection="myconnection", username="user", password="secret", ++ ) == { + "name": "myvm", + "result": True if not test else None, + "changes": {"rebooted": [{"domain": "myvm", "reboot": True}]}, + "comment": "Machine has been rebooted", +- } == virt.rebooted( +- "myvm", connection="myconnection", username="user", password="secret", +- ) ++ } + if not test: + reboot_mock.assert_called_with( + "myvm", +@@ -816,12 +816,12 @@ def test_rebooted_error(): + ), + }, + ): +- assert { ++ assert virt.rebooted("myvm") == { + "name": "myvm", + "result": False, + "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]}, + "comment": "No changes had happened", +- } == virt.rebooted("myvm") ++ } + + + def test_rebooted_not_existing(test): +@@ -832,9 +832,9 @@ def test_rebooted_not_existing(test): + with patch.dict( + virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])} + ): +- assert { ++ assert virt.rebooted("myvm") == { + "name": "myvm", + "changes": {}, + "result": False, + "comment": "No changes had happened", +- } == virt.rebooted("myvm") ++ } +diff --git a/tests/pytests/unit/states/virt/test_network.py b/tests/pytests/unit/states/virt/test_network.py +index 668eee0c64..a68acfa236 100644 +--- a/tests/pytests/unit/states/virt/test_network.py ++++ b/tests/pytests/unit/states/virt/test_network.py +@@ -19,12 +19,7 @@ def test_network_defined_not_existing(test): + "virt.network_define": define_mock, + }, + ): +- assert { +- "name": "mynet", +- "changes": {"mynet": "Network defined"}, +- "result": None if test else True, +- "comment": "Network mynet defined", +- } == virt.network_defined( ++ assert virt.network_defined( + "mynet", + "br2", + "bridge", +@@ -58,7 +53,12 @@ def test_network_defined_not_existing(test): + connection="myconnection", + username="user", + password="secret", +- ) ++ ) == { ++ "name": "mynet", ++ "changes": {"mynet": "Network defined"}, ++ "result": None if test else True, ++ "comment": "Network mynet defined", ++ } + if not test: + define_mock.assert_called_with( + "mynet", +@@ -117,16 +117,16 @@ def test_network_defined_no_change(test): + "virt.network_update": update_mock, + }, + ): +- assert { ++ assert virt.network_defined("mynet", "br2", "bridge") == { + "name": "mynet", + "changes": {}, + "result": True, + "comment": "Network mynet unchanged", +- } == virt.network_defined("mynet", "br2", "bridge") ++ } + define_mock.assert_not_called() +- assert [ ++ assert update_mock.call_args_list == [ + network_update_call("mynet", "br2", "bridge", test=True) +- ] == update_mock.call_args_list ++ ] + + + def test_network_defined_change(test): +@@ -148,12 +148,7 @@ def test_network_defined_change(test): + "virt.network_set_autostart": autostart_mock, + }, + ): +- assert { +- "name": "mynet", +- "changes": {"mynet": "Network updated, autostart flag changed"}, +- "result": None if test else True, +- "comment": "Network mynet updated, autostart flag changed", +- } == virt.network_defined( ++ assert virt.network_defined( + "mynet", + "br2", + "bridge", +@@ -187,7 +182,12 @@ def test_network_defined_change(test): + connection="myconnection", + username="user", + password="secret", +- ) ++ ) == { ++ "name": "mynet", ++ "changes": {"mynet": "Network updated, autostart flag changed"}, ++ "result": None if test else True, ++ "comment": "Network mynet updated, autostart flag changed", ++ } + define_mock.assert_not_called() + expected_update_kwargs = { + "vport": "openvswitch", +@@ -226,7 +226,7 @@ def test_network_defined_change(test): + ) + ] + if test: +- assert calls == update_mock.call_args_list ++ assert update_mock.call_args_list == calls + autostart_mock.assert_not_called() + else: + calls.append( +@@ -234,7 +234,7 @@ def test_network_defined_change(test): + "mynet", "br2", "bridge", **expected_update_kwargs, test=False + ) + ) +- assert calls == update_mock.call_args_list ++ assert update_mock.call_args_list == calls + autostart_mock.assert_called_with( + "mynet", + state="off", +@@ -258,12 +258,12 @@ def test_network_defined_error(test): + ) + }, + ): +- assert { ++ assert virt.network_defined("mynet", "br2", "bridge") == { + "name": "mynet", + "changes": {}, + "result": False, + "comment": "Some error", +- } == virt.network_defined("mynet", "br2", "bridge") ++ } + define_mock.assert_not_called() + + +@@ -285,12 +285,7 @@ def test_network_running_not_existing(test): + "virt.network_start": start_mock, + }, + ): +- assert { +- "name": "mynet", +- "changes": {"mynet": "Network defined and started"}, +- "comment": "Network mynet defined and started", +- "result": None if test else True, +- } == virt.network_running( ++ assert virt.network_running( + "mynet", + "br2", + "bridge", +@@ -324,7 +319,12 @@ def test_network_running_not_existing(test): + connection="myconnection", + username="user", + password="secret", +- ) ++ ) == { ++ "name": "mynet", ++ "changes": {"mynet": "Network defined and started"}, ++ "comment": "Network mynet defined and started", ++ "result": None if test else True, ++ } + if not test: + define_mock.assert_called_with( + "mynet", +@@ -390,15 +390,15 @@ def test_network_running_nochange(test): + "virt.network_update": update_mock, + }, + ): +- assert { ++ assert virt.network_running("mynet", "br2", "bridge") == { + "name": "mynet", + "changes": {}, + "comment": "Network mynet unchanged and is running", + "result": None if test else True, +- } == virt.network_running("mynet", "br2", "bridge") +- assert [ ++ } ++ assert update_mock.call_args_list == [ + network_update_call("mynet", "br2", "bridge", test=True) +- ] == update_mock.call_args_list ++ ] + + + def test_network_running_stopped(test): +@@ -420,20 +420,20 @@ def test_network_running_stopped(test): + "virt.network_update": update_mock, + }, + ): +- assert { +- "name": "mynet", +- "changes": {"mynet": "Network started"}, +- "comment": "Network mynet unchanged and started", +- "result": None if test else True, +- } == virt.network_running( ++ assert virt.network_running( + "mynet", + "br2", + "bridge", + connection="myconnection", + username="user", + password="secret", +- ) +- assert [ ++ ) == { ++ "name": "mynet", ++ "changes": {"mynet": "Network started"}, ++ "comment": "Network mynet unchanged and started", ++ "result": None if test else True, ++ } ++ assert update_mock.call_args_list == [ + network_update_call( + "mynet", + "br2", +@@ -443,7 +443,7 @@ def test_network_running_stopped(test): + password="secret", + test=True, + ) +- ] == update_mock.call_args_list ++ ] + if not test: + start_mock.assert_called_with( + "mynet", +@@ -468,9 +468,9 @@ def test_network_running_error(test): + ), + }, + ): +- assert { ++ assert virt.network_running("mynet", "br2", "bridge") == { + "name": "mynet", + "changes": {}, + "comment": "Some error", + "result": False, +- } == virt.network_running("mynet", "br2", "bridge") ++ } +diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py +index cac83e8717..a739efdbf6 100644 +--- a/tests/unit/modules/test_virt.py ++++ b/tests/unit/modules/test_virt.py +@@ -520,65 +520,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): + root.find("devices/graphics/listen").attrib["address"], "myhost" + ) + +- def test_gen_xml_spice_default(self): +- """ +- Test virt._gen_xml() with default spice graphics device +- """ +- diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") +- nicp = virt._nic_profile("default", "kvm") +- xml_data = virt._gen_xml( +- self.mock_conn, +- "hello", +- 1, +- 512, +- diskp, +- nicp, +- "kvm", +- "hvm", +- "x86_64", +- graphics={"type": "spice"}, +- ) +- root = ET.fromstring(xml_data) +- self.assertEqual(root.find("devices/graphics").attrib["type"], "spice") +- self.assertEqual(root.find("devices/graphics").attrib["autoport"], "yes") +- self.assertEqual(root.find("devices/graphics").attrib["listen"], "0.0.0.0") +- self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "address") +- self.assertEqual( +- root.find("devices/graphics/listen").attrib["address"], "0.0.0.0" +- ) +- +- def test_gen_xml_spice(self): +- """ +- Test virt._gen_xml() with spice graphics device +- """ +- diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello") +- nicp = virt._nic_profile("default", "kvm") +- xml_data = virt._gen_xml( +- self.mock_conn, +- "hello", +- 1, +- 512, +- diskp, +- nicp, +- "kvm", +- "hvm", +- "x86_64", +- graphics={ +- "type": "spice", +- "port": 1234, +- "tls_port": 5678, +- "listen": {"type": "none"}, +- }, +- ) +- root = ET.fromstring(xml_data) +- self.assertEqual(root.find("devices/graphics").attrib["type"], "spice") +- self.assertEqual(root.find("devices/graphics").attrib["autoport"], "no") +- self.assertEqual(root.find("devices/graphics").attrib["port"], "1234") +- self.assertEqual(root.find("devices/graphics").attrib["tlsPort"], "5678") +- self.assertFalse("listen" in root.find("devices/graphics").attrib) +- self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "none") +- self.assertFalse("address" in root.find("devices/graphics/listen").attrib) +- + def test_gen_xml_memory(self): + """ + Test virt._gen_xml() with advanced memory settings +-- +2.30.0 + + diff --git a/_lastrevision b/_lastrevision index 9eca5e8..eb8befa 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -73673e4ab1d13c4393183b8ad6066dfab39c7e63 \ No newline at end of file +98a9fb14263d76c4873bc68f208aeee04b583044 \ No newline at end of file diff --git a/do-not-crash-when-unexpected-cmd-output-at-listing-p.patch b/do-not-crash-when-unexpected-cmd-output-at-listing-p.patch new file mode 100644 index 0000000..423c0cb --- /dev/null +++ b/do-not-crash-when-unexpected-cmd-output-at-listing-p.patch @@ -0,0 +1,130 @@ +From cec95ba8f9b561d7ca4c66be9483e4b9386cb741 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Mon, 25 Jan 2021 12:15:59 +0000 +Subject: [PATCH] Do not crash when unexpected cmd output at listing + patches (bsc#1181290) + +Add unit tests to cover unexpected output when listing patches +--- + salt/modules/yumpkg.py | 20 ++++++++-- + tests/unit/modules/test_yumpkg.py | 63 +++++++++++++++++++++++++++++++ + 2 files changed, 80 insertions(+), 3 deletions(-) + +diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py +index df174e737d..82adbbd59d 100644 +--- a/salt/modules/yumpkg.py ++++ b/salt/modules/yumpkg.py +@@ -3291,10 +3291,17 @@ def _get_patches(installed_only=False): + + cmd = [_yum(), "--quiet", "updateinfo", "list", "all"] + ret = __salt__["cmd.run_stdout"](cmd, python_shell=False, env={"SALT_RUNNING": "1"}) ++ parsing_errors = False ++ + for line in salt.utils.itertools.split(ret, os.linesep): +- inst, advisory_id, sev, pkg = re.match( +- r"([i|\s]) ([^\s]+) +([^\s]+) +([^\s]+)", line +- ).groups() ++ try: ++ inst, advisory_id, sev, pkg = re.match( ++ r"([i|\s]) ([^\s]+) +([^\s]+) +([^\s]+)", line ++ ).groups() ++ except Exception: # pylint: disable=broad-except ++ parsing_errors = True ++ continue ++ + if advisory_id not in patches: + patches[advisory_id] = { + "installed": True if inst == "i" else False, +@@ -3305,6 +3312,13 @@ def _get_patches(installed_only=False): + if inst != "i": + patches[advisory_id]["installed"] = False + ++ if parsing_errors: ++ log.warning( ++ "Skipped some unexpected output while running '{}' to list patches. Please check output".format( ++ " ".join(cmd) ++ ) ++ ) ++ + if installed_only: + patches = {k: v for k, v in patches.items() if v["installed"]} + return patches +diff --git a/tests/unit/modules/test_yumpkg.py b/tests/unit/modules/test_yumpkg.py +index b97e82d307..96d3f12b17 100644 +--- a/tests/unit/modules/test_yumpkg.py ++++ b/tests/unit/modules/test_yumpkg.py +@@ -383,6 +383,69 @@ class YumTestCase(TestCase, LoaderModuleMockMixin): + _patch in patches["my-fake-patch-installed-1234"]["summary"] + ) + ++ def test_list_patches_with_unexpected_output(self): ++ """ ++ Test patches listin with unexpected output from updateinfo list ++ ++ :return: ++ """ ++ yum_out = [ ++ "Update notice RHBA-2014:0722 (from rhel7-dev-rhel7-rpm-x86_64) is broken, or a bad duplicate, skipping.", ++ "You should report this problem to the owner of the rhel7-dev-rhel7-rpm-x86_64 repository.", ++ 'To help pinpoint the issue, please attach the output of "yum updateinfo --verbose" to the report.', ++ "Update notice RHSA-2014:1971 (from rhel7-dev-rhel7-rpm-x86_64) is broken, or a bad duplicate, skipping.", ++ "Update notice RHSA-2015:1981 (from rhel7-dev-rhel7-rpm-x86_64) is broken, or a bad duplicate, skipping.", ++ "Update notice RHSA-2015:0067 (from rhel7-dev-rhel7-rpm-x86_64) is broken, or a bad duplicate, skipping", ++ "i my-fake-patch-not-installed-1234 recommended spacewalk-usix-2.7.5.2-2.2.noarch", ++ " my-fake-patch-not-installed-1234 recommended spacewalksd-5.0.26.2-21.2.x86_64", ++ "i my-fake-patch-not-installed-1234 recommended suseRegisterInfo-3.1.1-18.2.x86_64", ++ "i my-fake-patch-installed-1234 recommended my-package-one-1.1-0.1.x86_64", ++ "i my-fake-patch-installed-1234 recommended my-package-two-1.1-0.1.x86_64", ++ ] ++ ++ expected_patches = { ++ "my-fake-patch-not-installed-1234": { ++ "installed": False, ++ "summary": [ ++ "spacewalk-usix-2.7.5.2-2.2.noarch", ++ "spacewalksd-5.0.26.2-21.2.x86_64", ++ "suseRegisterInfo-3.1.1-18.2.x86_64", ++ ], ++ }, ++ "my-fake-patch-installed-1234": { ++ "installed": True, ++ "summary": [ ++ "my-package-one-1.1-0.1.x86_64", ++ "my-package-two-1.1-0.1.x86_64", ++ ], ++ }, ++ } ++ ++ with patch.dict(yumpkg.__grains__, {"osarch": "x86_64"}), patch.dict( ++ yumpkg.__salt__, ++ {"cmd.run_stdout": MagicMock(return_value=os.linesep.join(yum_out))}, ++ ): ++ patches = yumpkg.list_patches() ++ self.assertFalse(patches["my-fake-patch-not-installed-1234"]["installed"]) ++ self.assertTrue( ++ len(patches["my-fake-patch-not-installed-1234"]["summary"]) == 3 ++ ) ++ for _patch in expected_patches["my-fake-patch-not-installed-1234"][ ++ "summary" ++ ]: ++ self.assertTrue( ++ _patch in patches["my-fake-patch-not-installed-1234"]["summary"] ++ ) ++ ++ self.assertTrue(patches["my-fake-patch-installed-1234"]["installed"]) ++ self.assertTrue( ++ len(patches["my-fake-patch-installed-1234"]["summary"]) == 2 ++ ) ++ for _patch in expected_patches["my-fake-patch-installed-1234"]["summary"]: ++ self.assertTrue( ++ _patch in patches["my-fake-patch-installed-1234"]["summary"] ++ ) ++ + def test_latest_version_with_options(self): + with patch.object(yumpkg, "list_pkgs", MagicMock(return_value={})): + +-- +2.29.2 + + diff --git a/fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch b/fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch new file mode 100644 index 0000000..9bc9d4b --- /dev/null +++ b/fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch @@ -0,0 +1,287 @@ +From 828ca76e2083d87ace12b488277e51d4e30c8c9a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Thu, 21 Jan 2021 11:19:07 +0000 +Subject: [PATCH] Fix onlyif/unless when multiple conditions + (bsc#1180818) + +Add unit tests to ensure right onlyif/unless behavior + +Add extra unit test to cover valid cases + +Add unit tests cases to ensure proper onlyif/unless behavior + +Change tests to use 'exit' cmd and work outside Linux +--- + salt/state.py | 20 ++++-- + tests/unit/test_state.py | 148 ++++++++++++++++++++++++++++++++++++++- + 2 files changed, 163 insertions(+), 5 deletions(-) + +diff --git a/salt/state.py b/salt/state.py +index cc6db7e1b2..070a914636 100644 +--- a/salt/state.py ++++ b/salt/state.py +@@ -947,8 +947,10 @@ class State: + "result": True, + } + ) ++ return False + elif cmd == 0: + ret.update({"comment": "onlyif condition is true", "result": False}) ++ return True + + for entry in low_data_onlyif: + if isinstance(entry, str): +@@ -960,7 +962,8 @@ class State: + # Command failed, notify onlyif to skip running the item + cmd = 100 + log.debug("Last command return code: %s", cmd) +- _check_cmd(cmd) ++ if not _check_cmd(cmd): ++ return ret + elif isinstance(entry, dict): + if "fun" not in entry: + ret["comment"] = "no `fun` argument in onlyif: {}".format(entry) +@@ -972,7 +975,8 @@ class State: + if get_return: + result = salt.utils.data.traverse_dict_and_list(result, get_return) + if self.state_con.get("retcode", 0): +- _check_cmd(self.state_con["retcode"]) ++ if not _check_cmd(self.state_con["retcode"]): ++ return ret + elif not result: + ret.update( + { +@@ -981,6 +985,7 @@ class State: + "result": True, + } + ) ++ return ret + else: + ret.update({"comment": "onlyif condition is true", "result": False}) + +@@ -991,6 +996,7 @@ class State: + "result": False, + } + ) ++ return ret + return ret + + def _run_check_unless(self, low_data, cmd_opts): +@@ -1013,8 +1019,10 @@ class State: + "result": True, + } + ) ++ return False + elif cmd != 0: + ret.update({"comment": "unless condition is false", "result": False}) ++ return True + + for entry in low_data_unless: + if isinstance(entry, str): +@@ -1026,7 +1034,8 @@ class State: + except CommandExecutionError: + # Command failed, so notify unless to skip the item + cmd = 0 +- _check_cmd(cmd) ++ if not _check_cmd(cmd): ++ return ret + elif isinstance(entry, dict): + if "fun" not in entry: + ret["comment"] = "no `fun` argument in unless: {}".format(entry) +@@ -1038,7 +1047,8 @@ class State: + if get_return: + result = salt.utils.data.traverse_dict_and_list(result, get_return) + if self.state_con.get("retcode", 0): +- _check_cmd(self.state_con["retcode"]) ++ if not _check_cmd(self.state_con["retcode"]): ++ return ret + elif result: + ret.update( + { +@@ -1047,6 +1057,7 @@ class State: + "result": True, + } + ) ++ return ret + else: + ret.update( + {"comment": "unless condition is false", "result": False} +@@ -1058,6 +1069,7 @@ class State: + "result": False, + } + ) ++ return ret + + # No reason to stop, return ret + return ret +diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py +index b1bcf8fe83..95018a9cf3 100644 +--- a/tests/unit/test_state.py ++++ b/tests/unit/test_state.py +@@ -205,6 +205,152 @@ class StateCompilerTestCase(TestCase, AdaptedConfigurationTestCaseMixin): + ) + self.assertEqual(expected_result, return_result) + ++ def test_verify_unless_list_cmd(self): ++ low_data = { ++ "state": "cmd", ++ "name": 'echo "something"', ++ "__sls__": "tests.cmd", ++ "__env__": "base", ++ "__id__": "check unless", ++ "unless": ["exit 0", "exit 1"], ++ "order": 10001, ++ "fun": "run", ++ } ++ expected_result = { ++ "comment": "unless condition is true", ++ "result": True, ++ "skip_watch": True, ++ } ++ with patch("salt.state.State._gather_pillar") as state_patch: ++ minion_opts = self.get_temp_config("minion") ++ state_obj = salt.state.State(minion_opts) ++ return_result = state_obj._run_check_unless(low_data, {}) ++ self.assertEqual(expected_result, return_result) ++ ++ def test_verify_unless_list_cmd_different_order(self): ++ low_data = { ++ "state": "cmd", ++ "name": 'echo "something"', ++ "__sls__": "tests.cmd", ++ "__env__": "base", ++ "__id__": "check unless", ++ "unless": ["exit 1", "exit 0"], ++ "order": 10001, ++ "fun": "run", ++ } ++ expected_result = { ++ "comment": "unless condition is true", ++ "result": True, ++ "skip_watch": True, ++ } ++ with patch("salt.state.State._gather_pillar") as state_patch: ++ minion_opts = self.get_temp_config("minion") ++ state_obj = salt.state.State(minion_opts) ++ return_result = state_obj._run_check_unless(low_data, {}) ++ self.assertEqual(expected_result, return_result) ++ ++ def test_verify_onlyif_list_cmd_different_order(self): ++ low_data = { ++ "state": "cmd", ++ "name": 'echo "something"', ++ "__sls__": "tests.cmd", ++ "__env__": "base", ++ "__id__": "check onlyif", ++ "onlyif": ["exit 1", "exit 0"], ++ "order": 10001, ++ "fun": "run", ++ } ++ expected_result = { ++ "comment": "onlyif condition is false", ++ "result": True, ++ "skip_watch": True, ++ } ++ with patch("salt.state.State._gather_pillar") as state_patch: ++ minion_opts = self.get_temp_config("minion") ++ state_obj = salt.state.State(minion_opts) ++ return_result = state_obj._run_check_onlyif(low_data, {}) ++ self.assertEqual(expected_result, return_result) ++ ++ def test_verify_unless_list_cmd_valid(self): ++ low_data = { ++ "state": "cmd", ++ "name": 'echo "something"', ++ "__sls__": "tests.cmd", ++ "__env__": "base", ++ "__id__": "check unless", ++ "unless": ["exit 1", "exit 1"], ++ "order": 10001, ++ "fun": "run", ++ } ++ expected_result = {"comment": "unless condition is false", "result": False} ++ with patch("salt.state.State._gather_pillar") as state_patch: ++ minion_opts = self.get_temp_config("minion") ++ state_obj = salt.state.State(minion_opts) ++ return_result = state_obj._run_check_unless(low_data, {}) ++ self.assertEqual(expected_result, return_result) ++ ++ def test_verify_onlyif_list_cmd_valid(self): ++ low_data = { ++ "state": "cmd", ++ "name": 'echo "something"', ++ "__sls__": "tests.cmd", ++ "__env__": "base", ++ "__id__": "check onlyif", ++ "onlyif": ["exit 0", "exit 0"], ++ "order": 10001, ++ "fun": "run", ++ } ++ expected_result = {"comment": "onlyif condition is true", "result": False} ++ with patch("salt.state.State._gather_pillar") as state_patch: ++ minion_opts = self.get_temp_config("minion") ++ state_obj = salt.state.State(minion_opts) ++ return_result = state_obj._run_check_onlyif(low_data, {}) ++ self.assertEqual(expected_result, return_result) ++ ++ def test_verify_unless_list_cmd_invalid(self): ++ low_data = { ++ "state": "cmd", ++ "name": 'echo "something"', ++ "__sls__": "tests.cmd", ++ "__env__": "base", ++ "__id__": "check unless", ++ "unless": ["exit 0", "exit 0"], ++ "order": 10001, ++ "fun": "run", ++ } ++ expected_result = { ++ "comment": "unless condition is true", ++ "result": True, ++ "skip_watch": True, ++ } ++ with patch("salt.state.State._gather_pillar") as state_patch: ++ minion_opts = self.get_temp_config("minion") ++ state_obj = salt.state.State(minion_opts) ++ return_result = state_obj._run_check_unless(low_data, {}) ++ self.assertEqual(expected_result, return_result) ++ ++ def test_verify_onlyif_list_cmd_invalid(self): ++ low_data = { ++ "state": "cmd", ++ "name": 'echo "something"', ++ "__sls__": "tests.cmd", ++ "__env__": "base", ++ "__id__": "check onlyif", ++ "onlyif": ["exit 1", "exit 1"], ++ "order": 10001, ++ "fun": "run", ++ } ++ expected_result = { ++ "comment": "onlyif condition is false", ++ "result": True, ++ "skip_watch": True, ++ } ++ with patch("salt.state.State._gather_pillar") as state_patch: ++ minion_opts = self.get_temp_config("minion") ++ state_obj = salt.state.State(minion_opts) ++ return_result = state_obj._run_check_onlyif(low_data, {}) ++ self.assertEqual(expected_result, return_result) ++ + def test_verify_unless_parse(self): + low_data = { + "unless": [{"fun": "test.arg", "args": ["arg1", "arg2"]}], +@@ -376,7 +522,7 @@ class StateCompilerTestCase(TestCase, AdaptedConfigurationTestCaseMixin): + "__sls__": "tests.cmd", + "__env__": "base", + "__id__": "check onlyif", +- "onlyif": ["/bin/true", "/bin/false"], ++ "onlyif": ["exit 0", "exit 1"], + "order": 10001, + "fun": "run", + } +-- +2.29.2 + + diff --git a/open-suse-3002.2-xen-grub-316.patch b/open-suse-3002.2-xen-grub-316.patch new file mode 100644 index 0000000..05daa0c --- /dev/null +++ b/open-suse-3002.2-xen-grub-316.patch @@ -0,0 +1,211 @@ +From 6573d8ca0087f5ce6a8639c0ff583b3248f0704e Mon Sep 17 00:00:00 2001 +From: Cedric Bosdonnat +Date: Thu, 11 Feb 2021 16:41:14 +0100 +Subject: [PATCH] Open suse 3002.2 xen grub (#316) + +* virt: convert xen pv XML generation test to pytest + +* virt: better look for grub.xen when generating xen pv definition + +openSUSE 15.3 and SLES 15 SP3 have removed the compatibility symlink for +/usr/share/grub2/x86_64-xen/grub.xen to +/usr/lib/grub2/x86_64-xen/grub.xen. virt._gen_xml() need to check which +is present and put in Xen PV VMs XML definition. +--- + changelog/59484.fixed | 1 + + salt/modules/virt.py | 9 +- + .../pytests/unit/modules/virt/test_domain.py | 82 ++++++++++++++++++- + tests/unit/modules/test_virt.py | 48 ----------- + 4 files changed, 90 insertions(+), 50 deletions(-) + create mode 100644 changelog/59484.fixed + +diff --git a/changelog/59484.fixed b/changelog/59484.fixed +new file mode 100644 +index 0000000000..b685510ad9 +--- /dev/null ++++ b/changelog/59484.fixed +@@ -0,0 +1 @@ ++Detect and fix grub.xen path +diff --git a/salt/modules/virt.py b/salt/modules/virt.py +index da132630dd..35711fcef4 100644 +--- a/salt/modules/virt.py ++++ b/salt/modules/virt.py +@@ -889,7 +889,14 @@ def _gen_xml( + # Compute the Xen PV boot method + if __grains__["os_family"] == "Suse": + if not boot or not boot.get("kernel", None): +- context["boot"]["kernel"] = "/usr/lib/grub2/x86_64-xen/grub.xen" ++ paths = [ ++ path ++ for path in ["/usr/share", "/usr/lib"] ++ if os.path.exists(path + "/grub2/x86_64-xen/grub.xen") ++ ] ++ if not paths: ++ raise CommandExecutionError("grub-x86_64-xen needs to be installed") ++ context["boot"]["kernel"] = paths[0] + "/grub2/x86_64-xen/grub.xen" + context["boot_dev"] = [] + + default_port = 23023 +diff --git a/tests/pytests/unit/modules/virt/test_domain.py b/tests/pytests/unit/modules/virt/test_domain.py +index 72fa599a6c..76433eaef4 100644 +--- a/tests/pytests/unit/modules/virt/test_domain.py ++++ b/tests/pytests/unit/modules/virt/test_domain.py +@@ -5,7 +5,7 @@ import salt.modules.virt as virt + import salt.utils.xmlutil as xmlutil + import salt.syspaths + from salt._compat import ElementTree as ET +-from salt.exceptions import SaltInvocationError ++from salt.exceptions import CommandExecutionError, SaltInvocationError + from tests.support.mock import MagicMock, patch + + from .conftest import loader_modules_config +@@ -17,6 +17,86 @@ def configure_loader_modules(): + return loader_modules_config() + + ++@pytest.mark.parametrize( ++ "loader", ++ [ ++ "/usr/lib/grub2/x86_64-xen/grub.xen", ++ "/usr/share/grub2/x86_64-xen/grub.xen", ++ None, ++ ], ++) ++def test_gen_xml_for_xen_default_profile(loader): ++ """ ++ Test virt._gen_xml(), XEN PV default profile case ++ """ ++ diskp = virt._disk_profile( ++ virt.libvirt.openAuth.return_value, "default", "xen", [], "hello" ++ ) ++ nicp = virt._nic_profile("default", "xen") ++ with patch.dict( ++ virt.__grains__, {"os_family": "Suse"} # pylint: disable=no-member ++ ): ++ os_mock = MagicMock(spec=virt.os) ++ ++ def fake_exists(path): ++ return loader and path == loader ++ ++ os_mock.path.exists = MagicMock(side_effect=fake_exists) ++ ++ with patch.dict(virt.__dict__, {"os": os_mock}): ++ if loader: ++ xml_data = virt._gen_xml( ++ virt.libvirt.openAuth.return_value, ++ "hello", ++ 1, ++ 512, ++ diskp, ++ nicp, ++ "xen", ++ "xen", ++ "x86_64", ++ boot=None, ++ ) ++ root = ET.fromstring(xml_data) ++ assert root.attrib["type"] == "xen" ++ assert root.find("vcpu").text == "1" ++ assert root.find("memory").text == str(512 * 1024) ++ assert root.find("memory").attrib["unit"] == "KiB" ++ assert root.find(".//kernel").text == loader ++ ++ disks = root.findall(".//disk") ++ assert len(disks) == 1 ++ disk = disks[0] ++ root_dir = salt.config.DEFAULT_MINION_OPTS.get("root_dir") ++ assert disk.find("source").attrib["file"].startswith(root_dir) ++ assert "hello_system" in disk.find("source").attrib["file"] ++ assert disk.find("target").attrib["dev"] == "xvda" ++ assert disk.find("target").attrib["bus"] == "xen" ++ assert disk.find("driver").attrib["name"] == "qemu" ++ assert disk.find("driver").attrib["type"] == "qcow2" ++ ++ interfaces = root.findall(".//interface") ++ assert len(interfaces) == 1 ++ iface = interfaces[0] ++ assert iface.attrib["type"] == "bridge" ++ assert iface.find("source").attrib["bridge"] == "br0" ++ assert iface.find("model") is None ++ else: ++ with pytest.raises(CommandExecutionError): ++ xml_data = virt._gen_xml( ++ virt.libvirt.openAuth.return_value, ++ "hello", ++ 1, ++ 512, ++ diskp, ++ nicp, ++ "xen", ++ "xen", ++ "x86_64", ++ boot=None, ++ ) ++ ++ + def test_update_xen_disk_volumes(make_mock_vm, make_mock_storage_pool): + xml_def = """ + +diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py +index a739efdbf6..5c7e1e1cc4 100644 +--- a/tests/unit/modules/test_virt.py ++++ b/tests/unit/modules/test_virt.py +@@ -1126,54 +1126,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin): + self.assertEqual(iface.find("source").attrib["bridge"], "DEFAULT") + self.assertEqual(iface.find("model").attrib["type"], "e1000") + +- def test_gen_xml_for_xen_default_profile(self): +- """ +- Test virt._gen_xml(), XEN PV default profile case +- """ +- diskp = virt._disk_profile(self.mock_conn, "default", "xen", [], "hello") +- nicp = virt._nic_profile("default", "xen") +- with patch.dict( +- virt.__grains__, {"os_family": "Suse"} # pylint: disable=no-member +- ): +- xml_data = virt._gen_xml( +- self.mock_conn, +- "hello", +- 1, +- 512, +- diskp, +- nicp, +- "xen", +- "xen", +- "x86_64", +- boot=None, +- ) +- root = ET.fromstring(xml_data) +- self.assertEqual(root.attrib["type"], "xen") +- self.assertEqual(root.find("vcpu").text, "1") +- self.assertEqual(root.find("memory").text, str(512 * 1024)) +- self.assertEqual(root.find("memory").attrib["unit"], "KiB") +- self.assertEqual( +- root.find(".//kernel").text, "/usr/lib/grub2/x86_64-xen/grub.xen" +- ) +- +- disks = root.findall(".//disk") +- self.assertEqual(len(disks), 1) +- disk = disks[0] +- root_dir = salt.config.DEFAULT_MINION_OPTS.get("root_dir") +- self.assertTrue(disk.find("source").attrib["file"].startswith(root_dir)) +- self.assertTrue("hello_system" in disk.find("source").attrib["file"]) +- self.assertEqual(disk.find("target").attrib["dev"], "xvda") +- self.assertEqual(disk.find("target").attrib["bus"], "xen") +- self.assertEqual(disk.find("driver").attrib["name"], "qemu") +- self.assertEqual(disk.find("driver").attrib["type"], "qcow2") +- +- interfaces = root.findall(".//interface") +- self.assertEqual(len(interfaces), 1) +- iface = interfaces[0] +- self.assertEqual(iface.attrib["type"], "bridge") +- self.assertEqual(iface.find("source").attrib["bridge"], "br0") +- self.assertIsNone(iface.find("model")) +- + def test_gen_xml_for_esxi_custom_profile(self): + """ + Test virt._gen_xml(), ESXi/vmware custom profile case +-- +2.30.0 + + diff --git a/salt.changes b/salt.changes index 4e72674..d61f807 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,33 @@ +------------------------------------------------------------------- +Thu Feb 11 16:02:59 UTC 2021 - Pablo Suárez Hernández + +- virt: search for grub.xen path +- Xen spicevmc, DNS SRV records backports: + Fix virtual network generated DNS XML for SRV records + Don't add spicevmc channel to xen VMs +- virt UEFI fix: virt.update when efi=True + +- Added: + * virt-uefi-fix-backport-312.patch + * 3002.2-xen-spicevmc-dns-srv-records-backports-314.patch + * open-suse-3002.2-xen-grub-316.patch + +------------------------------------------------------------------- +Mon Jan 25 13:52:50 UTC 2021 - Pablo Suárez Hernández + +- Do not crash when unexpected cmd output at listing patches (bsc#1181290) + +- Added: + * do-not-crash-when-unexpected-cmd-output-at-listing-p.patch + +------------------------------------------------------------------- +Fri Jan 22 16:28:51 UTC 2021 - Pablo Suárez Hernández + +- Fix behavior for "onlyif/unless" when multiple conditions (bsc#1180818) + +- Added: + * fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch + ------------------------------------------------------------------- Wed Jan 13 13:49:34 UTC 2021 - Pablo Suárez Hernández diff --git a/salt.spec b/salt.spec index 49be134..32d1c59 100644 --- a/salt.spec +++ b/salt.spec @@ -348,6 +348,17 @@ Patch143: force-zyppnotify-to-prefer-packages.db-than-packages.patch Patch144: revert-add-patch-support-for-allow-vendor-change-opt.patch # PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/commit/73e357d7eee19a73cade22becb30d9689cae27ba Patch145: remove-deprecated-warning-that-breaks-miniion-execut.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/59345 +Patch146: fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/59354 +Patch147: do-not-crash-when-unexpected-cmd-output-at-listing-p.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/59189 +Patch148: virt-uefi-fix-backport-312.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/59355 +# https://github.com/saltstack/salt/pull/59417 +Patch149: 3002.2-xen-spicevmc-dns-srv-records-backports-314.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/59485 +Patch150: open-suse-3002.2-xen-grub-316.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: logrotate @@ -892,6 +903,11 @@ cp %{S:5} ./.travis.yml %patch143 -p1 %patch144 -p1 %patch145 -p1 +%patch146 -p1 +%patch147 -p1 +%patch148 -p1 +%patch149 -p1 +%patch150 -p1 %build # Putting /usr/bin at the front of $PATH is needed for RHEL/RES 7. Without this diff --git a/virt-uefi-fix-backport-312.patch b/virt-uefi-fix-backport-312.patch new file mode 100644 index 0000000..ca6445c --- /dev/null +++ b/virt-uefi-fix-backport-312.patch @@ -0,0 +1,2449 @@ +From 86e7e81f13134382d50e7a12f544d7b25cae4a4b Mon Sep 17 00:00:00 2001 +From: Cedric Bosdonnat +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 | 52 +- + tests/pytests/unit/modules/virt/conftest.py | 30 +- + .../pytests/unit/modules/virt/test_domain.py | 1010 ++++++++++++++- + .../pytests/unit/modules/virt/test_helpers.py | 7 + + tests/unit/modules/test_virt.py | 1118 ----------------- + 6 files changed, 1056 insertions(+), 1162 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 9f61983e8d..7da35445a3 100644 +--- a/salt/modules/virt.py ++++ b/salt/modules/virt.py +@@ -139,13 +139,13 @@ 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 ElementTree, ipaddress, saxutils + from salt.exceptions import CommandExecutionError, SaltInvocationError + 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 +@@ -1829,25 +1829,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): +@@ -1870,10 +1869,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 +@@ -3150,7 +3150,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 + +@@ -3160,7 +3160,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 + +@@ -3656,7 +3656,7 @@ def update( + boot, + boot_dev, + numatune, +- serial=serials, ++ serials=serials, + consoles=consoles, + stop_on_reboot=stop_on_reboot, + host_devices=host_devices, +@@ -4050,8 +4050,8 @@ def update( + "disk": _skip_update(["disks", "disk_profile"]), + "interface": _skip_update(["interfaces", "nic_profile"]), + "graphics": _skip_update(["graphics"]), +- "serial": _skip_update(["serial"]), +- "console": _skip_update(["console"]), ++ "serial": _skip_update(["serials"]), ++ "console": _skip_update(["consoles"]), + "hostdev": _skip_update(["host_devices"]), + } + changes = _compute_device_changes(desc, new_desc, to_skip) +diff --git a/tests/pytests/unit/modules/virt/conftest.py b/tests/pytests/unit/modules/virt/conftest.py +index 3bacd734a7..d306997deb 100644 +--- a/tests/pytests/unit/modules/virt/conftest.py ++++ b/tests/pytests/unit/modules/virt/conftest.py +@@ -65,10 +65,24 @@ def loader_modules_config(): + + @pytest.fixture + def make_mock_vm(): +- def _make_mock_vm(xml_def, running=False, inactive_def=None): ++ 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 = """ ++ ++ my_vm ++ 1048576 ++ 1048576 ++ 1 ++ restart ++ ++ hvm ++ ++ ++ """ ++ doc = ET.fromstring(desc) + name = doc.find("name").text + os_type = "hvm" + os_type_node = doc.find("os/type") +@@ -84,9 +98,9 @@ def make_mock_vm(): + domain_mock = mocked_conn.lookupByName(name) + + domain_mock.XMLDesc = MappedResultMock() +- domain_mock.XMLDesc.add(0, xml_def) ++ domain_mock.XMLDesc.add(0, desc) + domain_mock.XMLDesc.add( +- virt.libvirt.VIR_DOMAIN_XML_INACTIVE, inactive_def or xml_def ++ virt.libvirt.VIR_DOMAIN_XML_INACTIVE, inactive_def or desc + ) + domain_mock.OSType.return_value = os_type + +@@ -103,6 +117,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 + + domain_mock.connect.return_value = mocked_conn + +@@ -113,7 +129,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 +@@ -130,8 +146,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 = "".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 0bde881403..33357c60ea 100644 +--- a/tests/pytests/unit/modules/virt/test_domain.py ++++ b/tests/pytests/unit/modules/virt/test_domain.py +@@ -1,11 +1,15 @@ ++import os.path ++ + import pytest + import salt.modules.virt as virt + import salt.utils.xmlutil as xmlutil ++import salt.syspaths + from salt._compat import ElementTree as ET ++from salt.exceptions import SaltInvocationError + from tests.support.mock import MagicMock, patch + + from .conftest import loader_modules_config +-from .test_helpers import append_to_XMLDesc, assert_called, strip_xml ++from .test_helpers import append_to_XMLDesc, assert_called, strip_xml, assertEqualUnit + + + @pytest.fixture +@@ -54,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") + +@@ -544,8 +548,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 + + +@@ -568,8 +572,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 + + +@@ -581,8 +585,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 + + +@@ -594,8 +598,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 + + +@@ -1060,3 +1064,987 @@ def test_update_nic_hostdev_nochange(make_mock_network, make_mock_vm, test): + 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 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( ++ """ ++ ++ my_vm ++ 1048576 ++ 1048576 ++ 1 ++ restart ++ ++ hvm ++ ++ ++ ++ """ ++ ) ++ 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( ++ """ ++ ++ my_vm ++ 27434df0-706d-4603-8ad7-5a88d19a3417 ++ 524288 ++ 524288 ++ 1 ++ ++ /machine ++ ++ ++ hvm ++ /usr/share/qemu/edk2-x86_64-code.fd ++ /var/lib/libvirt/qemu/nvram/vm01_VARS.fd ++ ++ restart ++ ++ """ ++ ) ++ ++ 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, ++ }, ++ ) ++ ++ assert ret["definition"] ++ setxml = ET.fromstring(virt.libvirt.openAuth().defineXML.call_args[0][0]) ++ 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( ++ """ ++ ++ my_vm ++ 1048576 ++ 1048576 ++ 1 ++ restart ++ ++ hvm ++ ++ ++ ++ ++ ++ ++ ++ """ ++ ) ++ 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( ++ """ ++ ++ my_vm ++ 1048576 ++ 1048576 ++ 1 ++ restart ++ ++ hvm ++ ++ ++ ++ ++ ++ ++ """ ++ ) ++ ++ 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 = """ ++ ++ my_vm ++ 1048576 ++ 1048576 ++ 1 ++ restart ++ ++ hvm ++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ """.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(" -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( ++ """ ++ ++ my_vm ++ 1048576 ++ 1048576 ++ 1 ++ restart ++ ++ hvm ++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ """ ++ ) ++ 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 = """ ++ ++ my_vm ++ 1048576 ++ 1048576 ++ 1 ++ restart ++ ++ hvm ++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ """.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 = """ ++ ++ my_vm ++ 1048576 ++ 1048576 ++ 1 ++ restart ++ ++ hvm ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++ ++ ++