1#!/usr/bin/env python3
2#
3# Test case for the QMP 'change' command and all other associated
4# commands
5#
6# Copyright (C) 2015 Red Hat, Inc.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20#
21
22import os
23import stat
24import time
25import iotests
26from iotests import qemu_img
27
28old_img = os.path.join(iotests.test_dir, 'test0.img')
29new_img = os.path.join(iotests.test_dir, 'test1.img')
30
31def interface_to_device_name(interface):
32    if interface == 'ide':
33        return 'ide-cd'
34    elif interface == 'floppy':
35        return 'floppy'
36    elif interface == 'scsi':
37        return 'scsi-cd'
38    else:
39        return None
40
41class ChangeBaseClass(iotests.QMPTestCase):
42    has_opened = False
43    has_closed = False
44
45    device_name = 'qdev0'
46    use_drive = False
47
48    def process_events(self):
49        for event in self.vm.get_qmp_events(wait=False):
50            if (event['event'] == 'DEVICE_TRAY_MOVED' and
51                (event['data']['device'] == 'drive0' or
52                 event['data']['id'] == self.device_name)):
53                if event['data']['tray-open'] == False:
54                    self.has_closed = True
55                else:
56                    self.has_opened = True
57
58    def wait_for_open(self):
59        if not self.has_real_tray:
60            return
61
62        with iotests.Timeout(3, 'Timeout while waiting for the tray to open'):
63            while not self.has_opened:
64                self.process_events()
65
66    def wait_for_close(self):
67        if not self.has_real_tray:
68            return
69
70        with iotests.Timeout(3, 'Timeout while waiting for the tray to close'):
71            while not self.has_closed:
72                self.process_events()
73
74class GeneralChangeTestsBaseClass(ChangeBaseClass):
75
76    def test_change(self):
77        # 'change' requires a drive name, so skip the test for blockdev
78        if not self.use_drive:
79            return
80
81        result = self.vm.qmp('change', device='drive0', target=new_img,
82                                       arg=iotests.imgfmt)
83        self.assert_qmp(result, 'return', {})
84
85        self.wait_for_open()
86        self.wait_for_close()
87
88        result = self.vm.qmp('query-block')
89        if self.has_real_tray:
90            self.assert_qmp(result, 'return[0]/tray_open', False)
91        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
92
93    def test_blockdev_change_medium(self):
94        result = self.vm.qmp('blockdev-change-medium',
95                             id=self.device_name, filename=new_img,
96                             format=iotests.imgfmt)
97
98        self.assert_qmp(result, 'return', {})
99
100        self.wait_for_open()
101        self.wait_for_close()
102
103        result = self.vm.qmp('query-block')
104        if self.has_real_tray:
105            self.assert_qmp(result, 'return[0]/tray_open', False)
106        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
107
108    def test_eject(self):
109        result = self.vm.qmp('eject', id=self.device_name, force=True)
110        self.assert_qmp(result, 'return', {})
111
112        self.wait_for_open()
113
114        result = self.vm.qmp('query-block')
115        if self.has_real_tray:
116            self.assert_qmp(result, 'return[0]/tray_open', True)
117        self.assert_qmp_absent(result, 'return[0]/inserted')
118
119    def test_tray_eject_change(self):
120        result = self.vm.qmp('eject', id=self.device_name, force=True)
121        self.assert_qmp(result, 'return', {})
122
123        self.wait_for_open()
124
125        result = self.vm.qmp('query-block')
126        if self.has_real_tray:
127            self.assert_qmp(result, 'return[0]/tray_open', True)
128        self.assert_qmp_absent(result, 'return[0]/inserted')
129
130        result = self.vm.qmp('blockdev-change-medium', id=self.device_name,
131                             filename=new_img, format=iotests.imgfmt)
132        self.assert_qmp(result, 'return', {})
133
134        self.wait_for_close()
135
136        result = self.vm.qmp('query-block')
137        if self.has_real_tray:
138            self.assert_qmp(result, 'return[0]/tray_open', False)
139        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
140
141    def test_tray_open_close(self):
142        result = self.vm.qmp('blockdev-open-tray',
143                             id=self.device_name, force=True)
144        self.assert_qmp(result, 'return', {})
145
146        self.wait_for_open()
147
148        result = self.vm.qmp('query-block')
149        if self.has_real_tray:
150            self.assert_qmp(result, 'return[0]/tray_open', True)
151        if self.was_empty == True:
152            self.assert_qmp_absent(result, 'return[0]/inserted')
153        else:
154            self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
155
156        result = self.vm.qmp('blockdev-close-tray', id=self.device_name)
157        self.assert_qmp(result, 'return', {})
158
159        if self.has_real_tray or not self.was_empty:
160            self.wait_for_close()
161
162        result = self.vm.qmp('query-block')
163        if self.has_real_tray:
164            self.assert_qmp(result, 'return[0]/tray_open', False)
165        if self.was_empty == True:
166            self.assert_qmp_absent(result, 'return[0]/inserted')
167        else:
168            self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
169
170    def test_tray_eject_close(self):
171        result = self.vm.qmp('eject', id=self.device_name, force=True)
172        self.assert_qmp(result, 'return', {})
173
174        self.wait_for_open()
175
176        result = self.vm.qmp('query-block')
177        if self.has_real_tray:
178            self.assert_qmp(result, 'return[0]/tray_open', True)
179        self.assert_qmp_absent(result, 'return[0]/inserted')
180
181        result = self.vm.qmp('blockdev-close-tray', id=self.device_name)
182        self.assert_qmp(result, 'return', {})
183
184        self.wait_for_close()
185
186        result = self.vm.qmp('query-block')
187        if self.has_real_tray:
188            self.assert_qmp(result, 'return[0]/tray_open', False)
189        self.assert_qmp_absent(result, 'return[0]/inserted')
190
191    def test_tray_open_change(self):
192        result = self.vm.qmp('blockdev-open-tray', id=self.device_name,
193                                                   force=True)
194        self.assert_qmp(result, 'return', {})
195
196        self.wait_for_open()
197
198        result = self.vm.qmp('query-block')
199        if self.has_real_tray:
200            self.assert_qmp(result, 'return[0]/tray_open', True)
201        if self.was_empty == True:
202            self.assert_qmp_absent(result, 'return[0]/inserted')
203        else:
204            self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
205
206        result = self.vm.qmp('blockdev-change-medium', id=self.device_name,
207                                                       filename=new_img,
208                                                       format=iotests.imgfmt)
209        self.assert_qmp(result, 'return', {})
210
211        self.wait_for_close()
212
213        result = self.vm.qmp('query-block')
214        if self.has_real_tray:
215            self.assert_qmp(result, 'return[0]/tray_open', False)
216        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
217
218    def test_cycle(self, read_only_node=False):
219        result = self.vm.qmp('blockdev-add',
220                             node_name='new',
221                             driver=iotests.imgfmt,
222                             read_only=read_only_node,
223                             file={'filename': new_img,
224                                    'driver': 'file'})
225        self.assert_qmp(result, 'return', {})
226
227        result = self.vm.qmp('blockdev-open-tray',
228                             id=self.device_name, force=True)
229        self.assert_qmp(result, 'return', {})
230
231        self.wait_for_open()
232
233        result = self.vm.qmp('query-block')
234        if self.has_real_tray:
235            self.assert_qmp(result, 'return[0]/tray_open', True)
236        if self.was_empty == True:
237            self.assert_qmp_absent(result, 'return[0]/inserted')
238        else:
239            self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
240
241        result = self.vm.qmp('blockdev-remove-medium',
242                             id=self.device_name)
243        self.assert_qmp(result, 'return', {})
244
245        result = self.vm.qmp('query-block')
246        if self.has_real_tray:
247            self.assert_qmp(result, 'return[0]/tray_open', True)
248        self.assert_qmp_absent(result, 'return[0]/inserted')
249
250        result = self.vm.qmp('blockdev-insert-medium',
251                             id=self.device_name, node_name='new')
252        self.assert_qmp(result, 'return', {})
253
254        result = self.vm.qmp('query-block')
255        if self.has_real_tray:
256            self.assert_qmp(result, 'return[0]/tray_open', True)
257        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
258
259        result = self.vm.qmp('blockdev-close-tray', id=self.device_name)
260        self.assert_qmp(result, 'return', {})
261
262        self.wait_for_close()
263
264        result = self.vm.qmp('query-block')
265        if self.has_real_tray:
266            self.assert_qmp(result, 'return[0]/tray_open', False)
267        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
268
269    def test_cycle_read_only_media(self):
270        self.test_cycle(True)
271
272    def test_close_on_closed(self):
273        result = self.vm.qmp('blockdev-close-tray', id=self.device_name)
274        # Should be a no-op
275        self.assert_qmp(result, 'return', {})
276        self.assertEqual(self.vm.get_qmp_events(wait=False), [])
277
278    def test_remove_on_closed(self):
279        if not self.has_real_tray:
280            return
281
282        result = self.vm.qmp('blockdev-remove-medium', id=self.device_name)
283        self.assert_qmp(result, 'error/class', 'GenericError')
284
285    def test_insert_on_closed(self):
286        if not self.has_real_tray:
287            return
288
289        result = self.vm.qmp('blockdev-add',
290                             node_name='new',
291                             driver=iotests.imgfmt,
292                             file={'filename': new_img,
293                                   'driver': 'file'})
294        self.assert_qmp(result, 'return', {})
295
296        result = self.vm.qmp('blockdev-insert-medium', id=self.device_name,
297                                                       node_name='new')
298        self.assert_qmp(result, 'error/class', 'GenericError')
299
300class TestInitiallyFilled(GeneralChangeTestsBaseClass):
301    was_empty = False
302
303    def setUp(self):
304        qemu_img('create', '-f', iotests.imgfmt, old_img, '1440k')
305        qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k')
306        self.vm = iotests.VM()
307        if self.use_drive:
308            self.vm.add_drive(old_img, 'media=%s' % self.media, 'none')
309        else:
310            self.vm.add_blockdev([ 'node-name=drive0',
311                                   'driver=%s' % iotests.imgfmt,
312                                   'file.driver=file',
313                                   'file.filename=%s' % old_img ])
314        if self.interface == 'scsi':
315            self.vm.add_device('virtio-scsi-pci')
316        self.vm.add_device('%s,drive=drive0,id=%s' %
317                           (interface_to_device_name(self.interface),
318                            self.device_name))
319        self.vm.launch()
320
321    def tearDown(self):
322        self.vm.shutdown()
323        os.remove(old_img)
324        os.remove(new_img)
325
326    def test_insert_on_filled(self):
327        result = self.vm.qmp('blockdev-add',
328                             node_name='new',
329                             driver=iotests.imgfmt,
330                             file={'filename': new_img,
331                                   'driver': 'file'})
332        self.assert_qmp(result, 'return', {})
333
334        result = self.vm.qmp('blockdev-open-tray', id=self.device_name)
335        self.assert_qmp(result, 'return', {})
336
337        self.wait_for_open()
338
339        result = self.vm.qmp('blockdev-insert-medium', id=self.device_name,
340                                                       node_name='new')
341        self.assert_qmp(result, 'error/class', 'GenericError')
342
343class TestInitiallyEmpty(GeneralChangeTestsBaseClass):
344    was_empty = True
345
346    def setUp(self):
347        qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k')
348        self.vm = iotests.VM()
349        if self.use_drive:
350            self.vm.add_drive(None, 'media=%s' % self.media, 'none')
351        if self.interface == 'scsi':
352            self.vm.add_device('virtio-scsi-pci')
353        self.vm.add_device('%s,%sid=%s' %
354                           (interface_to_device_name(self.interface),
355                            'drive=drive0,' if self.use_drive else '',
356                            self.device_name))
357        self.vm.launch()
358
359    def tearDown(self):
360        self.vm.shutdown()
361        os.remove(new_img)
362
363    def test_remove_on_empty(self):
364        result = self.vm.qmp('blockdev-open-tray', id=self.device_name)
365        self.assert_qmp(result, 'return', {})
366
367        self.wait_for_open()
368
369        result = self.vm.qmp('blockdev-remove-medium', id=self.device_name)
370        # Should be a no-op
371        self.assert_qmp(result, 'return', {})
372
373# Do this in a function to avoid leaking variables like case into the global
374# name space (otherwise tests would be run for the abstract base classes)
375def create_basic_test_classes():
376    for (media, interface, has_real_tray) in [ ('cdrom', 'ide', True),
377                                               ('cdrom', 'scsi', True),
378                                               ('disk', 'floppy', False) ]:
379
380        for case in [ TestInitiallyFilled, TestInitiallyEmpty ]:
381            for use_drive in [ True, False ]:
382                attr = { 'media': media,
383                         'interface': interface,
384                         'has_real_tray': has_real_tray,
385                         'use_drive': use_drive }
386
387                name = '%s_%s_%s_%s' % (case.__name__, media, interface,
388                                        'drive' if use_drive else 'blockdev')
389                globals()[name] = type(name, (case, ), attr)
390
391create_basic_test_classes()
392
393class TestChangeReadOnly(ChangeBaseClass):
394    device_name = 'qdev0'
395
396    def setUp(self):
397        qemu_img('create', '-f', iotests.imgfmt, old_img, '1440k')
398        qemu_img('create', '-f', iotests.imgfmt, new_img, '1440k')
399        self.vm = iotests.VM()
400
401    def tearDown(self):
402        self.vm.shutdown()
403        os.chmod(old_img, 0o666)
404        os.chmod(new_img, 0o666)
405        os.remove(old_img)
406        os.remove(new_img)
407
408    def test_ro_ro_retain(self):
409        os.chmod(old_img, 0o444)
410        os.chmod(new_img, 0o444)
411        self.vm.add_drive(old_img, 'media=disk,read-only=on', 'none')
412        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
413        self.vm.launch()
414
415        result = self.vm.qmp('query-block')
416        self.assert_qmp(result, 'return[0]/inserted/ro', True)
417        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
418
419        result = self.vm.qmp('blockdev-change-medium', id=self.device_name,
420                                                       filename=new_img,
421                                                       format=iotests.imgfmt,
422                                                       read_only_mode='retain')
423        self.assert_qmp(result, 'return', {})
424
425        result = self.vm.qmp('query-block')
426        self.assert_qmp(result, 'return[0]/inserted/ro', True)
427        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
428
429    def test_ro_rw_retain(self):
430        os.chmod(old_img, 0o444)
431        self.vm.add_drive(old_img, 'media=disk,read-only=on', 'none')
432        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
433        self.vm.launch()
434
435        result = self.vm.qmp('query-block')
436        self.assert_qmp(result, 'return[0]/inserted/ro', True)
437        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
438
439        result = self.vm.qmp('blockdev-change-medium', id=self.device_name,
440                                                       filename=new_img,
441                                                       format=iotests.imgfmt,
442                                                       read_only_mode='retain')
443        self.assert_qmp(result, 'return', {})
444
445        result = self.vm.qmp('query-block')
446        self.assert_qmp(result, 'return[0]/inserted/ro', True)
447        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
448
449    @iotests.skip_if_user_is_root
450    def test_rw_ro_retain(self):
451        os.chmod(new_img, 0o444)
452        self.vm.add_drive(old_img, 'media=disk', 'none')
453        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
454        self.vm.launch()
455
456        result = self.vm.qmp('query-block')
457        self.assert_qmp(result, 'return[0]/inserted/ro', False)
458        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
459
460        result = self.vm.qmp('blockdev-change-medium', id=self.device_name,
461                                                       filename=new_img,
462                                                       format=iotests.imgfmt,
463                                                       read_only_mode='retain')
464        self.assert_qmp(result, 'error/class', 'GenericError')
465
466        self.assertEqual(self.vm.get_qmp_events(wait=False), [])
467
468        result = self.vm.qmp('query-block')
469        self.assert_qmp(result, 'return[0]/inserted/ro', False)
470        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
471
472    def test_ro_rw(self):
473        os.chmod(old_img, 0o444)
474        self.vm.add_drive(old_img, 'media=disk,read-only=on', 'none')
475        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
476        self.vm.launch()
477
478        result = self.vm.qmp('query-block')
479        self.assert_qmp(result, 'return[0]/inserted/ro', True)
480        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
481
482        result = self.vm.qmp('blockdev-change-medium',
483                             id=self.device_name,
484                             filename=new_img,
485                             format=iotests.imgfmt,
486                             read_only_mode='read-write')
487        self.assert_qmp(result, 'return', {})
488
489        result = self.vm.qmp('query-block')
490        self.assert_qmp(result, 'return[0]/inserted/ro', False)
491        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
492
493    def test_rw_ro(self):
494        os.chmod(new_img, 0o444)
495        self.vm.add_drive(old_img, 'media=disk', 'none')
496        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
497        self.vm.launch()
498
499        result = self.vm.qmp('query-block')
500        self.assert_qmp(result, 'return[0]/inserted/ro', False)
501        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
502
503        result = self.vm.qmp('blockdev-change-medium',
504                             id=self.device_name,
505                             filename=new_img,
506                             format=iotests.imgfmt,
507                             read_only_mode='read-only')
508        self.assert_qmp(result, 'return', {})
509
510        result = self.vm.qmp('query-block')
511        self.assert_qmp(result, 'return[0]/inserted/ro', True)
512        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
513
514    def test_make_rw_ro(self):
515        self.vm.add_drive(old_img, 'media=disk', 'none')
516        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
517        self.vm.launch()
518
519        result = self.vm.qmp('query-block')
520        self.assert_qmp(result, 'return[0]/inserted/ro', False)
521        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
522
523        result = self.vm.qmp('blockdev-change-medium',
524                             id=self.device_name,
525                             filename=new_img,
526                             format=iotests.imgfmt,
527                             read_only_mode='read-only')
528        self.assert_qmp(result, 'return', {})
529
530        result = self.vm.qmp('query-block')
531        self.assert_qmp(result, 'return[0]/inserted/ro', True)
532        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
533
534    @iotests.skip_if_user_is_root
535    def test_make_ro_rw(self):
536        os.chmod(new_img, 0o444)
537        self.vm.add_drive(old_img, 'media=disk', 'none')
538        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
539        self.vm.launch()
540
541        result = self.vm.qmp('query-block')
542        self.assert_qmp(result, 'return[0]/inserted/ro', False)
543        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
544
545        result = self.vm.qmp('blockdev-change-medium',
546                             id=self.device_name,
547                             filename=new_img,
548                             format=iotests.imgfmt,
549                             read_only_mode='read-write')
550        self.assert_qmp(result, 'error/class', 'GenericError')
551
552        result = self.vm.qmp('query-block')
553        self.assert_qmp(result, 'return[0]/inserted/ro', False)
554        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
555
556    def test_make_rw_ro_by_retain(self):
557        os.chmod(old_img, 0o444)
558        self.vm.add_drive(old_img, 'media=disk,read-only=on', 'none')
559        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
560        self.vm.launch()
561
562        result = self.vm.qmp('query-block')
563        self.assert_qmp(result, 'return[0]/inserted/ro', True)
564        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
565
566        result = self.vm.qmp('blockdev-change-medium', id=self.device_name,
567                                                       filename=new_img,
568                                                       format=iotests.imgfmt,
569                                                       read_only_mode='retain')
570        self.assert_qmp(result, 'return', {})
571
572        result = self.vm.qmp('query-block')
573        self.assert_qmp(result, 'return[0]/inserted/ro', True)
574        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
575
576    @iotests.skip_if_user_is_root
577    def test_make_ro_rw_by_retain(self):
578        os.chmod(new_img, 0o444)
579        self.vm.add_drive(old_img, 'media=disk', 'none')
580        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
581        self.vm.launch()
582
583        result = self.vm.qmp('query-block')
584        self.assert_qmp(result, 'return[0]/inserted/ro', False)
585        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
586
587        result = self.vm.qmp('blockdev-change-medium', id=self.device_name,
588                                                       filename=new_img,
589                                                       format=iotests.imgfmt,
590                                                       read_only_mode='retain')
591        self.assert_qmp(result, 'error/class', 'GenericError')
592
593        result = self.vm.qmp('query-block')
594        self.assert_qmp(result, 'return[0]/inserted/ro', False)
595        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
596
597    def test_rw_ro_cycle(self):
598        os.chmod(new_img, 0o444)
599        self.vm.add_drive(old_img, 'media=disk', 'none')
600        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
601        self.vm.launch()
602
603        result = self.vm.qmp('query-block')
604        self.assert_qmp(result, 'return[0]/inserted/ro', False)
605        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
606
607        result = self.vm.qmp('blockdev-add',
608                             node_name='new',
609                             driver=iotests.imgfmt,
610                             read_only=True,
611                             file={'filename': new_img,
612                                    'driver': 'file'})
613        self.assert_qmp(result, 'return', {})
614
615        result = self.vm.qmp('query-block')
616        self.assert_qmp(result, 'return[0]/inserted/ro', False)
617        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
618
619        result = self.vm.qmp('blockdev-remove-medium', id=self.device_name)
620        self.assert_qmp(result, 'return', {})
621
622        result = self.vm.qmp('query-block')
623        self.assert_qmp_absent(result, 'return[0]/inserted')
624
625        result = self.vm.qmp('blockdev-insert-medium', id=self.device_name,
626                                                       node_name='new')
627        self.assert_qmp(result, 'return', {})
628
629        result = self.vm.qmp('query-block')
630        self.assert_qmp(result, 'return[0]/inserted/ro', True)
631        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
632
633        result = self.vm.qmp('query-block')
634        self.assert_qmp(result, 'return[0]/inserted/ro', True)
635        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
636
637GeneralChangeTestsBaseClass = None
638TestInitiallyFilled = None
639TestInitiallyEmpty = None
640
641
642class TestBlockJobsAfterCycle(ChangeBaseClass):
643    device_name = 'qdev0'
644
645    def setUp(self):
646        qemu_img('create', '-f', iotests.imgfmt, old_img, '1440K')
647
648        self.vm = iotests.VM()
649        self.vm.add_drive_raw("id=drive0,driver=null-co,if=none")
650        self.vm.add_device('floppy,drive=drive0,id=%s' % self.device_name)
651        self.vm.launch()
652
653        result = self.vm.qmp('query-block')
654        self.assert_qmp(result, 'return[0]/inserted/image/format', 'null-co')
655
656        # For device-less BBs, calling blockdev-open-tray or blockdev-close-tray
657        # is not necessary
658        result = self.vm.qmp('blockdev-remove-medium', id=self.device_name)
659        self.assert_qmp(result, 'return', {})
660
661        result = self.vm.qmp('query-block')
662        self.assert_qmp_absent(result, 'return[0]/inserted')
663
664        result = self.vm.qmp('blockdev-add',
665                             node_name='node0',
666                             driver=iotests.imgfmt,
667                             file={'filename': old_img,
668                                   'driver': 'file'})
669        self.assert_qmp(result, 'return', {})
670
671        result = self.vm.qmp('blockdev-insert-medium', id=self.device_name,
672                                                       node_name='node0')
673        self.assert_qmp(result, 'return', {})
674
675        result = self.vm.qmp('query-block')
676        self.assert_qmp(result, 'return[0]/inserted/image/filename', old_img)
677
678    def tearDown(self):
679        self.vm.shutdown()
680        os.remove(old_img)
681        try:
682            os.remove(new_img)
683        except OSError:
684            pass
685
686    def test_snapshot_and_commit(self):
687        # We need backing file support
688        if iotests.imgfmt != 'qcow2' and iotests.imgfmt != 'qed':
689            return
690
691        result = self.vm.qmp('blockdev-snapshot-sync', device='drive0',
692                                                       snapshot_file=new_img,
693                                                       format=iotests.imgfmt)
694        self.assert_qmp(result, 'return', {})
695
696        result = self.vm.qmp('query-block')
697        self.assert_qmp(result, 'return[0]/inserted/image/filename', new_img)
698        self.assert_qmp(result,
699                        'return[0]/inserted/image/backing-image/filename',
700                        old_img)
701
702        result = self.vm.qmp('block-commit', device='drive0')
703        self.assert_qmp(result, 'return', {})
704
705        self.vm.event_wait(name='BLOCK_JOB_READY')
706
707        result = self.vm.qmp('query-block-jobs')
708        self.assert_qmp(result, 'return[0]/device', 'drive0')
709
710        result = self.vm.qmp('block-job-complete', device='drive0')
711        self.assert_qmp(result, 'return', {})
712
713        self.vm.event_wait(name='BLOCK_JOB_COMPLETED')
714
715
716if __name__ == '__main__':
717    if iotests.qemu_default_machine != 'pc':
718        # We need floppy and IDE CD-ROM
719        iotests.notrun('not suitable for this machine type: %s' %
720                       iotests.qemu_default_machine)
721    # Need to support image creation
722    iotests.main(supported_fmts=['vpc', 'parallels', 'qcow', 'vdi', 'qcow2',
723                                 'vmdk', 'raw', 'vhdx', 'qed'],
724                 supported_protocols=['file'])
725