diff --git a/qubes/api/admin.py b/qubes/api/admin.py index 3bd3a98d..c5ef20ce 100644 --- a/qubes/api/admin.py +++ b/qubes/api/admin.py @@ -899,9 +899,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI): scope='local', execute=True) @asyncio.coroutine def vm_shutdown(self): - force = (self.arg == 'force') - self.fire_event_for_permission(force=force) - yield from self.dest.shutdown(force=force) + if self.arg: + args = self.arg.split('+') + else: + args = [] + self.enforce(all(arg in ('force', 'wait') for arg in args)) + force = ('force' in args) + wait = ('wait' in args) + self.fire_event_for_permission(force=force, wait=wait) + yield from self.dest.shutdown(force=force, wait=wait) @qubes.api.method('admin.vm.Pause', no_payload=True, scope='local', execute=True) diff --git a/qubes/tests/api_admin.py b/qubes/tests/api_admin.py index be94d916..9b5b3b54 100644 --- a/qubes/tests/api_admin.py +++ b/qubes/tests/api_admin.py @@ -1030,7 +1030,7 @@ netvm default=True type=vm \n''' self.vm.shutdown = coroutine_mock value = self.call_mgmt_func(b'admin.vm.Shutdown', b'test-vm1') self.assertIsNone(value) - func_mock.assert_called_once_with(force=False) + func_mock.assert_called_once_with(force=False, wait=False) def test_231_shutdown_force(self): func_mock = unittest.mock.Mock() @@ -1041,7 +1041,51 @@ netvm default=True type=vm \n''' self.vm.shutdown = coroutine_mock value = self.call_mgmt_func(b'admin.vm.Shutdown', b'test-vm1', b'force') self.assertIsNone(value) - func_mock.assert_called_once_with(force=True) + func_mock.assert_called_once_with(force=True, wait=False) + + def test_232_shutdown_wait(self): + func_mock = unittest.mock.Mock() + + @asyncio.coroutine + def coroutine_mock(*args, **kwargs): + return func_mock(*args, **kwargs) + self.vm.shutdown = coroutine_mock + value = self.call_mgmt_func(b'admin.vm.Shutdown', b'test-vm1', b'wait') + self.assertIsNone(value) + func_mock.assert_called_once_with(force=False, wait=True) + + def test_233_shutdown_wait_force(self): + func_mock = unittest.mock.Mock() + + @asyncio.coroutine + def coroutine_mock(*args, **kwargs): + return func_mock(*args, **kwargs) + self.vm.shutdown = coroutine_mock + value = self.call_mgmt_func(b'admin.vm.Shutdown', b'test-vm1', b'wait+force') + self.assertIsNone(value) + func_mock.assert_called_once_with(force=True, wait=True) + + def test_234_shutdown_force_wait(self): + func_mock = unittest.mock.Mock() + + @asyncio.coroutine + def coroutine_mock(*args, **kwargs): + return func_mock(*args, **kwargs) + self.vm.shutdown = coroutine_mock + value = self.call_mgmt_func(b'admin.vm.Shutdown', b'test-vm1', b'force+wait') + self.assertIsNone(value) + func_mock.assert_called_once_with(force=True, wait=True) + + def test_234_shutdown_force_wait_invalid(self): + func_mock = unittest.mock.Mock() + + @asyncio.coroutine + def coroutine_mock(*args, **kwargs): + return func_mock(*args, **kwargs) + self.vm.shutdown = coroutine_mock + with self.assertRaises(qubes.api.PermissionDenied): + self.call_mgmt_func(b'admin.vm.Shutdown', b'test-vm1', b'forcewait') + func_mock.assert_not_called() def test_240_pause(self): func_mock = unittest.mock.Mock()