tools: handle shutdown order in qvm-shutdown

VMs can have runtime dependencies - for example it isn't possible to
shutdown netvm used by some other running VM(s). Since client-side tools
may not have full knowledge about rules enforcing those dependencies
(for example may not have access to 'netvm' property), implement
best-effort approach:
1. Try to shutdown all requested VMs
2. For those where shutdown request succeed, wait for actual shutdown
3. For others - go back to step 1

And loop until all VMs are shutdown, or all shutdown requests fails.
This commit is contained in:
Marek Marczykowski-Górecki 2017-06-25 21:03:06 +02:00
parent 2052b32202
commit 389252f386
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724

View File

@ -27,6 +27,13 @@ from __future__ import print_function
import sys import sys
import time import time
import asyncio
try:
import qubesadmin.events.utils
have_events = True
except ImportError:
have_events = False
import qubesadmin.tools import qubesadmin.tools
import qubesadmin.exc import qubesadmin.exc
@ -52,33 +59,64 @@ parser.add_argument('--timeout',
def main(args=None, app=None): # pylint: disable=missing-docstring def main(args=None, app=None): # pylint: disable=missing-docstring
args = parser.parse_args(args, app=app) args = parser.parse_args(args, app=app)
for vm in args.domains: if have_events:
try: loop = asyncio.get_event_loop()
vm.shutdown(force=args.force) remaining_domains = args.domains
except qubesadmin.exc.QubesVMNotStartedError: for _ in range(len(args.domains)):
pass this_round_domains = set(remaining_domains)
if not this_round_domains:
if not args.wait: break
return remaining_domains = set()
for vm in this_round_domains:
timeout = args.timeout try:
current_vms = list(sorted(args.domains)) vm.shutdown()
while timeout >= 0: except qubesadmin.exc.QubesVMNotRunningError:
current_vms = [vm for vm in current_vms pass
if vm.get_power_state() != 'Halted'] except qubesadmin.exc.QubesException as e:
if not current_vms: if not args.wait:
return 0 vm.log.error('Shutdown error: {}'.format(e))
args.app.log.info('Waiting for shutdown ({}): {}'.format( else:
timeout, ', '.join([str(vm) for vm in current_vms]))) remaining_domains.add(vm)
time.sleep(1) if not args.wait:
timeout -= 1 return len(remaining_domains)
this_round_domains.difference_update(remaining_domains)
args.app.log.info( if not this_round_domains:
'Killing remaining qubes: {}' # no VM shutdown request succeed, no sense to try again
.format(', '.join([str(vm) for vm in current_vms]))) break
for vm in current_vms: if have_events:
vm.kill() try:
# pylint: disable=no-member
loop.run_until_complete(asyncio.wait_for(
qubesadmin.events.utils.wait_for_domain_shutdown(
this_round_domains),
args.timeout))
except asyncio.TimeoutError:
for vm in this_round_domains:
vm.kill()
else:
timeout = args.timeout
current_vms = list(sorted(this_round_domains))
while timeout >= 0:
current_vms = [vm for vm in current_vms
if vm.get_power_state() != 'Halted']
if not current_vms:
break
args.app.log.info('Waiting for shutdown ({}): {}'.format(
timeout, ', '.join([str(vm) for vm in current_vms])))
time.sleep(1)
timeout -= 1
if current_vms:
args.app.log.info(
'Killing remaining qubes: {}'
.format(', '.join([str(vm) for vm in current_vms])))
for vm in current_vms:
vm.kill()
if args.wait:
if have_events:
loop.close()
return len([vm for vm in args.domains
if vm.get_power_state() != 'Halted'])
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) sys.exit(main())