Skip to content

Commit e45fee0

Browse files
committed
Handle global disposable preload feature
For: QubesOS/qubes-issues#1512 For: QubesOS/qubes-desktop-linux-manager#262
1 parent bc20def commit e45fee0

9 files changed

Lines changed: 620 additions & 103 deletions

File tree

qubes/app.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,9 +1732,12 @@ def on_property_set_default_netvm(
17321732
"property-reset:netvm", name="netvm", oldvalue=oldvalue
17331733
)
17341734

1735-
@qubes.events.handler("property-set:default_dispvm")
1735+
@qubes.events.handler(
1736+
"property-set:default_dispvm",
1737+
"property-reset:default_dispvm",
1738+
)
17361739
def on_property_set_default_dispvm(
1737-
self, event, name, newvalue, oldvalue=None
1740+
self, event, name, newvalue=None, oldvalue=None
17381741
):
17391742
# pylint: disable=unused-argument
17401743
for vm in self.domains:
@@ -1749,6 +1752,39 @@ def on_property_set_default_dispvm(
17491752
oldvalue=oldvalue,
17501753
)
17511754

1755+
if not newvalue:
1756+
newvalue = self.default_dispvm
1757+
if newvalue == oldvalue:
1758+
return
1759+
old_appvm = None
1760+
appvm = None
1761+
if oldvalue:
1762+
old_appvm = self.domains[oldvalue]
1763+
if newvalue:
1764+
appvm = self.domains[newvalue]
1765+
if (
1766+
old_appvm
1767+
and old_appvm.get_feat_global_preload_max() is not None
1768+
and old_appvm.is_global_preload_distinct()
1769+
):
1770+
reason = "it was the default_dispvm"
1771+
asyncio.ensure_future(
1772+
old_appvm.fire_event_async(
1773+
"domain-preload-dispvm-start", reason=reason
1774+
)
1775+
)
1776+
if (
1777+
appvm
1778+
and appvm.get_feat_global_preload_max() is not None
1779+
and appvm.is_global_preload_distinct()
1780+
):
1781+
reason = "it became the default_dispvm"
1782+
asyncio.ensure_future(
1783+
appvm.fire_event_async(
1784+
"domain-preload-dispvm-start", reason=reason
1785+
)
1786+
)
1787+
17521788
@qubes.events.handler("property-pre-set:default_kernel")
17531789
# pylint: disable-next=invalid-name
17541790
def on_property_pre_set_default_kernel(

qubes/tests/app.py

Lines changed: 116 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -689,13 +689,6 @@ def test_101_property_migrate_label(self):
689689

690690

691691
class TC_90_Qubes(qubes.tests.QubesTestCase):
692-
def tearDown(self):
693-
try:
694-
os.unlink("/tmp/qubestest.xml")
695-
except:
696-
pass
697-
super().tearDown()
698-
699692
def setUp(self):
700693
super(TC_90_Qubes, self).setUp()
701694
self.app = qubes.Qubes(
@@ -707,12 +700,43 @@ def setUp(self):
707700
self.template = self.app.add_new_vm(
708701
"TemplateVM", name="test-template", label="green"
709702
)
703+
self.template.features["qrexec"] = True
704+
self.template.features["supported-rpc.qubes.WaitForRunningSystem"] = (
705+
True
706+
)
707+
self.appvm = self.app.add_new_vm(
708+
"AppVM",
709+
name="test-dvm",
710+
template=self.template,
711+
label="red",
712+
)
713+
self.appvm_alt = self.app.add_new_vm(
714+
qubes.vm.appvm.AppVM,
715+
name="test-alt-dvm",
716+
template=self.template,
717+
label="red",
718+
)
719+
for qube in [self.appvm, self.appvm_alt]:
720+
qube.features["gui"] = False
721+
qube.template_for_dispvms = True
722+
self.app.save()
723+
self.emitter = qubes.tests.TestEmitter()
724+
725+
def tearDown(self):
726+
try:
727+
os.unlink("/tmp/qubestest.xml")
728+
except:
729+
pass
730+
del self.emitter
731+
super().tearDown()
710732

711733
def cleanup_qubes(self):
712734
self.app.close()
713735
del self.app
714736
try:
715737
del self.template
738+
del self.appvm
739+
del self.appvm_alt
716740
except AttributeError:
717741
pass
718742

@@ -932,11 +956,7 @@ def test_116_remotevm_add_and_remove(self):
932956

933957
assert remotevm1 in self.app.domains
934958
del self.app.domains["remote-vm1"]
935-
936-
self.assertCountEqual(
937-
{d.name for d in self.app.domains},
938-
{"dom0", "test-template", "test-vm", "remote-vm2"},
939-
)
959+
assert "remote-vm1" not in self.app.domains
940960

941961
def test_117_remotevm_status(self):
942962
remotevm1 = self.app.add_new_vm(
@@ -1096,6 +1116,90 @@ def test_207_default_kernel(self):
10961116
existence.side_effect = [True, False]
10971117
self.app.default_kernel = "unittest_GNU_Hurd_1.0.0"
10981118

1119+
def test_300_preload_default_dispvm(self):
1120+
"""Fire event for new setting from no previous one."""
1121+
self.appvm.features["preload-dispvm-max"] = "1"
1122+
with mock.patch.object(self.appvm, "fire_event_async") as mock_events:
1123+
self.app.default_dispvm = self.appvm
1124+
mock_events.assert_not_called()
1125+
1126+
self.app.default_dispvm = None
1127+
del self.appvm.features["preload-dispvm-max"]
1128+
self.app.domains["dom0"].features["preload-dispvm-max"] = "1"
1129+
with mock.patch.object(self.appvm, "fire_event_async") as mock_events:
1130+
self.app.default_dispvm = self.appvm
1131+
mock_events.assert_called_once_with(
1132+
"domain-preload-dispvm-start", reason=mock.ANY
1133+
)
1134+
1135+
def test_301_preload_default_dispvm_change(self):
1136+
"""Fire event for old and new setting."""
1137+
self.appvm.features["preload-dispvm-max"] = "1"
1138+
self.app.default_dispvm = self.appvm
1139+
self.appvm_alt.features["preload-dispvm-max"] = "1"
1140+
# Global is not set and thus there are no events.
1141+
with mock.patch.object(
1142+
self.appvm, "fire_event_async"
1143+
) as mock_old, mock.patch.object(
1144+
self.appvm_alt, "fire_event_async"
1145+
) as mock_new:
1146+
self.app.default_dispvm = self.appvm_alt
1147+
mock_old.assert_not_called()
1148+
mock_new.assert_not_called()
1149+
1150+
self.app.domains["dom0"].features["preload-dispvm-max"] = "1"
1151+
self.app.default_dispvm = self.appvm
1152+
self.appvm.features["preload-dispvm-max"] = "2"
1153+
self.appvm_alt.features["preload-dispvm-max"] = "2"
1154+
with mock.patch.object(
1155+
self.appvm, "fire_event_async"
1156+
) as mock_old, mock.patch.object(
1157+
self.appvm_alt, "fire_event_async"
1158+
) as mock_new:
1159+
self.app.default_dispvm = self.appvm_alt
1160+
mock_old.assert_called_once_with(
1161+
"domain-preload-dispvm-start", reason=mock.ANY
1162+
)
1163+
mock_new.assert_called_once_with(
1164+
"domain-preload-dispvm-start", reason=mock.ANY
1165+
)
1166+
1167+
def test_302_preload_default_dispvm_change_noop(self):
1168+
"""If global feature has the same value as the new and old setting,
1169+
don't fire the event.
1170+
"""
1171+
self.appvm.features["preload-dispvm-max"] = "1"
1172+
self.appvm_alt.features["preload-dispvm-max"] = "1"
1173+
self.app.domains["dom0"].features["preload-dispvm-max"] = "1"
1174+
self.app.default_dispvm = self.appvm
1175+
with mock.patch.object(
1176+
self.appvm, "fire_event_async"
1177+
) as mock_old, mock.patch.object(
1178+
self.appvm_alt, "fire_event_async"
1179+
) as mock_new:
1180+
self.app.default_dispvm = self.appvm_alt
1181+
mock_old.assert_not_called()
1182+
mock_new.assert_not_called()
1183+
1184+
def test_303_preload_default_dispvm_change_partial_noop(self):
1185+
"""If global feature has the same value as the new or old setting,
1186+
don't fire the event.
1187+
"""
1188+
self.appvm.features["preload-dispvm-max"] = "2"
1189+
self.appvm_alt.features["preload-dispvm-max"] = "1"
1190+
self.app.domains["dom0"].features["preload-dispvm-max"] = "2"
1191+
self.app.default_dispvm = self.appvm
1192+
with mock.patch.object(
1193+
self.appvm, "fire_event_async"
1194+
) as mock_old, mock.patch.object(
1195+
self.appvm_alt, "fire_event_async"
1196+
) as mock_new:
1197+
self.app.default_dispvm = self.appvm_alt
1198+
mock_old.assert_not_called()
1199+
mock_new.assert_called_once_with(
1200+
"domain-preload-dispvm-start", reason=mock.ANY
1201+
)
1202+
10991203
@qubes.tests.skipUnlessGit
11001204
def test_900_example_xml_in_doc(self):
11011205
self.assertXMLIsValid(

0 commit comments

Comments
 (0)