qvm-shutdown: report errors, don't crash on DispVMs
qvm-shutdown with the --wait option checks if the machine state is 'Halted', but a disposable VM is usually deleted by the time of the final check, resulting in a non-zero exit code. This change handles properly disposable VMs, and makes sure we always output an error message when finishing with a non-zero exit code. Fixes QubesOS/qubes-issues#5245.
This commit is contained in:
		
							parent
							
								
									de1969150d
								
							
						
					
					
						commit
						405464a67e
					
				| @ -293,6 +293,7 @@ class TC_00_qvm_shutdown(qubesadmin.tests.QubesTestCase): | ||||
|         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) | ||||
|         with self.assertRaisesRegexp(SystemExit, '2'): | ||||
|             qubesadmin.tools.qvm_shutdown.main( | ||||
|                 ['--wait', '--all', '--timeout=1'], app=self.app) | ||||
|         self.assertAllCalled() | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Pawel Marczewski
						Pawel Marczewski