1#!/usr/bin/env python3 2# group: rw 3# 4# Tests for drive-backup and blockdev-backup 5# 6# Copyright (C) 2013, 2014 Red Hat, Inc. 7# 8# Based on 041. 9# 10# This program is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program. If not, see <http://www.gnu.org/licenses/>. 22# 23 24import time 25import os 26import iotests 27from iotests import qemu_img, qemu_io 28 29test_img = os.path.join(iotests.test_dir, 'test.img') 30target_img = os.path.join(iotests.test_dir, 'target.img') 31blockdev_target_img = os.path.join(iotests.test_dir, 'blockdev-target.img') 32 33image_len = 64 * 1024 * 1024 # MB 34 35def setUpModule(): 36 qemu_img('create', '-f', iotests.imgfmt, test_img, str(image_len)) 37 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x11 0 64k', test_img) 38 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x00 64k 128k', test_img) 39 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x22 162k 32k', test_img) 40 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xd5 1M 32k', test_img) 41 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 32M 124k', test_img) 42 qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x33 67043328 64k', test_img) 43 44def tearDownModule(): 45 os.remove(test_img) 46 47 48class TestSingleDrive(iotests.QMPTestCase): 49 def setUp(self): 50 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len)) 51 52 self.vm = iotests.VM() 53 self.vm.add_drive('blkdebug::' + test_img, 'node-name=source') 54 self.vm.add_drive(blockdev_target_img, 'node-name=target', 55 interface="none") 56 if iotests.qemu_default_machine == 'pc': 57 self.vm.add_drive(None, 'media=cdrom', 'ide') 58 self.vm.launch() 59 60 def tearDown(self): 61 self.vm.shutdown() 62 os.remove(blockdev_target_img) 63 try: 64 os.remove(target_img) 65 except OSError: 66 pass 67 68 def do_test_cancel(self, cmd, target): 69 self.assert_no_active_block_jobs() 70 71 self.vm.pause_drive('drive0') 72 result = self.vm.qmp(cmd, device='drive0', target=target, sync='full') 73 self.assert_qmp(result, 'return', {}) 74 75 event = self.cancel_and_wait(resume=True) 76 self.assert_qmp(event, 'data/type', 'backup') 77 78 def test_cancel_drive_backup(self): 79 self.do_test_cancel('drive-backup', target_img) 80 81 def test_cancel_blockdev_backup(self): 82 self.do_test_cancel('blockdev-backup', 'drive1') 83 84 def do_test_pause(self, cmd, target, image): 85 self.assert_no_active_block_jobs() 86 87 self.vm.pause_drive('drive0') 88 result = self.vm.qmp(cmd, device='drive0', 89 target=target, sync='full') 90 self.assert_qmp(result, 'return', {}) 91 92 self.pause_job('drive0', wait=False) 93 self.vm.resume_drive('drive0') 94 self.pause_wait('drive0') 95 96 result = self.vm.qmp('query-block-jobs') 97 offset = self.dictpath(result, 'return[0]/offset') 98 99 time.sleep(0.5) 100 result = self.vm.qmp('query-block-jobs') 101 self.assert_qmp(result, 'return[0]/offset', offset) 102 103 result = self.vm.qmp('block-job-resume', device='drive0') 104 self.assert_qmp(result, 'return', {}) 105 106 self.wait_until_completed() 107 108 self.vm.shutdown() 109 self.assertTrue(iotests.compare_images(test_img, image), 110 'target image does not match source after backup') 111 112 def test_pause_drive_backup(self): 113 self.do_test_pause('drive-backup', target_img, target_img) 114 115 def test_pause_blockdev_backup(self): 116 self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img) 117 118 def do_test_resize_blockdev_backup(self, device, node): 119 def pre_finalize(): 120 result = self.vm.qmp('block_resize', device=device, size=65536) 121 self.assert_qmp(result, 'error/class', 'GenericError') 122 123 result = self.vm.qmp('block_resize', node_name=node, size=65536) 124 self.assert_qmp(result, 'error/class', 'GenericError') 125 126 result = self.vm.qmp('blockdev-backup', job_id='job0', device='drive0', 127 target='drive1', sync='full', auto_finalize=False, 128 auto_dismiss=False) 129 self.assert_qmp(result, 'return', {}) 130 131 self.vm.run_job('job0', auto_finalize=False, pre_finalize=pre_finalize) 132 133 def test_source_resize_blockdev_backup(self): 134 self.do_test_resize_blockdev_backup('drive0', 'source') 135 136 def test_target_resize_blockdev_backup(self): 137 self.do_test_resize_blockdev_backup('drive1', 'target') 138 139 def do_test_target_size(self, size): 140 result = self.vm.qmp('block_resize', device='drive1', size=size) 141 self.assert_qmp(result, 'return', {}) 142 143 result = self.vm.qmp('blockdev-backup', job_id='job0', device='drive0', 144 target='drive1', sync='full') 145 self.assert_qmp(result, 'error/class', 'GenericError') 146 147 def test_small_target(self): 148 self.do_test_target_size(image_len // 2) 149 150 def test_large_target(self): 151 self.do_test_target_size(image_len * 2) 152 153 def test_medium_not_found(self): 154 if iotests.qemu_default_machine != 'pc': 155 return 156 157 result = self.vm.qmp('drive-backup', device='drive2', # CD-ROM 158 target=target_img, sync='full') 159 self.assert_qmp(result, 'error/class', 'GenericError') 160 161 def test_medium_not_found_blockdev_backup(self): 162 if iotests.qemu_default_machine != 'pc': 163 return 164 165 result = self.vm.qmp('blockdev-backup', device='drive2', # CD-ROM 166 target='drive1', sync='full') 167 self.assert_qmp(result, 'error/class', 'GenericError') 168 169 def test_image_not_found(self): 170 result = self.vm.qmp('drive-backup', device='drive0', 171 target=target_img, sync='full', mode='existing') 172 self.assert_qmp(result, 'error/class', 'GenericError') 173 174 def test_invalid_format(self): 175 result = self.vm.qmp('drive-backup', device='drive0', 176 target=target_img, sync='full', 177 format='spaghetti-noodles') 178 self.assert_qmp(result, 'error/class', 'GenericError') 179 180 def do_test_device_not_found(self, cmd, **args): 181 result = self.vm.qmp(cmd, **args) 182 self.assert_qmp(result, 'error/class', 'GenericError') 183 184 def test_device_not_found(self): 185 self.do_test_device_not_found('drive-backup', device='nonexistent', 186 target=target_img, sync='full') 187 188 self.do_test_device_not_found('blockdev-backup', device='nonexistent', 189 target='drive0', sync='full') 190 191 self.do_test_device_not_found('blockdev-backup', device='drive0', 192 target='nonexistent', sync='full') 193 194 self.do_test_device_not_found('blockdev-backup', device='nonexistent', 195 target='nonexistent', sync='full') 196 197 def test_target_is_source(self): 198 result = self.vm.qmp('blockdev-backup', device='drive0', 199 target='drive0', sync='full') 200 self.assert_qmp(result, 'error/class', 'GenericError') 201 202class TestSetSpeed(iotests.QMPTestCase): 203 def setUp(self): 204 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len)) 205 206 self.vm = iotests.VM().add_drive('blkdebug::' + test_img) 207 self.vm.add_drive(blockdev_target_img, interface="none") 208 self.vm.launch() 209 210 def tearDown(self): 211 self.vm.shutdown() 212 os.remove(blockdev_target_img) 213 try: 214 os.remove(target_img) 215 except OSError: 216 pass 217 218 def do_test_set_speed(self, cmd, target): 219 self.assert_no_active_block_jobs() 220 221 self.vm.pause_drive('drive0') 222 result = self.vm.qmp(cmd, device='drive0', target=target, sync='full') 223 self.assert_qmp(result, 'return', {}) 224 225 # Default speed is 0 226 result = self.vm.qmp('query-block-jobs') 227 self.assert_qmp(result, 'return[0]/device', 'drive0') 228 self.assert_qmp(result, 'return[0]/speed', 0) 229 230 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 231 self.assert_qmp(result, 'return', {}) 232 233 # Ensure the speed we set was accepted 234 result = self.vm.qmp('query-block-jobs') 235 self.assert_qmp(result, 'return[0]/device', 'drive0') 236 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 237 238 event = self.cancel_and_wait(resume=True) 239 self.assert_qmp(event, 'data/type', 'backup') 240 241 # Check setting speed option works 242 self.vm.pause_drive('drive0') 243 result = self.vm.qmp(cmd, device='drive0', 244 target=target, sync='full', speed=4*1024*1024) 245 self.assert_qmp(result, 'return', {}) 246 247 result = self.vm.qmp('query-block-jobs') 248 self.assert_qmp(result, 'return[0]/device', 'drive0') 249 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 250 251 event = self.cancel_and_wait(resume=True) 252 self.assert_qmp(event, 'data/type', 'backup') 253 254 def test_set_speed_drive_backup(self): 255 self.do_test_set_speed('drive-backup', target_img) 256 257 def test_set_speed_blockdev_backup(self): 258 self.do_test_set_speed('blockdev-backup', 'drive1') 259 260 def do_test_set_speed_invalid(self, cmd, target): 261 self.assert_no_active_block_jobs() 262 263 result = self.vm.qmp(cmd, device='drive0', 264 target=target, sync='full', speed=-1) 265 self.assert_qmp(result, 'error/class', 'GenericError') 266 267 self.assert_no_active_block_jobs() 268 269 self.vm.pause_drive('drive0') 270 result = self.vm.qmp(cmd, device='drive0', 271 target=target, sync='full') 272 self.assert_qmp(result, 'return', {}) 273 274 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 275 self.assert_qmp(result, 'error/class', 'GenericError') 276 277 event = self.cancel_and_wait(resume=True) 278 self.assert_qmp(event, 'data/type', 'backup') 279 280 def test_set_speed_invalid_drive_backup(self): 281 self.do_test_set_speed_invalid('drive-backup', target_img) 282 283 def test_set_speed_invalid_blockdev_backup(self): 284 self.do_test_set_speed_invalid('blockdev-backup', 'drive1') 285 286# Note: We cannot use pause_drive() here, or the transaction command 287# would stall. Instead, we limit the block job speed here. 288class TestSingleTransaction(iotests.QMPTestCase): 289 def setUp(self): 290 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len)) 291 292 self.vm = iotests.VM().add_drive(test_img) 293 self.vm.add_drive(blockdev_target_img, interface="none") 294 if iotests.qemu_default_machine == 'pc': 295 self.vm.add_drive(None, 'media=cdrom', 'ide') 296 self.vm.launch() 297 298 def tearDown(self): 299 self.vm.shutdown() 300 os.remove(blockdev_target_img) 301 try: 302 os.remove(target_img) 303 except OSError: 304 pass 305 306 def do_test_cancel(self, cmd, target): 307 self.assert_no_active_block_jobs() 308 309 result = self.vm.qmp('transaction', actions=[{ 310 'type': cmd, 311 'data': { 'device': 'drive0', 312 'target': target, 313 'sync': 'full', 314 'speed': 64 * 1024 }, 315 } 316 ]) 317 318 self.assert_qmp(result, 'return', {}) 319 320 event = self.cancel_and_wait() 321 self.assert_qmp(event, 'data/type', 'backup') 322 323 def test_cancel_drive_backup(self): 324 self.do_test_cancel('drive-backup', target_img) 325 326 def test_cancel_blockdev_backup(self): 327 self.do_test_cancel('blockdev-backup', 'drive1') 328 329 def do_test_pause(self, cmd, target, image): 330 self.assert_no_active_block_jobs() 331 332 result = self.vm.qmp('transaction', actions=[{ 333 'type': cmd, 334 'data': { 'device': 'drive0', 335 'target': target, 336 'sync': 'full', 337 'speed': 64 * 1024 }, 338 } 339 ]) 340 self.assert_qmp(result, 'return', {}) 341 342 self.pause_job('drive0', wait=False) 343 344 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0) 345 self.assert_qmp(result, 'return', {}) 346 347 self.pause_wait('drive0') 348 349 result = self.vm.qmp('query-block-jobs') 350 offset = self.dictpath(result, 'return[0]/offset') 351 352 time.sleep(0.5) 353 result = self.vm.qmp('query-block-jobs') 354 self.assert_qmp(result, 'return[0]/offset', offset) 355 356 result = self.vm.qmp('block-job-resume', device='drive0') 357 self.assert_qmp(result, 'return', {}) 358 359 self.wait_until_completed() 360 361 self.vm.shutdown() 362 self.assertTrue(iotests.compare_images(test_img, image), 363 'target image does not match source after backup') 364 365 def test_pause_drive_backup(self): 366 self.do_test_pause('drive-backup', target_img, target_img) 367 368 def test_pause_blockdev_backup(self): 369 self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img) 370 371 def do_test_medium_not_found(self, cmd, target): 372 if iotests.qemu_default_machine != 'pc': 373 return 374 375 result = self.vm.qmp('transaction', actions=[{ 376 'type': cmd, 377 'data': { 'device': 'drive2', # CD-ROM 378 'target': target, 379 'sync': 'full' }, 380 } 381 ]) 382 self.assert_qmp(result, 'error/class', 'GenericError') 383 384 def test_medium_not_found_drive_backup(self): 385 self.do_test_medium_not_found('drive-backup', target_img) 386 387 def test_medium_not_found_blockdev_backup(self): 388 self.do_test_medium_not_found('blockdev-backup', 'drive1') 389 390 def test_image_not_found(self): 391 result = self.vm.qmp('transaction', actions=[{ 392 'type': 'drive-backup', 393 'data': { 'device': 'drive0', 394 'mode': 'existing', 395 'target': target_img, 396 'sync': 'full' }, 397 } 398 ]) 399 self.assert_qmp(result, 'error/class', 'GenericError') 400 401 def test_device_not_found(self): 402 result = self.vm.qmp('transaction', actions=[{ 403 'type': 'drive-backup', 404 'data': { 'device': 'nonexistent', 405 'mode': 'existing', 406 'target': target_img, 407 'sync': 'full' }, 408 } 409 ]) 410 self.assert_qmp(result, 'error/class', 'GenericError') 411 412 result = self.vm.qmp('transaction', actions=[{ 413 'type': 'blockdev-backup', 414 'data': { 'device': 'nonexistent', 415 'target': 'drive1', 416 'sync': 'full' }, 417 } 418 ]) 419 self.assert_qmp(result, 'error/class', 'GenericError') 420 421 result = self.vm.qmp('transaction', actions=[{ 422 'type': 'blockdev-backup', 423 'data': { 'device': 'drive0', 424 'target': 'nonexistent', 425 'sync': 'full' }, 426 } 427 ]) 428 self.assert_qmp(result, 'error/class', 'GenericError') 429 430 result = self.vm.qmp('transaction', actions=[{ 431 'type': 'blockdev-backup', 432 'data': { 'device': 'nonexistent', 433 'target': 'nonexistent', 434 'sync': 'full' }, 435 } 436 ]) 437 self.assert_qmp(result, 'error/class', 'GenericError') 438 439 def test_target_is_source(self): 440 result = self.vm.qmp('transaction', actions=[{ 441 'type': 'blockdev-backup', 442 'data': { 'device': 'drive0', 443 'target': 'drive0', 444 'sync': 'full' }, 445 } 446 ]) 447 self.assert_qmp(result, 'error/class', 'GenericError') 448 449 def test_abort(self): 450 result = self.vm.qmp('transaction', actions=[{ 451 'type': 'drive-backup', 452 'data': { 'device': 'nonexistent', 453 'mode': 'existing', 454 'target': target_img, 455 'sync': 'full' }, 456 }, { 457 'type': 'Abort', 458 'data': {}, 459 } 460 ]) 461 self.assert_qmp(result, 'error/class', 'GenericError') 462 self.assert_no_active_block_jobs() 463 464 result = self.vm.qmp('transaction', actions=[{ 465 'type': 'blockdev-backup', 466 'data': { 'device': 'nonexistent', 467 'target': 'drive1', 468 'sync': 'full' }, 469 }, { 470 'type': 'Abort', 471 'data': {}, 472 } 473 ]) 474 self.assert_qmp(result, 'error/class', 'GenericError') 475 self.assert_no_active_block_jobs() 476 477 result = self.vm.qmp('transaction', actions=[{ 478 'type': 'blockdev-backup', 479 'data': { 'device': 'drive0', 480 'target': 'nonexistent', 481 'sync': 'full' }, 482 }, { 483 'type': 'Abort', 484 'data': {}, 485 } 486 ]) 487 self.assert_qmp(result, 'error/class', 'GenericError') 488 self.assert_no_active_block_jobs() 489 490 491class TestCompressedToQcow2(iotests.QMPTestCase): 492 image_len = 64 * 1024 * 1024 # MB 493 target_fmt = {'type': 'qcow2', 'args': (), 'drive-opts': ''} 494 495 def tearDown(self): 496 self.vm.shutdown() 497 os.remove(blockdev_target_img) 498 try: 499 os.remove(target_img) 500 except OSError: 501 pass 502 503 def do_prepare_drives(self, attach_target): 504 self.vm = iotests.VM().add_drive('blkdebug::' + test_img, 505 opts=self.target_fmt['drive-opts']) 506 507 qemu_img('create', '-f', self.target_fmt['type'], blockdev_target_img, 508 str(self.image_len), *self.target_fmt['args']) 509 if attach_target: 510 self.vm.add_drive(blockdev_target_img, 511 img_format=self.target_fmt['type'], 512 interface="none", 513 opts=self.target_fmt['drive-opts']) 514 515 self.vm.launch() 516 517 def do_test_compress_complete(self, cmd, attach_target, **args): 518 self.do_prepare_drives(attach_target) 519 520 self.assert_no_active_block_jobs() 521 522 result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) 523 self.assert_qmp(result, 'return', {}) 524 525 self.wait_until_completed() 526 527 self.vm.shutdown() 528 self.assertTrue(iotests.compare_images(test_img, blockdev_target_img, 529 iotests.imgfmt, 530 self.target_fmt['type']), 531 'target image does not match source after backup') 532 533 def test_complete_compress_drive_backup(self): 534 self.do_test_compress_complete('drive-backup', False, 535 target=blockdev_target_img, 536 mode='existing') 537 538 def test_complete_compress_blockdev_backup(self): 539 self.do_test_compress_complete('blockdev-backup', 540 True, target='drive1') 541 542 def do_test_compress_cancel(self, cmd, attach_target, **args): 543 self.do_prepare_drives(attach_target) 544 545 self.assert_no_active_block_jobs() 546 547 self.vm.pause_drive('drive0') 548 result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) 549 self.assert_qmp(result, 'return', {}) 550 551 event = self.cancel_and_wait(resume=True) 552 self.assert_qmp(event, 'data/type', 'backup') 553 554 self.vm.shutdown() 555 556 def test_compress_cancel_drive_backup(self): 557 self.do_test_compress_cancel('drive-backup', False, 558 target=blockdev_target_img, 559 mode='existing') 560 561 def test_compress_cancel_blockdev_backup(self): 562 self.do_test_compress_cancel('blockdev-backup', True, 563 target='drive1') 564 565 def do_test_compress_pause(self, cmd, attach_target, **args): 566 self.do_prepare_drives(attach_target) 567 568 self.assert_no_active_block_jobs() 569 570 self.vm.pause_drive('drive0') 571 result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) 572 self.assert_qmp(result, 'return', {}) 573 574 self.pause_job('drive0', wait=False) 575 self.vm.resume_drive('drive0') 576 self.pause_wait('drive0') 577 578 result = self.vm.qmp('query-block-jobs') 579 offset = self.dictpath(result, 'return[0]/offset') 580 581 time.sleep(0.5) 582 result = self.vm.qmp('query-block-jobs') 583 self.assert_qmp(result, 'return[0]/offset', offset) 584 585 result = self.vm.qmp('block-job-resume', device='drive0') 586 self.assert_qmp(result, 'return', {}) 587 588 self.wait_until_completed() 589 590 self.vm.shutdown() 591 self.assertTrue(iotests.compare_images(test_img, blockdev_target_img, 592 iotests.imgfmt, 593 self.target_fmt['type']), 594 'target image does not match source after backup') 595 596 def test_compress_pause_drive_backup(self): 597 self.do_test_compress_pause('drive-backup', False, 598 target=blockdev_target_img, 599 mode='existing') 600 601 def test_compress_pause_blockdev_backup(self): 602 self.do_test_compress_pause('blockdev-backup', True, 603 target='drive1') 604 605 606class TestCompressedToVmdk(TestCompressedToQcow2): 607 target_fmt = {'type': 'vmdk', 'args': ('-o', 'subformat=streamOptimized'), 608 'drive-opts': 'cache.no-flush=on'} 609 610 @iotests.skip_if_unsupported(['vmdk']) 611 def setUp(self): 612 pass 613 614 615if __name__ == '__main__': 616 iotests.main(supported_fmts=['raw', 'qcow2'], 617 supported_protocols=['file']) 618