core-admin-client/qubesadmin/tools/qvm_shutdown.py
Marek Marczykowski-Górecki 7bcab46f96
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.
2017-10-28 22:40:24 +02:00

126 lines
4.3 KiB
Python

# encoding=utf-8
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010-2016 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2011-2016 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.com>
# Copyright (C) 2016 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.
''' Shutdown a qube '''
from __future__ import print_function
import sys
import time
import asyncio
try:
import qubesadmin.events.utils
have_events = True
except ImportError:
have_events = False
import qubesadmin.tools
import qubesadmin.exc
parser = qubesadmin.tools.QubesArgumentParser(
description=__doc__, vmname_nargs='+')
parser.add_argument('--wait',
action='store_true', default=False,
help='wait for the VMs to shut down')
parser.add_argument('--timeout',
action='store', type=float,
default=60,
help='timeout after which domains are killed when using --wait'
' (default: %(default)d)')
def main(args=None, app=None): # pylint: disable=missing-docstring
args = parser.parse_args(args, app=app)
if have_events:
loop = asyncio.get_event_loop()
remaining_domains = args.domains
for _ in range(len(args.domains)):
this_round_domains = set(remaining_domains)
if not this_round_domains:
break
remaining_domains = set()
for vm in this_round_domains:
try:
vm.shutdown()
except qubesadmin.exc.QubesVMNotRunningError:
pass
except qubesadmin.exc.QubesException as e:
if not args.wait:
vm.log.error('Shutdown error: {}'.format(e))
else:
remaining_domains.add(vm)
if not args.wait:
return len(remaining_domains)
this_round_domains.difference_update(remaining_domains)
if not this_round_domains:
# no VM shutdown request succeed, no sense to try again
break
if have_events:
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:
try:
vm.kill()
except qubesadmin.exc.QubesVMNotStartedError:
# already shut down
pass
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:
try:
vm.kill()
except qubesadmin.exc.QubesVMNotStartedError:
# already shut down
pass
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__':
sys.exit(main())