diff --git a/.coveragerc b/.coveragerc
index ff6415d7..13210d9c 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -5,3 +5,4 @@ exclude_lines =
# Don't complain if tests don't hit defensive assertion code:
raise NotImplementedError
+ .*raise_programming_error.*
diff --git a/tests/data/cli/compare/virt-xml-edit-cpu-host-copy.xml b/tests/data/cli/compare/virt-xml-edit-cpu-host-copy.xml
new file mode 100644
index 00000000..7b0873e1
--- /dev/null
+++ b/tests/data/cli/compare/virt-xml-edit-cpu-host-copy.xml
@@ -0,0 +1,2 @@
+Domain 'test-many-devices' defined successfully.
+Changes will take effect after the domain is fully powered off.
\ No newline at end of file
diff --git a/tests/data/xmlparse/change-cpumode-out.xml b/tests/data/xmlparse/change-cpumode-out.xml
index 23cc3e3c..f8a36a0a 100644
--- a/tests/data/xmlparse/change-cpumode-out.xml
+++ b/tests/data/xmlparse/change-cpumode-out.xml
@@ -16,6 +16,6 @@
- qemu64
+ Skylake-Client-IBRS
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 17b26d4b..2ef766d4 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1161,6 +1161,7 @@ c.add_compare("4a64cc71-19c4-2fd0-2323-3050941ea3c3 --edit --boot network,cdrom"
c.add_compare("--confirm 1 --edit --cpu host-passthrough", "prompt-response", input_text="yes") # prompt response, also using domid lookup
c.add_compare("--edit --print-diff --qemu-commandline clearxml=yes", "edit-clearxml-qemu-commandline", input_file=(XMLDIR + "/virtxml-qemu-commandline-clear.xml"))
c.add_compare("--connect %(URI-KVM)s test-hyperv-uefi --edit --boot uefi", "hyperv-uefi-collision")
+c.add_compare("--connect %(URI-KVM)s test-many-devices --edit --cpu host-copy", "edit-cpu-host-copy")
c = vixml.add_category("simple edit diff", "test-for-virtxml --edit --print-diff --define")
diff --git a/tests/test_xmlconfig.py b/tests/test_xmlconfig.py
index dccc6a17..5d7060ea 100644
--- a/tests/test_xmlconfig.py
+++ b/tests/test_xmlconfig.py
@@ -146,6 +146,9 @@ class TestXMLMisc(unittest.TestCase):
def testCPUTopology(self):
# Test CPU topology determining
cpu = virtinst.DomainCpu(self.conn)
+ cpu.set_topology_defaults(6)
+ assert cpu.sockets is None
+
cpu.sockets = "2"
cpu.set_topology_defaults(6)
self.assertEqual([cpu.sockets, cpu.cores, cpu.threads], [2, 3, 1])
diff --git a/tests/test_xmlparse.py b/tests/test_xmlparse.py
index f4fc562c..526e2943 100644
--- a/tests/test_xmlparse.py
+++ b/tests/test_xmlparse.py
@@ -345,9 +345,13 @@ class XMLParseTest(unittest.TestCase):
self._alter_compare(guest.get_xml(), outfile)
def testAlterCpuMode(self):
- guest, outfile = self._get_test_content("change-cpumode")
-
+ xml = open(DATADIR + "change-cpumode-in.xml").read()
+ outfile = DATADIR + "change-cpumode-out.xml"
+ conn = utils.URIs.openconn(utils.URIs.kvm_q35)
+ guest = virtinst.Guest(conn, xml)
check = self._make_checker(guest.cpu)
+
+ guest.cpu.model = "foo"
check("mode", "host-passthrough")
guest.cpu.check_security_features(guest)
check("secure", False)
@@ -360,8 +364,25 @@ class XMLParseTest(unittest.TestCase):
guest.cpu.check_security_features(guest)
check("secure", False)
+ # Test actually filling in security values, and removing them
+ guest.cpu.secure = True
+ guest.cpu.set_model(guest, "Skylake-Client-IBRS")
+ guest.cpu.check_security_features(guest)
+ check("secure", True)
+ guest.cpu.set_model(guest, "EPYC-IBPB")
+ guest.cpu.check_security_features(guest)
+ check("secure", True)
+ guest.cpu.secure = False
+ guest.cpu.set_model(guest, "Skylake-Client-IBRS")
+ guest.cpu.check_security_features(guest)
+ check("secure", False)
self._alter_compare(guest.get_xml(), outfile)
+ # Hits a codepath when domcaps don't provide the needed info
+ guest = virtinst.Guest(self.conn, xml)
+ guest.cpu.check_security_features(guest)
+ assert guest.cpu.secure is False
+
def testAlterDisk(self):
"""
Test changing DeviceDisk() parameters after parsing
@@ -1561,3 +1582,17 @@ class XMLParseTest(unittest.TestCase):
# Little test for DeviceAddress.pretty_desc
assert devs[-1].address.pretty_desc() == "0:0:0:3"
+
+ def testCPUHostModelOnly(self):
+ """
+ Hit the validation paths for default HOST_MODEL_ONLY
+ """
+ guest = virtinst.Guest(self.kvmconn)
+ guest.x86_cpu_default = guest.cpu.SPECIAL_MODE_HOST_MODEL_ONLY
+ guest.set_defaults(guest)
+ assert guest.cpu.model == "Opteron_G4"
+
+ # pylint: disable=protected-access
+ guest.cpu.model = "idontexist"
+ guest.cpu._validate_default_host_model_only(guest)
+ assert guest.cpu.model is None
diff --git a/virtinst/domain/cpu.py b/virtinst/domain/cpu.py
index 5fb3d226..8620b784 100644
--- a/virtinst/domain/cpu.py
+++ b/virtinst/domain/cpu.py
@@ -7,6 +7,7 @@
from ..logger import log
from ..xmlbuilder import XMLBuilder, XMLProperty, XMLChildProperty
+from .. import xmlutil
class _CPUCellSibling(XMLBuilder):
@@ -106,8 +107,8 @@ class DomainCpu(XMLBuilder):
self.clear()
self.set_model(guest, self.conn.caps.host.cpu.model)
else:
- raise RuntimeError("programming error: unknown "
- "special cpu mode '%s'" % val)
+ xmlutil.raise_programming_error(
+ True, "unknown special cpu mode '%s'" % val)
self.special_mode_was_set = True
@@ -129,7 +130,6 @@ class DomainCpu(XMLBuilder):
"""
domcaps = guest.lookup_domcaps()
features = domcaps.get_cpu_security_features()
-
if len(features) == 0:
self.secure = False
return
@@ -187,7 +187,7 @@ class DomainCpu(XMLBuilder):
cpu = self.conn.caps.host.cpu
model = cpu.model
fallback = None
- if not model:
+ if not model: # pragma: no cover
raise ValueError(_("No host CPU reported in capabilities"))
self.mode = "custom"
@@ -218,18 +218,13 @@ class DomainCpu(XMLBuilder):
def set_topology_defaults(self, vcpus):
"""
- Fill in unset topology values, using the passed vcpus count if
- required
+ Fill in unset topology values, using the passed vcpus count.
+ Will not set topology from scratch, this just fills in missing
+ topology values.
"""
- if vcpus is None:
- if self.sockets is None:
- self.sockets = 1
- if self.threads is None:
- self.threads = 1
- if self.cores is None:
- self.cores = 1
+ if not self.has_topology():
+ return
- vcpus = int(vcpus or 0)
if not self.sockets:
if not self.cores:
self.sockets = vcpus // self.threads
@@ -273,12 +268,12 @@ class DomainCpu(XMLBuilder):
# is not actually supported by qemu/kvm
# combo which will be reported in
if not self.model:
- return
+ return # pragma: no cover
domcaps = guest.lookup_domcaps()
domcaps_mode = domcaps.cpu.get_mode("custom")
if not domcaps_mode:
- return
+ return # pragma: no cover
cpu_model = domcaps_mode.get_model(self.model)
if cpu_model and cpu_model.usable != "no":