diff --git a/qubesadmin/tests/tools/qvm_shutdown.py b/qubesadmin/tests/tools/qvm_shutdown.py index 458f9d2..2201594 100644 --- a/qubesadmin/tests/tools/qvm_shutdown.py +++ b/qubesadmin/tests/tools/qvm_shutdown.py @@ -293,6 +293,7 @@ class TC_00_qvm_shutdown(qubesadmin.tests.QubesTestCase): self.app.expected_calls[ ('sys-net', 'admin.vm.CurrentState', None, None)] = \ b'0\x00power_state=Halted' - qubesadmin.tools.qvm_shutdown.main( - ['--wait', '--all', '--timeout=1'], app=self.app) + with self.assertRaisesRegexp(SystemExit, '2'): + qubesadmin.tools.qvm_shutdown.main( + ['--wait', '--all', '--timeout=1'], app=self.app) self.assertAllCalled() diff --git a/qubesadmin/tools/__init__.py b/qubesadmin/tools/__init__.py index 25b9638..ef18e06 100644 --- a/qubesadmin/tools/__init__.py +++ b/qubesadmin/tools/__init__.py @@ -408,12 +408,12 @@ class QubesArgumentParser(argparse.ArgumentParser): return namespace - def error_runtime(self, message): + def error_runtime(self, message, exit_code=1): '''Runtime error, without showing usage. :param str message: message to show ''' - self.exit(1, '{}: error: {}\n'.format(self.prog, message)) + self.exit(exit_code, '{}: error: {}\n'.format(self.prog, message)) @staticmethod diff --git a/qubesadmin/tools/qvm_shutdown.py b/qubesadmin/tools/qvm_shutdown.py index 0263af5..f9b10cf 100644 --- a/qubesadmin/tools/qvm_shutdown.py +++ b/qubesadmin/tools/qvm_shutdown.py @@ -73,7 +73,12 @@ def main(args=None, app=None): # pylint: disable=missing-docstring else: remaining_domains.add(vm) if not args.wait: - return len(remaining_domains) + if remaining_domains: + parser.error_runtime( + 'Failed to shut down: ' + + ', '.join(vm.name for vm in remaining_domains), + len(remaining_domains)) + return this_round_domains.difference_update(remaining_domains) if not this_round_domains: # no VM shutdown request succeed, no sense to try again @@ -122,8 +127,20 @@ def main(args=None, app=None): # pylint: disable=missing-docstring if args.wait: if have_events: loop.close() - return len([vm for vm in args.domains - if vm.get_power_state() != 'Halted']) + failed = [] + for vm in args.domains: + power_state = vm.get_power_state() + # DispVM might have been deleted before we check them, + # so NA is acceptable. + if not (power_state == 'Halted' or + (vm.klass == 'DispVM' and power_state == 'NA')): + failed.append(vm) + if failed: + parser.error_runtime( + 'Failed to shut down: ' + + ', '.join(vm.name for vm in failed), + len(failed)) + if __name__ == '__main__': sys.exit(main())