2121# pylint: disable=missing-docstring
2222
2323import asyncio
24- import unittest .mock
2524
2625import qubesadmin .tests
2726import 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 \0 connection-established\0 \0 ' ,
99- b'1\0 some-vm\0 domain-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\x00 some-vm class=AppVM state=Running\n '
108- self .app .expected_calls [
109- ('some-vm' , 'admin.vm.CurrentState' , None , None )] = \
110- [b'0\x00 power_state=Running' ] + \
111- [b'0\x00 power_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 \0 connection-established\0 \0 ' ,
128- b'1\0 sys-net\0 domain-shutdown\0 \0 ' ,
129- b'1\0 some-vm\0 domain-shutdown\0 \0 ' ,
130- b'1\0 other-vm\0 domain-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\x00 power_state=Halted'
151- self .app .expected_calls [
152- ('other-vm' , 'admin.vm.CurrentState' , None , None )] = \
153- b'0\x00 power_state=Halted'
154- self .app .expected_calls [
155- ('sys-net' , 'admin.vm.CurrentState' , None , None )] = \
156- b'0\x00 power_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 \0 connection-established\0 \0 ' ,
173- b'1\0 sys-net\0 domain-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 \x00 QubesVMShutdownTimeoutError \x00 \x00 Shutdown timed out \x00 '
179130 self .app .expected_calls [
180131 ('some-vm' , 'admin.vm.Kill' , None , None )] = \
181132 b'2\x00 QubesVMNotStartedError\x00 \x00 Domain 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 \x00 QubesVMShutdownTimeoutError \x00 \x00 Shutdown 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\x00 power_state=Running' ,
200- b'0\x00 power_state=Running' ,
201- b'0\x00 power_state=Running' ,
202- ]
203- self .app .expected_calls [
204- ('other-vm' , 'admin.vm.CurrentState' , None , None )] = [
205- b'0\x00 power_state=Running' ,
206- b'0\x00 power_state=Running' ,
207- b'0\x00 power_state=Running' ,
208- ]
209- self .app .expected_calls [
210- ('sys-net' , 'admin.vm.CurrentState' , None , None )] = [
211- b'0\x00 power_state=Halted' ,
212- b'0\x00 power_state=Halted' ,
213- b'0\x00 power_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 \0 connection-established\0 \0 ' ,
283- b'1\0 some-vm\0 domain-shutdown\0 \0 ' ,
284- # round 2: wait for other-vm
285- b'1\0 \0 connection-established\0 \0 ' ,
286- b'1\0 other-vm\0 domain-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\x00 QubesException \x00 \x00 Shutdown refused \x00 ' ,
217+ ('other-vm' , 'admin.vm.Shutdown' , 'wait' , None )] = [
218+ b'2\x00 QubesVMInUseError \x00 \x00 Denied 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\x00 power_state=Running' ,
306- b'0\x00 power_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\x00 QubesVMInUseError\x00 \x00 Denied 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\x00 power_state=Running' ,
311- b'0\x00 power_state=Halted' ,
244+ ('other-vm' , 'admin.vm.Shutdown' , 'wait' , None )] = [
245+ b'2\x00 QubesVMInUseError\x00 \x00 Denied as qube is in use\x00 ' , \
246+ b'2\x00 QubesVMInUseError\x00 \x00 Denied 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\x00 some-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\x00 QubesException\x00 \x00 Shutdown refused\x00 '
325- self .app .expected_calls [
326- ('some-vm' , 'admin.vm.CurrentState' , None , None )] = \
327- b'0\x00 power_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 \0 connection-established\0 \0 ' ,
346- ])
347-
348271 self .app .expected_calls [
349272 ('dom0' , 'admin.vm.List' , None , None )] = \
350273 b'0\x00 some-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 \x00 QubesVMShutdownTimeoutError \x00 \x00 Shutdown timed out \x00 '
354277 self .app .expected_calls [
355278 ('some-vm' , 'admin.vm.Kill' , None , None )] = \
356279 b'2\x00 QubesException\x00 \x00 Kill failed\x00 '
357- self .app .expected_calls [
358- ('some-vm' , 'admin.vm.CurrentState' , None , None )] = [
359- b'0\x00 power_state=Running' ,
360- b'0\x00 power_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 \0 connection-established\0 \0 ' ,
380- b'1\0 disp123\0 domain-shutdown\0 \0 ' ,
381- ])
382-
383- self .app .expected_calls [
384- ('dom0' , 'admin.vm.List' , None , None )] = \
385- b'0\x00 disp123 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\x00 power_state=Running' ,
392- # failed_domains: first get_power_state() != 'Halted',
393- # then klass == 'DispVM' triggers second get_power_state()
394- b'0\x00 power_state=NA' ,
395- b'0\x00 power_state=NA' ,
396- ]
397- qubesadmin .tools .qvm_shutdown .main (
398- ['--wait' , 'disp123' ], app = self .app )
399- self .assertAllCalled ()
0 commit comments