Skip to content

Commit e7d49e3

Browse files
committed
Expose S3 graceful VM suspend/resume API calls
This could be useful for preloaded disposables as (emergency) paused disposables might hang after hypervisor goes to suspend mode and resumes from it. related: QubesOS/qubes-issues#9918
1 parent 32d1be7 commit e7d49e3

File tree

2 files changed

+38
-0
lines changed

2 files changed

+38
-0
lines changed

qubes/api/admin.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,22 @@ async def vm_unpause(self):
992992
self.fire_event_for_permission()
993993
await self.dest.unpause()
994994

995+
@qubes.api.method(
996+
"admin.vm.Suspend", no_payload=True, scope="local", execute=True
997+
)
998+
async def vm_suspend(self):
999+
self.enforce(not self.arg)
1000+
self.fire_event_for_permission()
1001+
await self.dest.suspend()
1002+
1003+
@qubes.api.method(
1004+
"admin.vm.Resume", no_payload=True, scope="local", execute=True
1005+
)
1006+
async def vm_resume(self):
1007+
self.enforce(not self.arg)
1008+
self.fire_event_for_permission()
1009+
await self.dest.resume()
1010+
9951011
@qubes.api.method(
9961012
"admin.vm.Kill", no_payload=True, scope="local", execute=True
9971013
)

qubes/tests/api_admin.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,17 @@ async def coroutine_mock(*args, **kwargs):
14391439
self.assertIsNone(value)
14401440
func_mock.assert_called_once_with()
14411441

1442+
def test_241_suspend(self):
1443+
func_mock = unittest.mock.Mock()
1444+
1445+
async def coroutine_mock(*args, **kwargs):
1446+
return func_mock(*args, **kwargs)
1447+
1448+
self.vm.suspend = coroutine_mock
1449+
value = self.call_mgmt_func(b"admin.vm.Suspend", b"test-vm1")
1450+
self.assertIsNone(value)
1451+
func_mock.assert_called_once_with()
1452+
14421453
def test_250_unpause(self):
14431454
func_mock = unittest.mock.Mock()
14441455

@@ -1450,6 +1461,17 @@ async def coroutine_mock(*args, **kwargs):
14501461
self.assertIsNone(value)
14511462
func_mock.assert_called_once_with()
14521463

1464+
def test_251_resume(self):
1465+
func_mock = unittest.mock.Mock()
1466+
1467+
async def coroutine_mock(*args, **kwargs):
1468+
return func_mock(*args, **kwargs)
1469+
1470+
self.vm.resume = coroutine_mock
1471+
value = self.call_mgmt_func(b"admin.vm.Resume", b"test-vm1")
1472+
self.assertIsNone(value)
1473+
func_mock.assert_called_once_with()
1474+
14531475
def test_260_kill(self):
14541476
func_mock = unittest.mock.Mock()
14551477

0 commit comments

Comments
 (0)