mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 11:46:49 +00:00
feat(instances): a VM's TPM 2.0 is switched on and off on its Options tab
A VM made without a TPM, a Windows 10 to be upgraded to Windows 11, had no way to get one but the XML editor. The Options tab has a TPM 2.0 switch, as the RNG one: it adds an emulated TPM of the host's model to the definition for the next start, or removes it. Its state stays on the host until the VM is undefined, so a TPM added again has it back. A TPM the switch did not make (passthrough, 1.2, encrypted, with its own state path) is left to the XML editor, and a host without swtpm says so.
This commit is contained in:
parent
2a05576e18
commit
e35342bff1
10 changed files with 288 additions and 130 deletions
|
|
@ -2968,6 +2968,37 @@ class wvmInstance(wvmConnect):
|
|||
tree.find("devices").append(etree.fromstring(RNG_DEVICE))
|
||||
self._defineXML(etree.tostring(tree).decode())
|
||||
|
||||
def get_tpm(self):
|
||||
"""The persistent definition's TPM: "emulator" for a plain emulated
|
||||
TPM 2.0, the panel's own, "custom" for another one, None without one."""
|
||||
tpms = etree.fromstring(self._XMLDesc(PERSISTENT_XML)).xpath("devices/tpm")
|
||||
if not tpms:
|
||||
return None
|
||||
# libvirt adds only an alias; encryption, a state path or another
|
||||
# backend setting is kept by the XML editor, not dropped by the switch
|
||||
own = ("backend[@type='emulator' and (not(@version) or @version='2.0') and not(*)"
|
||||
" and not(@*[name()!='type' and name()!='version'])] and not(*[not(self::backend or self::alias)])")
|
||||
if len(tpms) == 1 and tpms[0].xpath(own):
|
||||
return "emulator"
|
||||
return "custom"
|
||||
|
||||
def set_tpm(self, enabled):
|
||||
"""Adds an emulated TPM 2.0 to the persistent definition, or removes it;
|
||||
another TPM is left to the XML editor. Its state stays on the host until
|
||||
the VM is undefined, so a TPM added again has it back."""
|
||||
if self.get_tpm() == "custom":
|
||||
raise util.OperationError("The VM has its own TPM: change it in the XML editor")
|
||||
tree = etree.fromstring(self._XMLDesc(PERSISTENT_XML))
|
||||
for tpm in tree.xpath("devices/tpm"):
|
||||
tpm.getparent().remove(tpm)
|
||||
if enabled:
|
||||
model = self.get_tpm_model(self.get_arch(), self.get_machine_type())
|
||||
if not model:
|
||||
raise util.OperationError("This host offers no TPM 2.0: install swtpm on it")
|
||||
tree.find("devices").append(
|
||||
etree.fromstring(f"<tpm model='{model}'><backend type='emulator' version='2.0'/></tpm>"))
|
||||
self._defineXML(etree.tostring(tree).decode())
|
||||
|
||||
def get_on_crash(self):
|
||||
"""What the next start does when the guest crashes; libvirt's default is destroy."""
|
||||
return etree.fromstring(self._XMLDesc(PERSISTENT_XML)).findtext("on_crash") or "destroy"
|
||||
|
|
|
|||
|
|
@ -176,6 +176,45 @@ class DeviceEditsTestCase(unittest.TestCase):
|
|||
p.set_rng(False)
|
||||
p._defineXML.assert_not_called()
|
||||
|
||||
def test_an_emulated_tpm_2_is_added_with_the_host_s_model_or_left_to_the_xml_editor(self):
|
||||
p = proxy()
|
||||
p.get_tpm_model = lambda arch, machine: "tpm-crb"
|
||||
p.get_arch, p.get_machine_type = lambda: "x86_64", lambda: "q35"
|
||||
self.assertIsNone(p.get_tpm())
|
||||
p.set_tpm(True)
|
||||
tpm = defined(p).find("devices/tpm")
|
||||
self.assertEqual((tpm.get("model"), dict(tpm.find("backend").attrib)), ("tpm-crb", {"type": "emulator", "version": "2.0"}))
|
||||
added = etree.tostring(defined(p)).decode()
|
||||
p._XMLDesc = lambda flags: added
|
||||
self.assertEqual(p.get_tpm(), "emulator")
|
||||
p.set_tpm(False)
|
||||
self.assertIsNone(defined(p).find("devices/tpm"))
|
||||
# a host without swtpm adds none
|
||||
p = proxy()
|
||||
p.get_tpm_model = lambda arch, machine: None
|
||||
p.get_arch, p.get_machine_type = lambda: "x86_64", lambda: "q35"
|
||||
with self.assertRaises(util.OperationError):
|
||||
p.set_tpm(True)
|
||||
p._defineXML.assert_not_called()
|
||||
# libvirt's alias leaves it the panel's
|
||||
p = proxy()
|
||||
p._XMLDesc = lambda flags: PERSISTENT.replace(
|
||||
"<video>", "<tpm model='tpm-crb'><backend type='emulator' version='2.0'/><alias name='tpm0'/></tpm><video>")
|
||||
self.assertEqual(p.get_tpm(), "emulator")
|
||||
# a passthrough TPM, a TPM 1.2, an encrypted one or one with its own state path is the VM's own
|
||||
for backend in ("<backend type='passthrough'><device path='/dev/tpm0'/></backend>",
|
||||
"<backend type='emulator' version='1.2'/>",
|
||||
"<backend type='emulator' version='2.0'><encryption secret='aaaaaaaa-0000-4000-8000-000000000001'/></backend>",
|
||||
"<backend type='emulator' version='2.0'><source type='dir' path='/srv/tpm'/></backend>",
|
||||
"<backend type='emulator' version='2.0' persistent_state='yes'/>"):
|
||||
with self.subTest(backend):
|
||||
p = proxy()
|
||||
p._XMLDesc = lambda flags, b=backend: PERSISTENT.replace("<video>", f"<tpm model='tpm-tis'>{b}</tpm><video>")
|
||||
self.assertEqual(p.get_tpm(), "custom")
|
||||
with self.assertRaises(util.OperationError):
|
||||
p.set_tpm(False)
|
||||
p._defineXML.assert_not_called()
|
||||
|
||||
def test_the_crash_action_is_restart_or_stop(self):
|
||||
p = proxy()
|
||||
self.assertEqual(p.get_on_crash(), "destroy") # libvirt's default
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue