Skip to content

Commit a827d7f

Browse files
committed
Late GUID for preloaded disposables
- Autostarted applications don't show when preloading; and - Preloaded qubes paused before the session will have a working GUI when the session starts. For: QubesOS/qubes-issues#1512 For: QubesOS/qubes-issues#9940 For: QubesOS/qubes-issues#9907 Requires: QubesOS/qubes-core-admin-client#359
1 parent 78aacd1 commit a827d7f

8 files changed

Lines changed: 105 additions & 124 deletions

File tree

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ all:
179179

180180
install:
181181
ifeq ($(OS),Linux)
182-
$(MAKE) install -C linux/autostart
183182
$(MAKE) install -C linux/systemd
184183
$(MAKE) install -C linux/aux-tools
185184
$(MAKE) install -C linux/system-config

linux/autostart/Makefile

Lines changed: 0 additions & 6 deletions
This file was deleted.

linux/autostart/qubes-preload-dispvm.desktop

Lines changed: 0 additions & 9 deletions
This file was deleted.

qubes/tests/api_admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3861,7 +3861,7 @@ def test_643_vm_create_disposable_preload_autostart(
38613861
"domain-preload-dispvm-autostart", self._test_event_handler
38623862
)
38633863
self.vm.features["qrexec"] = "1"
3864-
self.vm.features["supported-rpc.qubes.WaitForSession"] = "1"
3864+
self.vm.features["supported-rpc.qubes.WaitForRunningSystem"] = "1"
38653865
self.vm.features["preload-dispvm-max"] = "1"
38663866
for _ in range(10):
38673867
if len(self.vm.get_feat_preload()) == 1:

qubes/tests/integ/dispvm.py

Lines changed: 83 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,22 @@
2929
from unittest.mock import patch, mock_open
3030
import asyncio
3131
import sys
32+
import logging
3233

3334
import qubes.config
3435
import qubes.tests
3536
import qubesadmin.exc
3637

38+
# nose will duplicate this logger.
39+
logger = logging.getLogger(__name__)
40+
logger.setLevel(logging.DEBUG)
41+
handler = logging.StreamHandler()
42+
formatter = logging.Formatter(
43+
"%(asctime)s: %(funcName)s: %(levelname)s: %(message)s"
44+
)
45+
handler.setFormatter(formatter)
46+
logger.addHandler(handler)
47+
3748

3849
class TC_04_DispVM(qubes.tests.SystemTestCase):
3950
def setUp(self):
@@ -183,6 +194,7 @@ def test_011_failed_start_timeout(self):
183194

184195
class TC_20_DispVMMixin(object):
185196
def setUp(self): # pylint: disable=invalid-name
197+
logger.info("start")
186198
super(TC_20_DispVMMixin, self).setUp()
187199
if "whonix-g" in self.template:
188200
self.skipTest(
@@ -205,8 +217,10 @@ def setUp(self): # pylint: disable=invalid-name
205217
"--",
206218
"qubesdb-read /name | tr -d '\n'",
207219
]
220+
logger.info("end")
208221

209222
def tearDown(self): # pylint: disable=invalid-name
223+
logger.info("start")
210224
if "gui" in self.disp_base.features:
211225
del self.disp_base.features["gui"]
212226
old_preload = self.disp_base.get_feat_preload()
@@ -215,6 +229,7 @@ def tearDown(self): # pylint: disable=invalid-name
215229
self.loop.run_until_complete(asyncio.gather(*tasks))
216230
self.disp_base.features["preload-dispvm-max"] = False
217231
super(TC_20_DispVMMixin, self).tearDown()
232+
logger.info("end")
218233

219234
def _test_event_handler(
220235
self, vm, event, *args, **kwargs
@@ -233,7 +248,38 @@ async def no_preload(self):
233248
await asyncio.sleep(0)
234249
self.disp_base.features["preload-dispvm-max"] = False
235250

251+
async def wait_preload(
252+
self, preload_max, wait_pause=True, fail_on_timeout=True
253+
):
254+
"""Waiting for pause avoids objects leaking."""
255+
logger.info("start")
256+
for _ in range(60):
257+
if len(self.disp_base.get_feat_preload()) == preload_max:
258+
break
259+
await asyncio.sleep(1)
260+
else:
261+
if fail_on_timeout:
262+
self.fail("didn't preload in time")
263+
if not wait_pause:
264+
logger.info("end")
265+
return
266+
preload_dispvm = self.disp_base.get_feat_preload()
267+
preload_unfinished = preload_dispvm
268+
for _ in range(60):
269+
for qube in preload_unfinished.copy():
270+
if self.app.domains[qube].is_paused():
271+
preload_unfinished.remove(qube)
272+
continue
273+
if not preload_unfinished:
274+
break
275+
await asyncio.sleep(1)
276+
else:
277+
if fail_on_timeout:
278+
self.fail("last preloaded didn't pause in time")
279+
logger.info("end")
280+
236281
async def run_preload_proc(self):
282+
logger.info("start")
237283
proc = await asyncio.create_subprocess_exec(
238284
*self.preload_cmd,
239285
stdout=asyncio.subprocess.PIPE,
@@ -245,8 +291,11 @@ async def run_preload_proc(self):
245291
proc.terminate()
246292
await proc.wait()
247293
raise
294+
finally:
295+
logger.info("end")
248296

249297
async def run_preload(self):
298+
logger.info("start")
250299
appvm = self.disp_base
251300
dispvm = appvm.get_feat_preload()[0]
252301
dispvm = self.app.domains[dispvm]
@@ -324,6 +373,7 @@ async def run_preload(self):
324373
next_preload_list = appvm.get_feat_preload()
325374
self.assertTrue(next_preload_list)
326375
self.assertNotIn(dispvm_name, next_preload_list)
376+
logger.info("end")
327377

328378
def test_010_dvm_run_simple(self):
329379
dispvm = self.loop.run_until_complete(
@@ -353,6 +403,7 @@ def test_012_dvm_run_preload_low_mem(self):
353403

354404
async def _test_012_dvm_run_preload_low_mem(self):
355405
# pylint: disable=unspecified-encoding
406+
logger.info("start")
356407
unpatched_open = open
357408

358409
def mock_open_mem(file, *args, **kwargs):
@@ -362,135 +413,107 @@ def mock_open_mem(file, *args, **kwargs):
362413
return unpatched_open(file, *args, **kwargs)
363414

364415
with patch("builtins.open", side_effect=mock_open_mem):
365-
self.disp_base.features["preload-dispvm-max"] = "2"
366-
for _ in range(15):
367-
if len(self.disp_base.get_feat_preload()) == 2:
368-
break
369-
await asyncio.sleep(1)
416+
preload_max = 2
417+
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
418+
await self.wait_preload(preload_max, fail_on_timeout=False)
370419
self.assertEqual(1, len(self.disp_base.get_feat_preload()))
420+
logger.info("end")
371421

372422
def test_013_dvm_run_preload_gui(self):
373-
"""Test preloading with GUI feature enabled"""
423+
"""Test preloading with GUI feature enabled and use after pause."""
374424
self.loop.run_until_complete(self._test_013_dvm_run_preload_gui())
375425

376426
async def _test_013_dvm_run_preload_gui(self):
427+
logger.info("start")
428+
preload_max = 1
377429
self.disp_base.features["gui"] = True
378-
self.disp_base.features["preload-dispvm-max"] = "1"
379-
for _ in range(10):
380-
if len(self.disp_base.get_feat_preload()) == 1:
381-
break
382-
await asyncio.sleep(1)
383-
else:
384-
self.fail("didn't preload in time")
430+
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
431+
await self.wait_preload(preload_max)
385432
await self.run_preload()
433+
logger.info("end")
386434

387435
def test_014_dvm_run_preload_nogui(self):
388-
"""Test preloading with GUI feature disabled"""
436+
"""Test preloading with GUI feature disabled and use before pause."""
389437
self.loop.run_until_complete(self._test_014_dvm_run_preload_nogui())
390438

391439
async def _test_014_dvm_run_preload_nogui(self):
440+
logger.info("start")
441+
preload_max = 1
392442
self.disp_base.features["gui"] = False
393-
self.disp_base.features["preload-dispvm-max"] = "1"
394-
for _ in range(10):
395-
if len(self.disp_base.get_feat_preload()) == 1:
396-
break
397-
await asyncio.sleep(1)
398-
else:
399-
self.fail("didn't preload in time")
443+
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
444+
await self.wait_preload(preload_max, wait_pause=False)
400445
self.preload_cmd.insert(1, "--no-gui")
401446
await self.run_preload()
447+
logger.info("end")
402448

403449
def test_015_dvm_run_preload_race_more(self):
404450
"""Test race requesting multiple preloaded qubes"""
405451
self.loop.run_until_complete(self._test_015_dvm_run_preload_race_more())
406452

407-
async def _test_preload_wait_pause(self, preload_max):
408-
"""Waiting for pause avoids objects leaking."""
409-
for _ in range(60):
410-
if len(self.disp_base.get_feat_preload()) == preload_max:
411-
break
412-
await asyncio.sleep(1)
413-
else:
414-
self.fail("didn't preload in time")
415-
preload_dispvm = self.disp_base.get_feat_preload()
416-
preload_unfinished = preload_dispvm
417-
for _ in range(60):
418-
for qube in preload_unfinished.copy():
419-
if self.app.domains[qube].is_paused():
420-
preload_unfinished.remove(qube)
421-
continue
422-
if not preload_unfinished:
423-
break
424-
await asyncio.sleep(1)
425-
else:
426-
self.fail("last preloaded didn't pause in time")
427-
428453
async def _test_015_dvm_run_preload_race_more(self):
429454
# The limiting factor is how much memory is available on OpenQA and the
430455
# unreasonable memory allocated before the qube is paused due to:
431456
# https://github.com/QubesOS/qubes-issues/issues/9917
432457
# Whonix (Kicksecure) 17 fail more due to memory consumption. From the
433458
# templates deployed by default, only Debian and Fedora survives due to
434459
# using less memory than the other OSes.
460+
logger.info("start")
435461
preload_max = 4
436462
os_dist = self.disp_base.features.check_with_template("os-distribution")
437463
if os_dist in ["whonix", "kicksecure"]:
438464
preload_max -= 1
439465
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
440-
await self._test_preload_wait_pause(preload_max)
466+
await self.wait_preload(preload_max)
441467
old_preload = self.disp_base.get_feat_preload()
442468
tasks = [self.run_preload_proc() for _ in range(preload_max)]
443469
targets = await asyncio.gather(*tasks)
444-
await self._test_preload_wait_pause(preload_max)
470+
await self.wait_preload(preload_max)
445471
preload_dispvm = self.disp_base.get_feat_preload()
446472
self.assertTrue(set(old_preload).isdisjoint(preload_dispvm))
447473
self.assertEqual(len(targets), preload_max)
448474
self.assertEqual(len(targets), len(set(targets)))
475+
logger.info("end")
449476

450477
def test_016_dvm_run_preload_race_less(self):
451478
"""Test race requesting preloaded qube while the maximum is zeroed."""
452479
self.loop.run_until_complete(self._test_016_dvm_run_preload_race_less())
453480

454481
async def _test_016_dvm_run_preload_race_less(self):
455-
self.disp_base.features["preload-dispvm-max"] = "1"
456-
for _ in range(60):
457-
if len(self.disp_base.get_feat_preload()) == 1:
458-
break
459-
await asyncio.sleep(1)
460-
else:
461-
self.fail("didn't preload in time")
482+
logger.info("start")
483+
preload_max = 1
484+
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
485+
await self.wait_preload(preload_max, wait_pause=False)
462486
tasks = [self.run_preload_proc(), self.no_preload()]
463487
target = await asyncio.gather(*tasks)
464488
target_dispvm = target[0]
465489
self.assertTrue(target_dispvm.startswith("disp"))
490+
logger.info("end")
466491

467492
def test_017_dvm_run_preload_autostart(self):
493+
logger.info("start")
494+
preload_max = 1
468495
proc = self.loop.run_until_complete(
469496
asyncio.create_subprocess_exec("/usr/lib/qubes/preload-dispvm")
470497
)
471498
self.loop.run_until_complete(
472499
asyncio.wait_for(proc.communicate(), timeout=10)
473500
)
474501
self.assertEqual(self.disp_base.get_feat_preload(), [])
475-
self.disp_base.features["preload-dispvm-max"] = "1"
476-
for _ in range(10):
477-
if len(self.disp_base.get_feat_preload()) == 1:
478-
break
479-
self.loop.run_until_complete(asyncio.sleep(1))
480-
else:
481-
self.fail("didn't preload in time")
502+
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
503+
self.loop.run_until_complete(self.wait_preload(preload_max))
482504
old_preload = self.disp_base.get_feat_preload()
483505
proc = self.loop.run_until_complete(
484506
asyncio.create_subprocess_exec("/usr/lib/qubes/preload-dispvm")
485507
)
486508
self.loop.run_until_complete(asyncio.wait_for(proc.wait(), timeout=30))
487509
preload_dispvm = self.disp_base.get_feat_preload()
488-
self.assertEqual(len(old_preload), 1)
489-
self.assertEqual(len(preload_dispvm), 1)
510+
self.assertEqual(len(old_preload), preload_max)
511+
self.assertEqual(len(preload_dispvm), preload_max)
490512
self.assertTrue(
491513
set(old_preload).isdisjoint(preload_dispvm),
492514
f"old_preload={old_preload} preload_dispvm={preload_dispvm}",
493515
)
516+
logger.info("end")
494517

495518
@unittest.skipUnless(
496519
spawn.find_executable("xdotool"), "xdotool not installed"

0 commit comments

Comments
 (0)