Skip to content

Commit 9e6f3dd

Browse files
committed
Wait for multiple shutdowns without events
- Avoids extra calls that might not be available - Gather returned exception to raise appropriate message and act accordingly
1 parent 5a44219 commit 9e6f3dd

2 files changed

Lines changed: 175 additions & 228 deletions

File tree

qubesadmin/tests/tools/qvm_shutdown.py

Lines changed: 39 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
# pylint: disable=missing-docstring
2222

2323
import asyncio
24-
import unittest.mock
2524

2625
import qubesadmin.tests
2726
import qubesadmin.tests.tools
@@ -88,27 +87,12 @@ def test_010_wait(self):
8887
loop = asyncio.new_event_loop()
8988
asyncio.set_event_loop(loop)
9089

91-
mock_events = unittest.mock.AsyncMock()
92-
patch = unittest.mock.patch(
93-
'qubesadmin.events.EventsDispatcher._get_events_reader',
94-
mock_events)
95-
patch.start()
96-
self.addCleanup(patch.stop)
97-
mock_events.side_effect = qubesadmin.tests.tools.MockEventsReader([
98-
b'1\0\0connection-established\0\0',
99-
b'1\0some-vm\0domain-shutdown\0\0',
100-
])
101-
10290
self.app.expected_calls[
103-
('some-vm', 'admin.vm.Shutdown', None, None)] = \
91+
('some-vm', 'admin.vm.Shutdown', 'wait', None)] = \
10492
b'0\x00'
10593
self.app.expected_calls[
10694
('dom0', 'admin.vm.List', None, None)] = \
10795
b'0\x00some-vm class=AppVM state=Running\n'
108-
self.app.expected_calls[
109-
('some-vm', 'admin.vm.CurrentState', None, None)] = \
110-
[b'0\x00power_state=Running'] + \
111-
[b'0\x00power_state=Halted']
11296
qubesadmin.tools.qvm_shutdown.main(['--wait', 'some-vm'], app=self.app)
11397
self.assertAllCalled()
11498

@@ -117,43 +101,21 @@ def test_012_wait_all(self):
117101
loop = asyncio.new_event_loop()
118102
asyncio.set_event_loop(loop)
119103

120-
mock_events = unittest.mock.AsyncMock()
121-
patch = unittest.mock.patch(
122-
'qubesadmin.events.EventsDispatcher._get_events_reader',
123-
mock_events)
124-
patch.start()
125-
self.addCleanup(patch.stop)
126-
mock_events.side_effect = qubesadmin.tests.tools.MockEventsReader([
127-
b'1\0\0connection-established\0\0',
128-
b'1\0sys-net\0domain-shutdown\0\0',
129-
b'1\0some-vm\0domain-shutdown\0\0',
130-
b'1\0other-vm\0domain-shutdown\0\0',
131-
])
132-
133104
self.app.expected_calls[
134-
('some-vm', 'admin.vm.Shutdown', 'force', None)] = \
105+
('some-vm', 'admin.vm.Shutdown', 'force+wait', None)] = \
135106
b'0\x00'
136107
self.app.expected_calls[
137-
('other-vm', 'admin.vm.Shutdown', 'force', None)] = \
108+
('other-vm', 'admin.vm.Shutdown', 'force+wait', None)] = \
138109
b'0\x00'
139110
self.app.expected_calls[
140-
('sys-net', 'admin.vm.Shutdown', 'force', None)] = \
111+
('sys-net', 'admin.vm.Shutdown', 'force+wait', None)] = \
141112
b'0\x00'
142113
self.app.expected_calls[
143114
('dom0', 'admin.vm.List', None, None)] = \
144115
b'0\x00' \
145116
b'sys-net class=AppVM state=Running\n' \
146117
b'some-vm class=AppVM state=Running\n' \
147118
b'other-vm class=AppVM state=Running\n'
148-
self.app.expected_calls[
149-
('some-vm', 'admin.vm.CurrentState', None, None)] = \
150-
b'0\x00power_state=Halted'
151-
self.app.expected_calls[
152-
('other-vm', 'admin.vm.CurrentState', None, None)] = \
153-
b'0\x00power_state=Halted'
154-
self.app.expected_calls[
155-
('sys-net', 'admin.vm.CurrentState', None, None)] = \
156-
b'0\x00power_state=Halted'
157119
qubesadmin.tools.qvm_shutdown.main(['--wait', '--all'], app=self.app)
158120
self.assertAllCalled()
159121

@@ -162,56 +124,27 @@ def test_015_wait_all_kill_timeout(self):
162124
loop = asyncio.new_event_loop()
163125
asyncio.set_event_loop(loop)
164126

165-
mock_events = unittest.mock.AsyncMock()
166-
patch = unittest.mock.patch(
167-
'qubesadmin.events.EventsDispatcher._get_events_reader',
168-
mock_events)
169-
patch.start()
170-
self.addCleanup(patch.stop)
171-
mock_events.side_effect = qubesadmin.tests.tools.MockEventsReader([
172-
b'1\0\0connection-established\0\0',
173-
b'1\0sys-net\0domain-shutdown\0\0',
174-
])
175-
176127
self.app.expected_calls[
177-
('some-vm', 'admin.vm.Shutdown', 'force', None)] = \
178-
b'0\x00'
128+
('some-vm', 'admin.vm.Shutdown', 'force+wait', None)] = \
129+
b'2\x00QubesVMShutdownTimeoutError\x00\x00Shutdown timed out\x00'
179130
self.app.expected_calls[
180131
('some-vm', 'admin.vm.Kill', None, None)] = \
181132
b'2\x00QubesVMNotStartedError\x00\x00Domain is powered off\x00'
182133
self.app.expected_calls[
183-
('other-vm', 'admin.vm.Shutdown', 'force', None)] = \
184-
b'0\x00'
134+
('other-vm', 'admin.vm.Shutdown', 'force+wait', None)] = \
135+
b'2\x00QubesVMShutdownTimeoutError\x00\x00Shutdown timed out\x00'
185136
self.app.expected_calls[
186137
('other-vm', 'admin.vm.Kill', None, None)] = \
187138
b'0\x00'
188139
self.app.expected_calls[
189-
('sys-net', 'admin.vm.Shutdown', 'force', None)] = \
140+
('sys-net', 'admin.vm.Shutdown', 'force+wait', None)] = \
190141
b'0\x00'
191142
self.app.expected_calls[
192143
('dom0', 'admin.vm.List', None, None)] = \
193144
b'0\x00' \
194145
b'sys-net class=AppVM state=Running\n' \
195146
b'some-vm class=AppVM state=Running\n' \
196147
b'other-vm class=AppVM state=Running\n'
197-
self.app.expected_calls[
198-
('some-vm', 'admin.vm.CurrentState', None, None)] = [
199-
b'0\x00power_state=Running',
200-
b'0\x00power_state=Running',
201-
b'0\x00power_state=Running',
202-
]
203-
self.app.expected_calls[
204-
('other-vm', 'admin.vm.CurrentState', None, None)] = [
205-
b'0\x00power_state=Running',
206-
b'0\x00power_state=Running',
207-
b'0\x00power_state=Running',
208-
]
209-
self.app.expected_calls[
210-
('sys-net', 'admin.vm.CurrentState', None, None)] = [
211-
b'0\x00power_state=Halted',
212-
b'0\x00power_state=Halted',
213-
b'0\x00power_state=Halted',
214-
]
215148
with self.assertRaisesRegex(SystemExit, '2'):
216149
qubesadmin.tools.qvm_shutdown.main(
217150
['--wait', '--all', '--timeout=1'], app=self.app)
@@ -271,44 +204,47 @@ def test_011_wait_retry(self):
271204
loop = asyncio.new_event_loop()
272205
asyncio.set_event_loop(loop)
273206

274-
mock_events = unittest.mock.AsyncMock()
275-
patch = unittest.mock.patch(
276-
'qubesadmin.events.EventsDispatcher._get_events_reader',
277-
mock_events)
278-
patch.start()
279-
self.addCleanup(patch.stop)
280-
mock_events.side_effect = qubesadmin.tests.tools.MockEventsReader([
281-
# round 1: wait for some-vm
282-
b'1\0\0connection-established\0\0',
283-
b'1\0some-vm\0domain-shutdown\0\0',
284-
# round 2: wait for other-vm
285-
b'1\0\0connection-established\0\0',
286-
b'1\0other-vm\0domain-shutdown\0\0',
287-
])
288-
289207
self.app.expected_calls[
290208
('dom0', 'admin.vm.List', None, None)] = \
291209
b'0\x00' \
292210
b'some-vm class=AppVM state=Running\n' \
293211
b'other-vm class=AppVM state=Running\n'
294212
self.app.expected_calls[
295-
('some-vm', 'admin.vm.Shutdown', None, None)] = \
213+
('some-vm', 'admin.vm.Shutdown', 'wait', None)] = \
296214
b'0\x00'
297215
# other-vm fails first attempt, succeeds on retry
298216
self.app.expected_calls[
299-
('other-vm', 'admin.vm.Shutdown', None, None)] = [
300-
b'2\x00QubesException\x00\x00Shutdown refused\x00',
217+
('other-vm', 'admin.vm.Shutdown', 'wait', None)] = [
218+
b'2\x00QubesVMInUseError\x00\x00Denied as qube is in use\x00',
301219
b'0\x00',
302220
]
221+
qubesadmin.tools.qvm_shutdown.main(
222+
['--wait', 'some-vm', 'other-vm'], app=self.app)
223+
self.assertAllCalled()
224+
225+
def test_012_wait_retry(self):
226+
'''test --wait retries VMs whose shutdown request failed as many as
227+
there were used qubes'''
228+
loop = asyncio.new_event_loop()
229+
asyncio.set_event_loop(loop)
230+
303231
self.app.expected_calls[
304-
('some-vm', 'admin.vm.CurrentState', None, None)] = [
305-
b'0\x00power_state=Running',
306-
b'0\x00power_state=Halted',
232+
('dom0', 'admin.vm.List', None, None)] = \
233+
b'0\x00' \
234+
b'some-vm class=AppVM state=Running\n' \
235+
b'other-vm class=AppVM state=Running\n'
236+
# some-vm fails first attempt, succeeds on retry
237+
self.app.expected_calls[
238+
('some-vm', 'admin.vm.Shutdown', 'wait', None)] = [
239+
b'2\x00QubesVMInUseError\x00\x00Denied as qube is in use\x00', \
240+
b'0\x00'
307241
]
242+
# other-vm fails first and second attempt, succeeds on retry
308243
self.app.expected_calls[
309-
('other-vm', 'admin.vm.CurrentState', None, None)] = [
310-
b'0\x00power_state=Running',
311-
b'0\x00power_state=Halted',
244+
('other-vm', 'admin.vm.Shutdown', 'wait', None)] = [
245+
b'2\x00QubesVMInUseError\x00\x00Denied as qube is in use\x00', \
246+
b'2\x00QubesVMInUseError\x00\x00Denied as qube is in use\x00',
247+
b'0\x00',
312248
]
313249
qubesadmin.tools.qvm_shutdown.main(
314250
['--wait', 'some-vm', 'other-vm'], app=self.app)
@@ -320,11 +256,8 @@ def test_013_wait_all_shutdown_fail(self):
320256
('dom0', 'admin.vm.List', None, None)] = \
321257
b'0\x00some-vm class=AppVM state=Running\n'
322258
self.app.expected_calls[
323-
('some-vm', 'admin.vm.Shutdown', None, None)] = \
259+
('some-vm', 'admin.vm.Shutdown', 'wait', None)] = \
324260
b'2\x00QubesException\x00\x00Shutdown refused\x00'
325-
self.app.expected_calls[
326-
('some-vm', 'admin.vm.CurrentState', None, None)] = \
327-
b'0\x00power_state=Running'
328261
with self.assertRaises(SystemExit):
329262
qubesadmin.tools.qvm_shutdown.main(
330263
['--wait', 'some-vm'], app=self.app)
@@ -335,65 +268,16 @@ def test_016_wait_kill_exception(self):
335268
loop = asyncio.new_event_loop()
336269
asyncio.set_event_loop(loop)
337270

338-
mock_events = unittest.mock.AsyncMock()
339-
patch = unittest.mock.patch(
340-
'qubesadmin.events.EventsDispatcher._get_events_reader',
341-
mock_events)
342-
patch.start()
343-
self.addCleanup(patch.stop)
344-
mock_events.side_effect = qubesadmin.tests.tools.MockEventsReader([
345-
b'1\0\0connection-established\0\0',
346-
])
347-
348271
self.app.expected_calls[
349272
('dom0', 'admin.vm.List', None, None)] = \
350273
b'0\x00some-vm class=AppVM state=Running\n'
351274
self.app.expected_calls[
352-
('some-vm', 'admin.vm.Shutdown', None, None)] = \
353-
b'0\x00'
275+
('some-vm', 'admin.vm.Shutdown', 'wait', None)] = \
276+
b'2\x00QubesVMShutdownTimeoutError\x00\x00Shutdown timed out\x00'
354277
self.app.expected_calls[
355278
('some-vm', 'admin.vm.Kill', None, None)] = \
356279
b'2\x00QubesException\x00\x00Kill failed\x00'
357-
self.app.expected_calls[
358-
('some-vm', 'admin.vm.CurrentState', None, None)] = [
359-
b'0\x00power_state=Running',
360-
b'0\x00power_state=Running',
361-
]
362280
with self.assertRaises(SystemExit):
363281
qubesadmin.tools.qvm_shutdown.main(
364282
['--wait', '--timeout=1', 'some-vm'], app=self.app)
365283
self.assertAllCalled()
366-
367-
def test_017_wait_dispvm_na(self):
368-
'''test --wait treats DispVM with NA power state as shut down'''
369-
loop = asyncio.new_event_loop()
370-
asyncio.set_event_loop(loop)
371-
372-
mock_events = unittest.mock.AsyncMock()
373-
patch = unittest.mock.patch(
374-
'qubesadmin.events.EventsDispatcher._get_events_reader',
375-
mock_events)
376-
patch.start()
377-
self.addCleanup(patch.stop)
378-
mock_events.side_effect = qubesadmin.tests.tools.MockEventsReader([
379-
b'1\0\0connection-established\0\0',
380-
b'1\0disp123\0domain-shutdown\0\0',
381-
])
382-
383-
self.app.expected_calls[
384-
('dom0', 'admin.vm.List', None, None)] = \
385-
b'0\x00disp123 class=DispVM state=Running\n'
386-
self.app.expected_calls[
387-
('disp123', 'admin.vm.Shutdown', None, None)] = \
388-
b'0\x00'
389-
self.app.expected_calls[
390-
('disp123', 'admin.vm.CurrentState', None, None)] = [
391-
b'0\x00power_state=Running',
392-
# failed_domains: first get_power_state() != 'Halted',
393-
# then klass == 'DispVM' triggers second get_power_state()
394-
b'0\x00power_state=NA',
395-
b'0\x00power_state=NA',
396-
]
397-
qubesadmin.tools.qvm_shutdown.main(
398-
['--wait', 'disp123'], app=self.app)
399-
self.assertAllCalled()

0 commit comments

Comments
 (0)