diff --git a/qubesadmin/devices.py b/qubesadmin/devices.py index 1dc14d3..e0192ba 100644 --- a/qubesadmin/devices.py +++ b/qubesadmin/devices.py @@ -34,10 +34,11 @@ Devices are identified by pair of (backend domain, `ident`), where `ident` is class DeviceAssignment(object): # pylint: disable=too-few-public-methods ''' Maps a device to a frontend_domain. ''' - def __init__(self, backend_domain, ident, options=None, persistent=False, - frontend_domain=None): + def __init__(self, backend_domain, ident, options=None, + persistent=False, frontend_domain=None, devclass=None): self.backend_domain = backend_domain self.ident = ident + self.devclass = devclass self.options = options or {} self.persistent = persistent self.frontend_domain = frontend_domain @@ -55,6 +56,22 @@ class DeviceAssignment(object): # pylint: disable=too-few-public-methods return self.backend_domain == other.backend_domain \ and self.ident == other.ident + def clone(self): + '''Clone object instance''' + return self.__class__( + self.backend_domain, + self.ident, + self.options, + self.persistent, + self.frontend_domain, + self.devclass, + ) + + @property + def device(self): + '''Get DeviceInfo object corresponding to this DeviceAssignment''' + return self.backend_domain.devices[self.devclass][self.ident] + class DeviceInfo(object): ''' Holds all information about a device ''' @@ -120,6 +137,10 @@ class DeviceCollection(object): else: assert device_assignment.frontend_domain == self._vm, \ "Trying to attach DeviceAssignment belonging to other domain" + if device_assignment.devclass is None: + device_assignment.devclass = self._class + else: + assert device_assignment.devclass == self._class options = device_assignment.options.copy() if device_assignment.persistent: @@ -143,6 +164,10 @@ class DeviceCollection(object): else: assert device_assignment.frontend_domain == self._vm, \ "Trying to detach DeviceAssignment belonging to other domain" + if device_assignment.devclass is None: + device_assignment.devclass = self._class + else: + assert device_assignment.devclass == self._class self._vm.qubesd_call(None, 'admin.vm.device.{}.Detach'.format(self._class), @@ -174,13 +199,14 @@ class DeviceCollection(object): continue backend_domain = self._vm.app.domains[backend_domain] yield DeviceAssignment(backend_domain, ident, options, - persistent=dev_persistent, frontend_domain=self._vm) + persistent=dev_persistent, frontend_domain=self._vm, + devclass=self._class) def attached(self): '''List devices which are (or may be) attached to this vm ''' for assignment in self.assignments(): - yield self._device(assignment) + yield assignment.device def persistent(self): ''' Devices persistently attached and safe to access before libvirt @@ -188,13 +214,7 @@ class DeviceCollection(object): ''' for assignment in self.assignments(True): - yield self._device(assignment) - - def _device(self, assignment): - ''' Helper method for geting a `qubes.devices.DeviceInfo` object from - `qubes.devices.DeviceAssignment`. ''' - - return assignment.backend_domain.devices[self._class][assignment.ident] + yield assignment.device def available(self): '''List devices exposed by this vm''' diff --git a/qubesadmin/tests/devices.py b/qubesadmin/tests/devices.py index bb057dc..f169e09 100644 --- a/qubesadmin/tests/devices.py +++ b/qubesadmin/tests/devices.py @@ -161,6 +161,12 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase): ('test-vm', 'admin.vm.device.test.List', None, None)] = \ b'0\0test-vm2+dev1\n' \ b'test-vm3+dev2\n' + self.app.expected_calls[ + ('test-vm2', 'admin.vm.device.test.Available', None, None)] = \ + b'0\0dev1 description=desc\n' + self.app.expected_calls[ + ('test-vm3', 'admin.vm.device.test.Available', None, None)] = \ + b'0\0dev2 description=desc\n' assigns = list(self.vm.devices['test'].assignments()) self.assertEqual(len(assigns), 2) self.assertIsInstance(assigns[0], qubesadmin.devices.DeviceAssignment) @@ -170,6 +176,9 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase): self.assertEqual(assigns[0].frontend_domain, self.app.domains['test-vm']) self.assertEqual(assigns[0].options, {}) + self.assertEqual(assigns[0].devclass, 'test') + self.assertEqual(assigns[0].device, + self.app.domains['test-vm2'].devices['test']['dev1']) self.assertIsInstance(assigns[1], qubesadmin.devices.DeviceAssignment) self.assertEqual(assigns[1].backend_domain, @@ -178,6 +187,9 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase): self.assertEqual(assigns[1].frontend_domain, self.app.domains['test-vm']) self.assertEqual(assigns[1].options, {}) + self.assertEqual(assigns[1].devclass, 'test') + self.assertEqual(assigns[1].device, + self.app.domains['test-vm3'].devices['test']['dev2']) self.assertAllCalled() @@ -196,6 +208,7 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase): self.app.domains['test-vm']) self.assertEqual(assigns[0].options, {'ro': 'True'}) self.assertEqual(assigns[0].persistent, False) + self.assertEqual(assigns[0].devclass, 'test') self.assertIsInstance(assigns[1], qubesadmin.devices.DeviceAssignment) self.assertEqual(assigns[1].backend_domain, @@ -205,6 +218,7 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase): self.app.domains['test-vm']) self.assertEqual(assigns[1].options, {'ro': 'False'}) self.assertEqual(assigns[1].persistent, True) + self.assertEqual(assigns[1].devclass, 'test') self.assertAllCalled() @@ -223,6 +237,7 @@ class TC_00_DeviceCollection(qubesadmin.tests.QubesTestCase): self.app.domains['test-vm']) self.assertEqual(assigns[0].options, {}) self.assertEqual(assigns[0].persistent, True) + self.assertEqual(assigns[0].devclass, 'test') self.assertAllCalled() def test_042_assignments_non_persistent(self):