tools/qvm-shutdown: fix handling shutdown timeout for multiple VMs

When some VM timeout on shutdown, the tool will try to kill all of them,
but at this point some of them may be already powered off (not all
hanged during shutdown, but only some). Handle this
situation instead of crashing. And add appropriate test.
This commit is contained in:
Marek Marczykowski-Górecki 2017-10-28 22:40:24 +02:00
parent 2fac77da6f
commit 7bcab46f96
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 66 additions and 2 deletions

View File

@ -240,3 +240,59 @@ class TC_00_qvm_shutdown(qubesadmin.tests.QubesTestCase):
False): False):
qubesadmin.tools.qvm_shutdown.main(['--wait', '--all'], app=self.app) qubesadmin.tools.qvm_shutdown.main(['--wait', '--all'], app=self.app)
self.assertAllCalled() self.assertAllCalled()
@unittest.skipUnless(qubesadmin.tools.qvm_shutdown.have_events,
'Events not present')
def test_015_wait_all_kill_timeout(self):
'''test --wait option, with multiple VMs and killing on timeout'''
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
patch = unittest.mock.patch(
'qubesadmin.events.EventsDispatcher._get_events_reader')
mock_events = patch.start()
self.addCleanup(patch.stop)
mock_events.side_effect = qubesadmin.tests.tools.MockEventsReader([
b'1\0\0connection-established\0\0',
b'1\0sys-net\0domain-shutdown\0\0',
])
self.app.expected_calls[
('some-vm', 'admin.vm.Shutdown', None, None)] = \
b'0\x00'
self.app.expected_calls[
('some-vm', 'admin.vm.Kill', None, None)] = \
b'2\x00QubesVMNotStartedError\x00\x00Domain is powered off\x00'
self.app.expected_calls[
('other-vm', 'admin.vm.Shutdown', None, None)] = \
b'0\x00'
self.app.expected_calls[
('other-vm', 'admin.vm.Kill', None, None)] = \
b'0\x00'
self.app.expected_calls[
('sys-net', 'admin.vm.Shutdown', None, None)] = \
b'0\x00'
self.app.expected_calls[
('sys-net', 'admin.vm.Kill', None, None)] = \
b'2\x00QubesVMNotStartedError\x00\x00Domain is powered off\x00'
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00' \
b'sys-net class=AppVM state=Running\n' \
b'some-vm class=AppVM state=Running\n' \
b'other-vm class=AppVM state=Running\n'
self.app.expected_calls[
('some-vm', 'admin.vm.List', None, None)] = [
b'0\x00some-vm class=AppVM state=Running\n',
]
self.app.expected_calls[
('other-vm', 'admin.vm.List', None, None)] = [
b'0\x00other-vm class=AppVM state=Running\n',
b'0\x00other-vm class=AppVM state=Running\n',
]
self.app.expected_calls[
('sys-net', 'admin.vm.List', None, None)] = \
b'0\x00sys-net class=AppVM state=Halted\n'
qubesadmin.tools.qvm_shutdown.main(
['--wait', '--all', '--timeout=1'], app=self.app)
self.assertAllCalled()

View File

@ -87,7 +87,11 @@ def main(args=None, app=None): # pylint: disable=missing-docstring
args.timeout)) args.timeout))
except asyncio.TimeoutError: except asyncio.TimeoutError:
for vm in this_round_domains: for vm in this_round_domains:
vm.kill() try:
vm.kill()
except qubesadmin.exc.QubesVMNotStartedError:
# already shut down
pass
else: else:
timeout = args.timeout timeout = args.timeout
current_vms = list(sorted(this_round_domains)) current_vms = list(sorted(this_round_domains))
@ -105,7 +109,11 @@ def main(args=None, app=None): # pylint: disable=missing-docstring
'Killing remaining qubes: {}' 'Killing remaining qubes: {}'
.format(', '.join([str(vm) for vm in current_vms]))) .format(', '.join([str(vm) for vm in current_vms])))
for vm in current_vms: for vm in current_vms:
vm.kill() try:
vm.kill()
except qubesadmin.exc.QubesVMNotStartedError:
# already shut down
pass
if args.wait: if args.wait:
if have_events: if have_events: