diff --git a/qubes/api/admin.py b/qubes/api/admin.py index 67203a37..39e7ca2d 100644 --- a/qubes/api/admin.py +++ b/qubes/api/admin.py @@ -712,6 +712,25 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI): raise self.app.save() + @qubes.api.method('admin.vm.Remove', no_payload=True) + @asyncio.coroutine + def vm_remove(self): + assert not self.arg + + self.fire_event_for_permission() + + if not self.dest.is_halted(): + raise qubes.exc.QubesVMNotHaltedError(self.dest) + + del self.app.domains[self.dest] + try: + yield from self.dest.remove_from_disk() + except: # pylint: disable=bare-except + self.app.log.exception('Error wile removing VM \'%s\' files', + self.dest.name) + + self.app.save() + @qubes.api.method('admin.vm.Clone') @asyncio.coroutine def vm_clone(self, untrusted_payload): diff --git a/qubes/tests/api_admin.py b/qubes/tests/api_admin.py index db417663..20ff7e0f 100644 --- a/qubes/tests/api_admin.py +++ b/qubes/tests/api_admin.py @@ -1550,6 +1550,27 @@ class TC_00_VMs(AdminAPITestCase): self.assertFalse(mock_detach.called) self.assertFalse(self.app.save.called) + @unittest.mock.patch('qubes.storage.Storage.remove') + @unittest.mock.patch('shutil.rmtree') + def test_500_vm_remove(self, mock_rmtree, mock_remove): + value = self.call_mgmt_func(b'admin.vm.Remove', b'test-vm1') + self.assertIsNone(value) + mock_rmtree.assert_called_once_with( + '/tmp/qubes-test-dir/appvms/test-vm1') + mock_remove.assert_called_once_with() + self.app.save.assert_called_once_with() + + @unittest.mock.patch('qubes.storage.Storage.remove') + @unittest.mock.patch('shutil.rmtree') + def test_501_vm_remove_running(self, mock_rmtree, mock_remove): + with unittest.mock.patch.object( + self.vm, 'get_power_state', lambda: 'Running'): + with self.assertRaises(qubes.exc.QubesVMNotHaltedError): + self.call_mgmt_func(b'admin.vm.Remove', b'test-vm1') + self.assertFalse(mock_rmtree.called) + self.assertFalse(mock_remove.called) + self.assertFalse(self.app.save.called) + def test_990_vm_unexpected_payload(self): methods_with_no_payload = [ b'admin.vm.List',