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