Skip to content

Commit ab3f759

Browse files
committed
Merge remote-tracking branch 'origin/pr/718'
* origin/pr/718: Consistent preload template gathering Add global preload to template gathering
2 parents c3abe12 + 10b3de5 commit ab3f759

File tree

6 files changed

+135
-51
lines changed

6 files changed

+135
-51
lines changed

linux/aux-tools/preload-dispvm

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,43 @@ import concurrent.futures
1212
import qubesadmin
1313

1414

15-
def get_max(qube):
16-
return int(qube.features.get("preload-dispvm-max", 0) or 0)
15+
def get_preload_max(qube) -> int | None:
16+
value = qube.features.get("preload-dispvm-max", None)
17+
return int(value) if value else value
1718

1819

1920
async def main():
2021
app = qubesadmin.Qubes()
2122
domains = app.domains
2223
default_dispvm = getattr(app, "default_dispvm", None)
24+
global_max = get_preload_max(domains["dom0"])
2325
appvms = [
2426
qube
2527
for qube in domains
26-
if get_max(qube) > 0
27-
and (
28-
(
29-
qube.klass == "AppVM"
30-
and getattr(qube, "template_for_dispvms", False)
28+
if (
29+
qube.klass == "AppVM"
30+
and getattr(qube, "template_for_dispvms", False)
31+
and (
32+
(qube != default_dispvm and get_preload_max(qube))
33+
or (
34+
(qube == default_dispvm and global_max)
35+
or (global_max is None and get_preload_max(qube))
36+
)
3137
)
32-
or (qube.name == "dom0" and default_dispvm)
3338
)
3439
]
3540
method = "admin.vm.CreateDisposable"
3641
loop = asyncio.get_running_loop()
3742
tasks = []
38-
if "dom0" in appvms and default_dispvm in appvms:
39-
appvms.remove(default_dispvm)
4043
with concurrent.futures.ThreadPoolExecutor() as executor:
4144
for qube in appvms:
42-
maximum = get_max(qube)
43-
msg = f"{qube}:{maximum}"
44-
if qube.name == "dom0":
45-
qube = default_dispvm
46-
msg = "global:" + msg
47-
print(msg)
45+
if qube == default_dispvm and global_max is not None:
46+
maximum = global_max
47+
msg = f"global:{qube}:{maximum}"
48+
else:
49+
maximum = get_preload_max(qube)
50+
msg = f"{qube}:{maximum}"
51+
print(repr(msg))
4852
exec_args = qube.qubesd_call, qube.name, method, "preload-autostart"
4953
future = loop.run_in_executor(executor, *exec_args)
5054
tasks.append(future)

qubes/api/internal.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,7 @@ async def suspend_pre(self):
265265
:return:
266266
"""
267267

268-
preload_templates = qubes.vm.dispvm.get_preload_templates(
269-
self.app.domains
270-
)
268+
preload_templates = qubes.vm.dispvm.get_preload_templates(self.app)
271269
for qube in preload_templates:
272270
qube.remove_preload_excess(0)
273271

@@ -416,9 +414,7 @@ async def suspend_post(self):
416414
"qubes.SuspendPostAll",
417415
)
418416

419-
preload_templates = qubes.vm.dispvm.get_preload_templates(
420-
self.app.domains
421-
)
417+
preload_templates = qubes.vm.dispvm.get_preload_templates(self.app)
422418
for qube in preload_templates:
423419
asyncio.ensure_future(
424420
qube.fire_event_async("domain-preload-dispvm-autostart")

qubes/tests/integ/dispvm.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -298,25 +298,23 @@ async def cleanup_preload_run(self, qube):
298298

299299
def cleanup_preload(self):
300300
logger.info("start")
301-
if "preload-dispvm-max" in self.app.domains[self.disp_base].features:
302-
logger.info("has local preload")
303-
self.loop.run_until_complete(
304-
self.cleanup_preload_run(self.disp_base)
305-
)
306-
logger.info("deleting local feature")
307-
del self.disp_base.features["preload-dispvm-max"]
308-
if "preload-dispvm-max" in self.app.domains["dom0"].features:
309-
logger.info("has global preload")
310-
default_dispvm = self.app.default_dispvm
311-
if default_dispvm:
312-
self.loop.run_until_complete(
313-
self.cleanup_preload_run(default_dispvm)
314-
)
315-
logger.info("deleting global feature")
316-
del self.app.domains["dom0"].features["preload-dispvm-max"]
301+
default_dispvm = self.app.default_dispvm
302+
# Clean features from all qubes to avoid them being considered by
303+
# tests that target preloads on the whole system, such as
304+
# `/usr/lib/qubes/preload-dispvm`.
317305
if "preload-dispvm-threshold" in self.app.domains["dom0"].features:
318306
logger.info("deleting global threshold feature")
319307
del self.app.domains["dom0"].features["preload-dispvm-threshold"]
308+
for qube in self.app.domains:
309+
if "preload-dispvm-max" not in qube.features:
310+
continue
311+
logger.info("removing preloaded disposables: '%s'", qube.name)
312+
if qube == default_dispvm:
313+
self.loop.run_until_complete(
314+
self.cleanup_preload_run(default_dispvm)
315+
)
316+
logger.info("deleting max preload feature")
317+
del qube.features["preload-dispvm-max"]
320318
logger.info("end")
321319

322320
async def no_preload(self):
@@ -616,6 +614,7 @@ def test_017_preload_autostart(self):
616614
self.app.default_dispvm = self.disp_base
617615

618616
preload_max = 1
617+
logger.info("no refresh to be made")
619618
proc = self.loop.run_until_complete(
620619
asyncio.create_subprocess_exec("/usr/lib/qubes/preload-dispvm")
621620
)
@@ -624,13 +623,14 @@ def test_017_preload_autostart(self):
624623
)
625624
self.assertEqual(self.disp_base.get_feat_preload(), [])
626625

626+
logger.info("refresh to be made")
627627
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
628628
self.loop.run_until_complete(self.wait_preload(preload_max))
629629
old_preload = self.disp_base.get_feat_preload()
630630
proc = self.loop.run_until_complete(
631631
asyncio.create_subprocess_exec("/usr/lib/qubes/preload-dispvm")
632632
)
633-
self.loop.run_until_complete(asyncio.wait_for(proc.wait(), timeout=30))
633+
self.loop.run_until_complete(asyncio.wait_for(proc.wait(), timeout=40))
634634
preload_dispvm = self.disp_base.get_feat_preload()
635635
self.assertEqual(len(old_preload), preload_max)
636636
self.assertEqual(len(preload_dispvm), preload_max)
@@ -639,6 +639,7 @@ def test_017_preload_autostart(self):
639639
f"old_preload={old_preload} preload_dispvm={preload_dispvm}",
640640
)
641641

642+
logger.info("global refresh to be made")
642643
preload_max += 1
643644
self.adminvm.features["preload-dispvm-max"] = str(preload_max)
644645
self.loop.run_until_complete(self.wait_preload(preload_max))
@@ -647,7 +648,7 @@ def test_017_preload_autostart(self):
647648
proc = self.loop.run_until_complete(
648649
asyncio.create_subprocess_exec("/usr/lib/qubes/preload-dispvm")
649650
)
650-
self.loop.run_until_complete(asyncio.wait_for(proc.wait(), timeout=30))
651+
self.loop.run_until_complete(asyncio.wait_for(proc.wait(), timeout=40))
651652
preload_dispvm = self.disp_base.get_feat_preload()
652653
self.assertEqual(len(old_preload), preload_max)
653654
self.assertEqual(len(preload_dispvm), preload_max)

qubes/tests/vm/dispvm.py

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,16 @@
3535
class TestApp(qubes.tests.vm.TestApp):
3636
def __init__(self):
3737
super(TestApp, self).__init__()
38-
self.qid_counter = 1
38+
self.qid_counter = 0
3939

4040
def add_new_vm(self, cls, **kwargs):
4141
qid = self.qid_counter
42-
self.qid_counter += 1
43-
vm = cls(self, None, qid=qid, **kwargs)
42+
if self.qid_counter == 0:
43+
self.qid_counter += 1
44+
vm = cls(self, None, **kwargs)
45+
else:
46+
self.qid_counter += 1
47+
vm = cls(self, None, qid=qid, **kwargs)
4448
self.domains[vm.name] = vm
4549
self.domains[vm] = vm
4650
return vm
@@ -58,6 +62,8 @@ def setUp(self):
5862
name="linux-kernel"
5963
)
6064
self.app.vmm.offline_mode = True
65+
self.app.default_dispvm = None
66+
self.adminvm = self.app.add_new_vm(qubes.vm.adminvm.AdminVM)
6167
self.template = self.app.add_new_vm(
6268
qubes.vm.templatevm.TemplateVM, name="test-template", label="red"
6369
)
@@ -68,8 +74,12 @@ def setUp(self):
6874
template=self.template,
6975
label="red",
7076
)
71-
self.app.domains[self.appvm.name] = self.appvm
72-
self.app.domains[self.appvm] = self.appvm
77+
self.appvm_alt = self.app.add_new_vm(
78+
qubes.vm.appvm.AppVM,
79+
name="test-vm-alt",
80+
template=self.template,
81+
label="red",
82+
)
7383
self.addCleanup(self.cleanup_dispvm)
7484
self.emitter = qubes.tests.TestEmitter()
7585

@@ -83,10 +93,15 @@ def cleanup_dispvm(self):
8393
del self.dispvm
8494
self.template.close()
8595
self.appvm.close()
86-
del self.template
96+
self.appvm_alt.close()
8797
del self.appvm
98+
del self.appvm_alt
99+
del self.template
100+
del self.adminvm
101+
self.app.close()
88102
self.app.domains.clear()
89103
self.app.pools.clear()
104+
del self.app
90105

91106
async def mock_coro(self, *args, **kwargs):
92107
pass
@@ -275,6 +290,49 @@ def test_000_from_appvm_preload_fill_gap(
275290
mock_symlink.assert_not_called()
276291
mock_makedirs.assert_called_once()
277292

293+
def test_000_get_preload_max(self):
294+
self.assertEqual(qubes.vm.dispvm.get_preload_max(self.appvm), None)
295+
self.appvm.features["supported-rpc.qubes.WaitForRunningSystem"] = True
296+
self.appvm.features["preload-dispvm-max"] = 1
297+
self.assertEqual(qubes.vm.dispvm.get_preload_max(self.appvm), 1)
298+
self.assertEqual(qubes.vm.dispvm.get_preload_max(self.adminvm), None)
299+
self.adminvm.features["preload-dispvm-max"] = ""
300+
self.assertEqual(qubes.vm.dispvm.get_preload_max(self.adminvm), "")
301+
self.adminvm.features["preload-dispvm-max"] = 2
302+
self.assertEqual(qubes.vm.dispvm.get_preload_max(self.adminvm), 2)
303+
304+
def test_000_get_preload_templates(self):
305+
get_preload_templates = qubes.vm.dispvm.get_preload_templates
306+
self.assertEqual(get_preload_templates(self.app), [])
307+
self.appvm.template_for_dispvms = True
308+
self.appvm_alt.template_for_dispvms = True
309+
self.assertEqual(get_preload_templates(self.app), [])
310+
311+
self.appvm.features["supported-rpc.qubes.WaitForRunningSystem"] = True
312+
self.appvm_alt.features["supported-rpc.qubes.WaitForRunningSystem"] = (
313+
True
314+
)
315+
self.appvm.features["preload-dispvm-max"] = 1
316+
self.appvm_alt.features["preload-dispvm-max"] = 0
317+
self.assertEqual(get_preload_templates(self.app), [self.appvm])
318+
319+
self.adminvm.features["preload-dispvm-max"] = ""
320+
# Still not default_dispvm
321+
self.appvm_alt.features["preload-dispvm-max"] = 1
322+
self.assertEqual(
323+
get_preload_templates(self.app), [self.appvm, self.appvm_alt]
324+
)
325+
326+
with mock.patch.object(self.appvm, "fire_event_async"):
327+
self.app.default_dispvm = self.appvm
328+
self.assertEqual(get_preload_templates(self.app), [self.appvm_alt])
329+
330+
self.app.default_dispvm = None
331+
self.adminvm.features["preload-dispvm-max"] = 1
332+
self.assertEqual(
333+
get_preload_templates(self.app), [self.appvm, self.appvm_alt]
334+
)
335+
278336
def test_001_from_appvm_reject_not_allowed(self):
279337
with self.assertRaises(qubes.exc.QubesException):
280338
dispvm = self.loop.run_until_complete(

qubes/tests/vm/mix/dvmtemplate.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,9 @@ def test_013_dvm_preload_get_treshold(self):
259259
self.adminvm.features["preload-dispvm-threshold"] = value
260260
threshold = self.appvm.get_feat_preload_threshold()
261261
self.assertEqual(threshold, int(value or 0) * 1024**2)
262+
263+
def test_100_get_preload_templates(self):
264+
print(qubes.vm.dispvm.get_preload_templates(self.app))
265+
self.appvm.features["supported-rpc.qubes.WaitForRunningSystem"] = True
266+
self.appvm.features["preload-dispvm-max"] = 1
267+
self.assertEqual(qubes.vm.dispvm.get_preload_max(self.appvm), 1)

qubes/vm/dispvm.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,33 @@ def _setter_template(self, prop, value):
4040
return value
4141

4242

43-
def get_preload_templates(domains) -> list:
44-
return [
43+
# Keep in sync with linux/aux-tools/preload-dispvm
44+
def get_preload_max(qube) -> int | None:
45+
value = qube.features.get("preload-dispvm-max", None)
46+
return int(value) if value else value
47+
48+
49+
# Keep in sync with linux/aux-tools/preload-dispvm
50+
def get_preload_templates(app) -> list:
51+
domains = app.domains
52+
default_dispvm = getattr(app, "default_dispvm", None)
53+
global_max = get_preload_max(domains["dom0"])
54+
appvms = [
4555
qube
4656
for qube in domains
47-
if int(qube.features.get("preload-dispvm-max", 0) or 0) > 0
48-
and qube.klass == "AppVM"
49-
and getattr(qube, "template_for_dispvms", False)
57+
if (
58+
qube.klass == "AppVM"
59+
and getattr(qube, "template_for_dispvms", False)
60+
and (
61+
(qube != default_dispvm and get_preload_max(qube))
62+
or (
63+
(qube == default_dispvm and global_max)
64+
or (global_max is None and get_preload_max(qube))
65+
)
66+
)
67+
)
5068
]
69+
return appvms
5170

5271

5372
class DispVM(qubes.vm.qubesvm.QubesVM):

0 commit comments

Comments
 (0)