devices: implement DeviceCollection.update_persistent()

Allow attached device to be converted from persistent to non-persistent
and the other way around.
This is to allow starting a VM with some device attached temporarily.
When VM is not running, it is possible to attach device only
persistently, so this change will allow to do that, then, after starting
the VM, change it to non-persistent - so it will not be attached again
at further startups.

QubesOS/qubes-issues#3055
This commit is contained in:
Marek Marczykowski-Górecki 2017-09-01 12:42:02 +02:00
parent 1f5d43a094
commit 9d062c4c66
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 70 additions and 2 deletions

View File

@ -270,6 +270,27 @@ class DeviceCollection(object):
device_assignment.bus = self._bus
self._set.add(device_assignment)
def update_persistent(self, device: DeviceInfo, persistent: bool):
'''Update `persistent` flag of already attached device.
'''
if self._vm.is_halted():
raise qubes.exc.QubesVMNotStartedError(self._vm,
'VM must be running to modify device persistence flag')
assignments = [a for a in self.assignments() if a.device == device]
if not assignments:
raise qubes.exc.QubesValueError('Device not assigned')
assert len(assignments) == 1
assignment = assignments[0]
# be careful to use already present assignment, not the provided one
# - to not change options as a side effect
if persistent and device not in self._set:
assignment.persistent = True
self._set.add(assignment)
elif not persistent and device in self._set:
self._set.discard(assignment)
@asyncio.coroutine
def detach(self, device_assignment: DeviceAssignment):
'''Detach (remove) device from domain.

View File

@ -71,6 +71,9 @@ class TestVM(qubes.tests.TestEmitter):
def is_halted(self):
return not self.running
def is_running(self):
return self.running
class TC_00_DeviceCollection(qubes.tests.QubesTestCase):
@ -130,8 +133,6 @@ class TC_00_DeviceCollection(qubes.tests.QubesTestCase):
self.loop.run_until_complete(self.collection.attach(self.assignment))
self.assertEventFired(self.emitter, 'device-list-attached:testclass')
self.assertEqual({self.device}, set(self.collection.persistent()))
self.assertEqual({self.device},
set(self.collection.persistent()))
self.assertEqual(set([]),
set(self.collection.attached()))
@ -153,6 +154,52 @@ class TC_00_DeviceCollection(qubes.tests.QubesTestCase):
self.assertEqual({self.device}, set(self.collection))
self.assertEventFired(self.emitter, 'device-list:testclass')
def test_020_update_persistent_to_false(self):
self.emitter.running = True
self.assertEqual(set([]), set(self.collection.persistent()))
self.loop.run_until_complete(self.collection.attach(self.assignment))
# device-attach event not implemented, so manipulate object manually
self.device.frontend_domain = self.emitter
self.assertEqual({self.device}, set(self.collection.persistent()))
self.assertEqual({self.device}, set(self.collection.attached()))
self.assertEqual({self.device}, set(self.collection.persistent()))
self.assertEqual({self.device}, set(self.collection.attached()))
self.collection.update_persistent(self.device, False)
self.assertEqual(set(), set(self.collection.persistent()))
self.assertEqual({self.device}, set(self.collection.attached()))
def test_021_update_persistent_to_true(self):
self.assignment.persistent = False
self.emitter.running = True
self.assertEqual(set([]), set(self.collection.persistent()))
self.loop.run_until_complete(self.collection.attach(self.assignment))
# device-attach event not implemented, so manipulate object manually
self.device.frontend_domain = self.emitter
self.assertEqual(set(), set(self.collection.persistent()))
self.assertEqual({self.device}, set(self.collection.attached()))
self.assertEqual(set(), set(self.collection.persistent()))
self.assertEqual({self.device}, set(self.collection.attached()))
self.collection.update_persistent(self.device, True)
self.assertEqual({self.device}, set(self.collection.persistent()))
self.assertEqual({self.device}, set(self.collection.attached()))
def test_022_update_persistent_reject_not_running(self):
self.assertEqual(set([]), set(self.collection.persistent()))
self.loop.run_until_complete(self.collection.attach(self.assignment))
self.assertEqual({self.device}, set(self.collection.persistent()))
self.assertEqual(set(), set(self.collection.attached()))
with self.assertRaises(qubes.exc.QubesVMNotStartedError):
self.collection.update_persistent(self.device, False)
def test_023_update_persistent_reject_not_attached(self):
self.assertEqual(set(), set(self.collection.persistent()))
self.assertEqual(set(), set(self.collection.attached()))
self.emitter.running = True
with self.assertRaises(qubes.exc.QubesValueError):
self.collection.update_persistent(self.device, True)
with self.assertRaises(qubes.exc.QubesValueError):
self.collection.update_persistent(self.device, False)
class TC_01_DeviceManager(qubes.tests.QubesTestCase):
def setUp(self):