Skip to content

Commit 48e1693

Browse files
ben-grandeHW42
authored andcommitted
DispVM: Fix volume size check in is_preload_outdated
The type of size in volume_config depends on whether the qube object has been loaded from xml (str) or created freshly (int). Due to this, in almost all cases preloaded disposables were wrongly discarded. Use the Volume objects in vm.volumes instead. While at it also improve the return value to correctly report why it thinks it's outdated (both for changed private size as well as outdated volumes). For: QubesOS/qubes-issues#1512 For: QubesOS/qubes-issues#10525 Fixes: QubesOS/qubes-issues#10572 For: #771
1 parent 756ff94 commit 48e1693

3 files changed

Lines changed: 99 additions & 74 deletions

File tree

qubes/tests/integ/dispvm.py

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -802,17 +802,23 @@ async def _test_018_preload_global(self):
802802
self.log_preload()
803803
logger.info("end")
804804

805-
def test_019_preload_refresh(self):
806-
"""Refresh preload on volume change."""
807-
self.loop.run_until_complete(self._test_019_preload_refresh())
805+
def test_019_preload_discard_outdated_volumes(self):
806+
"""Discard preload if volumes are outdated compared to its templates."""
807+
self.loop.run_until_complete(
808+
self._test_019_preload_discard_outdated_volumes()
809+
)
808810

809-
async def _test_019_preload_refresh(self):
811+
async def _test_019_preload_discard_outdated_volumes(self):
810812
logger.info("start")
811813
self.log_preload()
812814
preload_max = 1
813815

814816
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
815817
for qube in [self.disp_base, self.disp_base.template]:
818+
logger.info(
819+
"discard because of outdated volume originating from %s",
820+
qube.name,
821+
)
816822
await self.wait_preload(preload_max)
817823
old_preload = self.disp_base.get_feat_preload()
818824
await qube.start()
@@ -832,25 +838,50 @@ async def _test_019_preload_refresh(self):
832838
self.log_preload()
833839
logger.info("end")
834840

835-
def test_020_preload_discard_outdated(self):
836-
"""Discard preload if properties differ from the disposable template."""
837-
self.loop.run_until_complete(self._test_020_preload_discard_outdated())
841+
def test_020_preload_discard_outdated_volume_size(self):
842+
"""Discard preload if private size differs with disposable template."""
843+
self.loop.run_until_complete(
844+
self._test_020_preload_discard_outdated_volume_size()
845+
)
846+
847+
async def _test_020_preload_discard_outdated_volume_size(self):
848+
logger.info("start")
849+
self.log_preload()
850+
preload_max = 1
851+
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
852+
await self.wait_preload(preload_max)
853+
preload_dispvm = self.disp_base.get_feat_preload()
854+
old_size = self.disp_base.volume_config["private"]["size"]
855+
size = int(old_size) + 512
856+
await self.disp_base.storage.resize("private", size)
857+
self.app.save()
858+
dispvm = await asyncio.wait_for(
859+
qubes.vm.dispvm.DispVM.from_appvm(self.disp_base), 30
860+
)
861+
self.assertNotIn(dispvm.name, preload_dispvm)
862+
await dispvm.cleanup()
863+
logger.info("end")
864+
865+
def test_021_preload_discard_outdated_setting(self):
866+
"""Discard preload if properties differ with the disposable template."""
867+
self.loop.run_until_complete(
868+
self._test_021_preload_discard_outdated_setting()
869+
)
838870

839-
async def _test_020_preload_discard_outdated(self):
871+
async def _test_021_preload_discard_outdated_setting(self):
840872
logger.info("start")
841873
self.log_preload()
842874
preload_max = 1
843875
self.disp_base.features["preload-dispvm-max"] = str(preload_max)
844876
await self.wait_preload(preload_max)
845877
preload_dispvm = self.disp_base.get_feat_preload()
846878
self.disp_base.netvm = None
847-
try:
848-
dispvm = await asyncio.wait_for(
849-
qubes.vm.dispvm.DispVM.from_appvm(self.disp_base), 30
850-
)
851-
self.assertNotIn(dispvm.name, preload_dispvm)
852-
finally:
853-
await dispvm.cleanup()
879+
dispvm = await asyncio.wait_for(
880+
qubes.vm.dispvm.DispVM.from_appvm(self.disp_base), 30
881+
)
882+
self.assertNotIn(dispvm.name, preload_dispvm)
883+
await dispvm.cleanup()
884+
await self.wait_preload(preload_max)
854885
logger.info("end")
855886

856887
@unittest.skipUnless(which("xdotool"), "xdotool not installed")

qubes/tests/vm/dispvm.py

Lines changed: 47 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def test_000_from_appvm(self, mock_storage, mock_makedirs, mock_symlink):
150150
mock_symlink.assert_not_called()
151151

152152
@mock.patch("qubes.storage.Storage")
153-
def test_000_from_appvm_preload_reject_max(self, mock_storage):
153+
def test_001_from_appvm_preload_reject_max(self, mock_storage):
154154
mock_storage.return_value.create.side_effect = self.mock_coro
155155
self.appvm.template_for_dispvms = True
156156
orig_getitem = self.app.domains.__getitem__
@@ -174,7 +174,7 @@ def test_000_from_appvm_preload_reject_max(self, mock_storage):
174174
@mock.patch("os.symlink")
175175
@mock.patch("os.makedirs")
176176
@mock.patch("qubes.storage.Storage")
177-
def test_000_from_appvm_preload_use(
177+
def test_002_from_appvm_preload_use(
178178
self,
179179
mock_storage,
180180
mock_makedirs,
@@ -223,10 +223,8 @@ def test_000_from_appvm_preload_use(
223223
mock_qube.features = dispvm.features
224224
mock_qube.unpause = self.mock_coro
225225
mock_qube.request_preload.return_value = dispvm
226-
mock_qube.is_preload_outdated = dispvm.is_preload_outdated
226+
mock_qube.is_preload_outdated.return_value = {}
227227
mock_qube.get_preload = mock.AsyncMock()
228-
mock_qube.volumes = {}
229-
dispvm.volume_config = self.appvm.volume_config
230228
fresh_dispvm = self.loop.run_until_complete(
231229
qubes.vm.dispvm.DispVM.from_appvm(self.appvm)
232230
)
@@ -244,7 +242,7 @@ def test_000_from_appvm_preload_use(
244242
@mock.patch("os.symlink")
245243
@mock.patch("os.makedirs")
246244
@mock.patch("qubes.storage.Storage")
247-
def test_000_from_appvm_preload_fill_gap(
245+
def test_003_from_appvm_preload_fill_gap(
248246
self,
249247
mock_storage,
250248
mock_makedirs,
@@ -295,7 +293,7 @@ def test_000_from_appvm_preload_fill_gap(
295293
mock_symlink.assert_not_called()
296294
mock_makedirs.assert_called_once()
297295

298-
def test_000_get_preload_max(self):
296+
def test_004_get_preload_max(self):
299297
self.assertEqual(qubes.vm.dispvm.get_preload_max(self.appvm), None)
300298
self.appvm.features["supported-rpc.qubes.WaitForRunningSystem"] = True
301299
self.appvm.features["preload-dispvm-max"] = 1
@@ -306,7 +304,7 @@ def test_000_get_preload_max(self):
306304
self.adminvm.features["preload-dispvm-max"] = 2
307305
self.assertEqual(qubes.vm.dispvm.get_preload_max(self.adminvm), 2)
308306

309-
def test_000_get_preload_templates(self):
307+
def test_005_get_preload_templates(self):
310308
get_preload_templates = qubes.vm.dispvm.get_preload_templates
311309
self.assertEqual(get_preload_templates(self.app), [])
312310
self.appvm.template_for_dispvms = True
@@ -338,14 +336,14 @@ def test_000_get_preload_templates(self):
338336
get_preload_templates(self.app), [self.appvm, self.appvm_alt]
339337
)
340338

341-
def test_001_from_appvm_reject_not_allowed(self):
339+
def test_006_from_appvm_reject_not_allowed(self):
342340
with self.assertRaises(qubes.exc.QubesException):
343341
self.loop.run_until_complete(
344342
qubes.vm.dispvm.DispVM.from_appvm(self.appvm)
345343
)
346344

347345
@unittest.skip("test is broken")
348-
def test_002_template_change(self):
346+
def test_007_template_change(self):
349347
self.appvm.template_for_dispvms = True
350348
orig_getitem = self.app.domains.__getitem__
351349
with mock.patch.object(
@@ -372,7 +370,7 @@ def test_002_template_change(self):
372370
self.loop.run_until_complete(dispvm.kill())
373371
dispvm.template = self.appvm
374372

375-
def test_003_dvmtemplate_template_change(self):
373+
def test_008_dvmtemplate_template_change(self):
376374
self.appvm.template_for_dispvms = True
377375
orig_domains = self.app.domains
378376
with mock.patch.object(
@@ -394,7 +392,7 @@ def test_003_dvmtemplate_template_change(self):
394392
with self.assertRaises(qubes.exc.QubesValueError):
395393
self.appvm.template = qubes.property.DEFAULT
396394

397-
def test_004_dvmtemplate_allowed_change(self):
395+
def test_009_dvmtemplate_allowed_change(self):
398396
self.appvm.template_for_dispvms = True
399397
orig_domains = self.app.domains
400398
with mock.patch.object(
@@ -660,23 +658,17 @@ def test_023_inherit_ephemeral(self, _mock_makedirs, _mock_symlink):
660658
self.assertIs(dispvm.template, self.appvm)
661659
self.assertTrue(dispvm.volumes["volatile"].ephemeral)
662660

663-
@mock.patch("qubes.vm.qubesvm.QubesVM.start")
664661
@mock.patch("os.symlink")
665662
@mock.patch("os.makedirs")
666-
@mock.patch("qubes.storage.Storage")
667-
def test_024_is_preload_outdated(
668-
self,
669-
mock_storage,
670-
mock_makedirs,
671-
mock_symlink,
672-
mock_start,
673-
):
674-
mock_storage.return_value.create.side_effect = self.mock_coro
675-
mock_makedirs.return_value = self.mock_coro
676-
mock_symlink.return_value = self.mock_coro
677-
mock_start.side_effect = self.mock_coro
663+
def test_024_is_preload_outdated(self, _mock_makedirs, _mock_symlink):
664+
self.app.pools["alternative"] = qubes.tests.vm.appvm.TestPool(
665+
name="alternative"
666+
)
678667
self.appvm.template_for_dispvms = True
679-
668+
self.loop.run_until_complete(self.template.create_on_disk())
669+
self.loop.run_until_complete(
670+
self.appvm.create_on_disk(pool="alternative")
671+
)
680672
self.appvm.features["supported-rpc.qubes.WaitForRunningSystem"] = True
681673
self.appvm.features["preload-dispvm-max"] = "1"
682674
orig_getitem = self.app.domains.__getitem__
@@ -685,12 +677,6 @@ def test_024_is_preload_outdated(
685677
) as mock_domains:
686678
mock_qube = mock.Mock()
687679
mock_qube.template = self.appvm
688-
mock_qube.qrexec_timeout = self.appvm.qrexec_timeout
689-
mock_qube.preload_complete = mock.Mock(spec=asyncio.Event)
690-
mock_qube.preload_complete.is_set.return_value = True
691-
mock_qube.preload_complete.set = self.mock_coro
692-
mock_qube.preload_complete.clear = self.mock_coro
693-
mock_qube.preload_complete.wait = self.mock_coro
694680
mock_domains.configure_mock(
695681
**{
696682
"get_new_unused_dispid": mock.Mock(return_value=42),
@@ -700,24 +686,35 @@ def test_024_is_preload_outdated(
700686
),
701687
}
702688
)
703-
dispvm = self.loop.run_until_complete(
704-
qubes.vm.dispvm.DispVM.from_appvm(self.appvm, preload=True)
689+
dispvm = self.app.add_new_vm(
690+
qubes.vm.dispvm.DispVM, name="disp42", template=self.appvm
705691
)
706-
dispvm.volume_config = self.appvm.volume_config
692+
self.appvm.features["preload-dispvm"] = "disp42"
707693

708-
self.assertFalse(dispvm.is_preload_outdated())
709-
self.appvm.debug = not self.appvm.debug
710-
self.assertEqual(
711-
dispvm.is_preload_outdated(), {"properties": ["debug"]}
712-
)
713-
self.appvm.debug = not self.appvm.debug
694+
self.loop.run_until_complete(dispvm.create_on_disk())
714695

715-
self.assertFalse(dispvm.is_preload_outdated())
716-
self.appvm_alt.provides_network = True
717-
self.assertFalse(dispvm.is_preload_outdated())
718-
self.appvm.netvm = self.appvm_alt
719-
self.assertIn("properties", dispvm.is_preload_outdated().keys())
720-
self.assertEqual(
721-
sorted(dispvm.is_preload_outdated()["properties"]),
722-
sorted(["netvm", "dns", "visible_netmask"]),
723-
)
696+
with mock.patch.object(
697+
dispvm.volumes["root"], "is_outdated", return_value=False
698+
), mock.patch.object(
699+
dispvm.volumes["private"], "is_outdated", return_value=False
700+
), mock.patch.object(
701+
dispvm.volumes["volatile"], "is_outdated", return_value=False
702+
), mock.patch.object(
703+
dispvm.volumes["kernel"], "is_outdated", return_value=False
704+
):
705+
self.assertFalse(dispvm.is_preload_outdated())
706+
self.appvm.debug = not self.appvm.debug
707+
self.assertEqual(
708+
dispvm.is_preload_outdated(), {"properties": ["debug"]}
709+
)
710+
self.appvm.debug = not self.appvm.debug
711+
712+
self.assertFalse(dispvm.is_preload_outdated())
713+
self.appvm_alt.provides_network = True
714+
self.assertFalse(dispvm.is_preload_outdated())
715+
self.appvm.netvm = self.appvm_alt
716+
self.assertIn("properties", dispvm.is_preload_outdated().keys())
717+
self.assertEqual(
718+
sorted(dispvm.is_preload_outdated()["properties"]),
719+
sorted(["netvm", "dns", "visible_netmask"]),
720+
)

qubes/vm/dispvm.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -457,17 +457,14 @@ def is_preload_outdated(self) -> dict:
457457
return differed
458458

459459
appvm = self.template
460-
if (
461-
self.volume_config["private"]["size"]
462-
!= appvm.volume_config["private"]["size"]
463-
):
464-
differed["volumes"] = ["private"]
460+
if self.volumes["private"].size != appvm.volumes["private"].size:
461+
differed["volumes_size"] = ["private"]
465462
return differed
466463

467-
if any(vol for vol in self.volumes.values() if vol.is_outdated()):
468-
# Volume name is irrelevant. We use any() to return fast.
469-
differed["volumes"] = ["root"]
470-
return differed
464+
for vol_name, vol in self.volumes.items():
465+
if vol.is_outdated():
466+
differed["volumes_outdated"] = [vol_name]
467+
return differed
471468

472469
appvm_props = appvm.property_dict()
473470
props = self.property_dict()

0 commit comments

Comments
 (0)