api/admin: implement admin.vm.device....Set.persistent
This will allow converting persistent device (used to boot VM for example) to non-persistent. QubesOS/qubes-issues#3055
This commit is contained in:
parent
9d062c4c66
commit
cea70748a6
4
Makefile
4
Makefile
@ -55,14 +55,17 @@ ADMIN_API_METHODS_SIMPLE = \
|
|||||||
admin.vm.device.pci.Available \
|
admin.vm.device.pci.Available \
|
||||||
admin.vm.device.pci.Detach \
|
admin.vm.device.pci.Detach \
|
||||||
admin.vm.device.pci.List \
|
admin.vm.device.pci.List \
|
||||||
|
admin.vm.device.pci.Set.persistent \
|
||||||
admin.vm.device.block.Attach \
|
admin.vm.device.block.Attach \
|
||||||
admin.vm.device.block.Available \
|
admin.vm.device.block.Available \
|
||||||
admin.vm.device.block.Detach \
|
admin.vm.device.block.Detach \
|
||||||
admin.vm.device.block.List \
|
admin.vm.device.block.List \
|
||||||
|
admin.vm.device.block.Set.persistent \
|
||||||
admin.vm.device.mic.Attach \
|
admin.vm.device.mic.Attach \
|
||||||
admin.vm.device.mic.Available \
|
admin.vm.device.mic.Available \
|
||||||
admin.vm.device.mic.Detach \
|
admin.vm.device.mic.Detach \
|
||||||
admin.vm.device.mic.List \
|
admin.vm.device.mic.List \
|
||||||
|
admin.vm.device.mic.Set.persistent \
|
||||||
admin.vm.feature.CheckWithTemplate \
|
admin.vm.feature.CheckWithTemplate \
|
||||||
admin.vm.feature.Get \
|
admin.vm.feature.Get \
|
||||||
admin.vm.feature.List \
|
admin.vm.feature.List \
|
||||||
@ -188,6 +191,7 @@ endif
|
|||||||
admin.vm.device.testclass.Attach \
|
admin.vm.device.testclass.Attach \
|
||||||
admin.vm.device.testclass.Detach \
|
admin.vm.device.testclass.Detach \
|
||||||
admin.vm.device.testclass.List \
|
admin.vm.device.testclass.List \
|
||||||
|
admin.vm.device.testclass.Set.persistent \
|
||||||
admin.vm.device.testclass.Available
|
admin.vm.device.testclass.Available
|
||||||
# sanity check
|
# sanity check
|
||||||
for method in $(DESTDIR)/etc/qubes-rpc/policy/admin.*; do \
|
for method in $(DESTDIR)/etc/qubes-rpc/policy/admin.*; do \
|
||||||
|
@ -1093,6 +1093,36 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
yield from self.dest.devices[devclass].detach(assignment)
|
yield from self.dest.devices[devclass].detach(assignment)
|
||||||
self.app.save()
|
self.app.save()
|
||||||
|
|
||||||
|
# Attach/Detach action can both modify persistent state (with
|
||||||
|
# persistent=True) and volatile state of running VM (with persistent=False).
|
||||||
|
# For this reason, write=True + execute=True
|
||||||
|
@qubes.api.method('admin.vm.device.{endpoint}.Set.persistent',
|
||||||
|
endpoints=(ep.name
|
||||||
|
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
||||||
|
scope='local', write=True, execute=True)
|
||||||
|
@asyncio.coroutine
|
||||||
|
def vm_device_set_persistent(self, endpoint, untrusted_payload):
|
||||||
|
devclass = endpoint
|
||||||
|
|
||||||
|
assert untrusted_payload in (b'True', b'False')
|
||||||
|
persistent = untrusted_payload == b'True'
|
||||||
|
del untrusted_payload
|
||||||
|
|
||||||
|
# qrexec already verified that no strange characters are in self.arg
|
||||||
|
backend_domain, ident = self.arg.split('+', 1)
|
||||||
|
# device must be already attached
|
||||||
|
matching_devices = [dev for dev
|
||||||
|
in self.dest.devices[devclass].attached()
|
||||||
|
if dev.backend_domain.name == backend_domain and dev.ident == ident]
|
||||||
|
assert len(matching_devices) == 1
|
||||||
|
dev = matching_devices[0]
|
||||||
|
|
||||||
|
self.fire_event_for_permission(device=dev,
|
||||||
|
persistent=persistent)
|
||||||
|
|
||||||
|
self.dest.devices[devclass].update_persistent(dev, persistent)
|
||||||
|
self.app.save()
|
||||||
|
|
||||||
@qubes.api.method('admin.vm.firewall.Get', no_payload=True,
|
@qubes.api.method('admin.vm.firewall.Get', no_payload=True,
|
||||||
scope='local', read=True)
|
scope='local', read=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
|
@ -2131,6 +2131,102 @@ class TC_00_VMs(AdminAPITestCase):
|
|||||||
b'test-vm1')
|
b'test-vm1')
|
||||||
self.assertFalse(self.app.save.called)
|
self.assertFalse(self.app.save.called)
|
||||||
|
|
||||||
|
def test_650_vm_device_set_persistent_true(self):
|
||||||
|
self.vm.add_handler('device-list:testclass',
|
||||||
|
self.device_list_testclass)
|
||||||
|
self.vm.add_handler('device-list-attached:testclass',
|
||||||
|
self.device_list_attached_testclass)
|
||||||
|
with unittest.mock.patch.object(qubes.vm.qubesvm.QubesVM,
|
||||||
|
'is_halted', lambda _: False):
|
||||||
|
value = self.call_mgmt_func(
|
||||||
|
b'admin.vm.device.testclass.Set.persistent',
|
||||||
|
b'test-vm1', b'test-vm1+1234', b'True')
|
||||||
|
self.assertIsNone(value)
|
||||||
|
dev = qubes.devices.DeviceInfo(self.vm, '1234')
|
||||||
|
self.assertIn(dev, self.vm.devices['testclass'].persistent())
|
||||||
|
self.app.save.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_651_vm_device_set_persistent_false_unchanged(self):
|
||||||
|
self.vm.add_handler('device-list:testclass',
|
||||||
|
self.device_list_testclass)
|
||||||
|
self.vm.add_handler('device-list-attached:testclass',
|
||||||
|
self.device_list_attached_testclass)
|
||||||
|
with unittest.mock.patch.object(qubes.vm.qubesvm.QubesVM,
|
||||||
|
'is_halted', lambda _: False):
|
||||||
|
value = self.call_mgmt_func(
|
||||||
|
b'admin.vm.device.testclass.Set.persistent',
|
||||||
|
b'test-vm1', b'test-vm1+1234', b'False')
|
||||||
|
self.assertIsNone(value)
|
||||||
|
dev = qubes.devices.DeviceInfo(self.vm, '1234')
|
||||||
|
self.assertNotIn(dev, self.vm.devices['testclass'].persistent())
|
||||||
|
self.app.save.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_652_vm_device_set_persistent_false(self):
|
||||||
|
self.vm.add_handler('device-list:testclass',
|
||||||
|
self.device_list_testclass)
|
||||||
|
assignment = qubes.devices.DeviceAssignment(self.vm, '1234', {},
|
||||||
|
True)
|
||||||
|
self.loop.run_until_complete(
|
||||||
|
self.vm.devices['testclass'].attach(assignment))
|
||||||
|
self.vm.add_handler('device-list-attached:testclass',
|
||||||
|
self.device_list_attached_testclass)
|
||||||
|
dev = qubes.devices.DeviceInfo(self.vm, '1234')
|
||||||
|
self.assertIn(dev, self.vm.devices['testclass'].persistent())
|
||||||
|
with unittest.mock.patch.object(qubes.vm.qubesvm.QubesVM,
|
||||||
|
'is_halted', lambda _: False):
|
||||||
|
value = self.call_mgmt_func(
|
||||||
|
b'admin.vm.device.testclass.Set.persistent',
|
||||||
|
b'test-vm1', b'test-vm1+1234', b'False')
|
||||||
|
self.assertIsNone(value)
|
||||||
|
self.assertNotIn(dev, self.vm.devices['testclass'].persistent())
|
||||||
|
self.assertIn(dev, self.vm.devices['testclass'].attached())
|
||||||
|
self.app.save.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_653_vm_device_set_persistent_true_unchanged(self):
|
||||||
|
self.vm.add_handler('device-list:testclass',
|
||||||
|
self.device_list_testclass)
|
||||||
|
assignment = qubes.devices.DeviceAssignment(self.vm, '1234', {},
|
||||||
|
True)
|
||||||
|
self.loop.run_until_complete(
|
||||||
|
self.vm.devices['testclass'].attach(assignment))
|
||||||
|
self.vm.add_handler('device-list-attached:testclass',
|
||||||
|
self.device_list_attached_testclass)
|
||||||
|
with unittest.mock.patch.object(qubes.vm.qubesvm.QubesVM,
|
||||||
|
'is_halted', lambda _: False):
|
||||||
|
value = self.call_mgmt_func(
|
||||||
|
b'admin.vm.device.testclass.Set.persistent',
|
||||||
|
b'test-vm1', b'test-vm1+1234', b'True')
|
||||||
|
self.assertIsNone(value)
|
||||||
|
dev = qubes.devices.DeviceInfo(self.vm, '1234')
|
||||||
|
self.assertIn(dev, self.vm.devices['testclass'].persistent())
|
||||||
|
self.assertIn(dev, self.vm.devices['testclass'].attached())
|
||||||
|
self.app.save.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_654_vm_device_set_persistent_not_attached(self):
|
||||||
|
self.vm.add_handler('device-list:testclass',
|
||||||
|
self.device_list_testclass)
|
||||||
|
with unittest.mock.patch.object(qubes.vm.qubesvm.QubesVM,
|
||||||
|
'is_halted', lambda _: False):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
self.call_mgmt_func(
|
||||||
|
b'admin.vm.device.testclass.Set.persistent',
|
||||||
|
b'test-vm1', b'test-vm1+1234', b'True')
|
||||||
|
dev = qubes.devices.DeviceInfo(self.vm, '1234')
|
||||||
|
self.assertNotIn(dev, self.vm.devices['testclass'].persistent())
|
||||||
|
self.assertFalse(self.app.save.called)
|
||||||
|
|
||||||
|
def test_655_vm_device_set_persistent_invalid_value(self):
|
||||||
|
self.vm.add_handler('device-list:testclass',
|
||||||
|
self.device_list_testclass)
|
||||||
|
with unittest.mock.patch.object(qubes.vm.qubesvm.QubesVM,
|
||||||
|
'is_halted', lambda _: False):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
self.call_mgmt_func(
|
||||||
|
b'admin.vm.device.testclass.Set.persistent',
|
||||||
|
b'test-vm1', b'test-vm1+1234', b'maybe')
|
||||||
|
dev = qubes.devices.DeviceInfo(self.vm, '1234')
|
||||||
|
self.assertNotIn(dev, self.vm.devices['testclass'].persistent())
|
||||||
|
self.assertFalse(self.app.save.called)
|
||||||
|
|
||||||
def test_990_vm_unexpected_payload(self):
|
def test_990_vm_unexpected_payload(self):
|
||||||
methods_with_no_payload = [
|
methods_with_no_payload = [
|
||||||
|
Loading…
Reference in New Issue
Block a user