Merge remote-tracking branch 'origin/pr/130'

* origin/pr/130:
  Added explicit 'force' option to qvm-shutdown
  Added force shutdown option to vm.shutdown
This commit is contained in:
Marek Marczykowski-Górecki 2020-02-07 04:35:22 +01:00
commit 0120e32340
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
4 changed files with 25 additions and 100 deletions

View File

@ -25,7 +25,7 @@ Options
.. option:: --all
perform the action on all qubes
perform the action on all qubes; implies :option:`--force`
.. option:: --exclude=EXCLUDE
@ -35,12 +35,18 @@ Options
wait for the VMs to shut down. If some domains are providing network to other
domains, wait for those domains to shut down before shutting down their
dependents.
dependents, unless :option:`--all` was specified
.. option:: --timeout
timeout after which domains are killed when using :option:`--wait`
.. option:: --force
force qube shutdown, regardless of whether there exist any connected domains
(such as those using it as network VM)
Authors
-------

View File

@ -126,13 +126,13 @@ class TC_00_qvm_shutdown(qubesadmin.tests.QubesTestCase):
])
self.app.expected_calls[
('some-vm', 'admin.vm.Shutdown', None, None)] = \
('some-vm', 'admin.vm.Shutdown', 'force', None)] = \
b'0\x00'
self.app.expected_calls[
('other-vm', 'admin.vm.Shutdown', None, None)] = \
('other-vm', 'admin.vm.Shutdown', 'force', None)] = \
b'0\x00'
self.app.expected_calls[
('sys-net', 'admin.vm.Shutdown', None, None)] = \
('sys-net', 'admin.vm.Shutdown', 'force', None)] = \
b'0\x00'
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
@ -152,95 +152,6 @@ class TC_00_qvm_shutdown(qubesadmin.tests.QubesTestCase):
qubesadmin.tools.qvm_shutdown.main(['--wait', '--all'], app=self.app)
self.assertAllCalled()
@unittest.skipUnless(qubesadmin.tools.qvm_shutdown.have_events,
'Events not present')
def test_013_wait_all_order(self):
'''test --wait option, with some VMs requiring specific shutdown
order'''
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',
b'1\0some-vm\0domain-shutdown\0\0',
b'1\0other-vm\0domain-shutdown\0\0',
])
self.app.expected_calls[
('some-vm', 'admin.vm.Shutdown', None, None)] = \
b'0\x00'
self.app.expected_calls[
('other-vm', 'admin.vm.Shutdown', None, None)] = \
b'0\x00'
self.app.expected_calls[
('sys-net', 'admin.vm.Shutdown', None, None)] = [
b'2\x00QubesVMError\x00\x00Other Vms connected\x00',
b'0\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.CurrentState', None, None)] = \
b'0\x00power_state=Halted'
self.app.expected_calls[
('other-vm', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Halted'
self.app.expected_calls[
('sys-net', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Halted'
qubesadmin.tools.qvm_shutdown.main(['--wait', '--all'], app=self.app)
self.assertAllCalled()
def test_014_wait_all_order_no_events(self):
'''test --wait option, with some VMs requiring specific shutdown
order'''
self.app.expected_calls[
('some-vm', 'admin.vm.Shutdown', None, None)] = \
b'0\x00'
self.app.expected_calls[
('other-vm', 'admin.vm.Shutdown', None, None)] = \
b'0\x00'
self.app.expected_calls[
('sys-net', 'admin.vm.Shutdown', None, None)] = [
b'2\x00QubesVMError\x00\x00Other Vms connected\x00',
b'0\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.CurrentState', None, None)] = \
[b'0\x00power_state=Running',
b'0\x00power_state=Halted',
b'0\x00power_state=Halted']
self.app.expected_calls[
('other-vm', 'admin.vm.CurrentState', None, None)] = \
[b'0\x00power_state=Running',
b'0\x00power_state=Halted',
b'0\x00power_state=Halted']
self.app.expected_calls[
('sys-net', 'admin.vm.CurrentState', None, None)] = \
[b'0\x00power_state=Running',
b'0\x00power_state=Halted',
b'0\x00power_state=Halted']
with unittest.mock.patch('qubesadmin.tools.qvm_shutdown.have_events',
False):
qubesadmin.tools.qvm_shutdown.main(['--wait', '--all'], app=self.app)
self.assertAllCalled()
@unittest.skipUnless(qubesadmin.tools.qvm_shutdown.have_events,
'Events not present')
def test_015_wait_all_kill_timeout(self):
@ -258,19 +169,19 @@ class TC_00_qvm_shutdown(qubesadmin.tests.QubesTestCase):
])
self.app.expected_calls[
('some-vm', 'admin.vm.Shutdown', None, None)] = \
('some-vm', 'admin.vm.Shutdown', 'force', 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)] = \
('other-vm', 'admin.vm.Shutdown', 'force', 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)] = \
('sys-net', 'admin.vm.Shutdown', 'force', None)] = \
b'0\x00'
self.app.expected_calls[
('sys-net', 'admin.vm.Kill', None, None)] = \

View File

@ -50,10 +50,17 @@ parser.add_argument('--timeout',
help='timeout after which domains are killed when using --wait'
' (default: %(default)d)')
parser.add_argument(
'--force',
action='store_true', default=False,
help='force shutdown regardless of connected domains; use with caution')
def main(args=None, app=None): # pylint: disable=missing-docstring
args = parser.parse_args(args, app=app)
force = args.force or bool(args.all_domains)
if have_events:
loop = asyncio.get_event_loop()
remaining_domains = args.domains
@ -64,7 +71,7 @@ def main(args=None, app=None): # pylint: disable=missing-docstring
remaining_domains = set()
for vm in this_round_domains:
try:
vm.shutdown()
vm.shutdown(force=force)
except qubesadmin.exc.QubesVMNotRunningError:
pass
except qubesadmin.exc.QubesException as e:

View File

@ -113,7 +113,8 @@ class QubesVM(qubesadmin.base.PropertyHolder):
# TODO: force parameter
# TODO: wait parameter (using event?)
if force:
raise NotImplementedError
self.qubesd_call(self._method_dest, 'admin.vm.Shutdown', 'force')
else:
self.qubesd_call(self._method_dest, 'admin.vm.Shutdown')
def kill(self):