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 self.vm.cmd(cmd, device='drive0', target=target, sync='full') 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 self.vm.cmd(cmd, device='drive0', 88 target=target, sync='full') 89 90 self.pause_job('drive0', wait=False) 91 self.vm.resume_drive('drive0') 92 self.pause_wait('drive0') 93 94 result = self.vm.qmp('query-block-jobs') 95 offset = self.dictpath(result, 'return[0]/offset') 96 97 time.sleep(0.5) 98 result = self.vm.qmp('query-block-jobs') 99 self.assert_qmp(result, 'return[0]/offset', offset) 100 101 self.vm.cmd('block-job-resume', device='drive0') 102 103 self.wait_until_completed() 104 105 self.vm.shutdown() 106 self.assertTrue(iotests.compare_images(test_img, image), 107 'target image does not match source after backup') 108 109 def test_pause_drive_backup(self): 110 self.do_test_pause('drive-backup', target_img, target_img) 111 112 def test_pause_blockdev_backup(self): 113 self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img) 114 115 def do_test_resize_blockdev_backup(self, device, node): 116 def pre_finalize(): 117 result = self.vm.qmp('block_resize', device=device, size=65536) 118 self.assert_qmp(result, 'error/class', 'GenericError') 119 120 result = self.vm.qmp('block_resize', node_name=node, size=65536) 121 self.assert_qmp(result, 'error/class', 'GenericError') 122 123 self.vm.cmd('blockdev-backup', job_id='job0', device='drive0', 124 target='drive1', sync='full', auto_finalize=False, 125 auto_dismiss=False) 126 127 self.vm.run_job('job0', auto_finalize=False, pre_finalize=pre_finalize) 128 129 def test_source_resize_blockdev_backup(self): 130 self.do_test_resize_blockdev_backup('drive0', 'source') 131 132 def test_target_resize_blockdev_backup(self): 133 self.do_test_resize_blockdev_backup('drive1', 'target') 134 135 def do_test_target_size(self, size): 136 self.vm.cmd('block_resize', device='drive1', size=size) 137 138 result = self.vm.qmp('blockdev-backup', job_id='job0', device='drive0', 139 target='drive1', sync='full') 140 self.assert_qmp(result, 'error/class', 'GenericError') 141 142 def test_small_target(self): 143 self.do_test_target_size(image_len // 2) 144 145 def test_large_target(self): 146 self.do_test_target_size(image_len * 2) 147 148 def test_medium_not_found(self): 149 if iotests.qemu_default_machine != 'pc': 150 return 151 152 result = self.vm.qmp('drive-backup', device='drive2', # CD-ROM 153 target=target_img, sync='full') 154 self.assert_qmp(result, 'error/class', 'GenericError') 155 156 def test_medium_not_found_blockdev_backup(self): 157 if iotests.qemu_default_machine != 'pc': 158 return 159 160 result = self.vm.qmp('blockdev-backup', device='drive2', # CD-ROM 161 target='drive1', sync='full') 162 self.assert_qmp(result, 'error/class', 'GenericError') 163 164 def test_image_not_found(self): 165 result = self.vm.qmp('drive-backup', device='drive0', 166 target=target_img, sync='full', mode='existing') 167 self.assert_qmp(result, 'error/class', 'GenericError') 168 169 def test_invalid_format(self): 170 result = self.vm.qmp('drive-backup', device='drive0', 171 target=target_img, sync='full', 172 format='spaghetti-noodles') 173 self.assert_qmp(result, 'error/class', 'GenericError') 174 175 def do_test_device_not_found(self, cmd, **args): 176 result = self.vm.qmp(cmd, **args) 177 self.assert_qmp(result, 'error/class', 'GenericError') 178 179 def test_device_not_found(self): 180 self.do_test_device_not_found('drive-backup', device='nonexistent', 181 target=target_img, sync='full') 182 183 self.do_test_device_not_found('blockdev-backup', device='nonexistent', 184 target='drive0', sync='full') 185 186 self.do_test_device_not_found('blockdev-backup', device='drive0', 187 target='nonexistent', sync='full') 188 189 self.do_test_device_not_found('blockdev-backup', device='nonexistent', 190 target='nonexistent', sync='full') 191 192 def test_target_is_source(self): 193 result = self.vm.qmp('blockdev-backup', device='drive0', 194 target='drive0', sync='full') 195 self.assert_qmp(result, 'error/class', 'GenericError') 196 197class TestSetSpeed(iotests.QMPTestCase): 198 def setUp(self): 199 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len)) 200 201 self.vm = iotests.VM().add_drive('blkdebug::' + test_img) 202 self.vm.add_drive(blockdev_target_img, interface="none") 203 self.vm.launch() 204 205 def tearDown(self): 206 self.vm.shutdown() 207 os.remove(blockdev_target_img) 208 try: 209 os.remove(target_img) 210 except OSError: 211 pass 212 213 def do_test_set_speed(self, cmd, target): 214 self.assert_no_active_block_jobs() 215 216 self.vm.pause_drive('drive0') 217 self.vm.cmd(cmd, device='drive0', target=target, sync='full') 218 219 # Default speed is 0 220 result = self.vm.qmp('query-block-jobs') 221 self.assert_qmp(result, 'return[0]/device', 'drive0') 222 self.assert_qmp(result, 'return[0]/speed', 0) 223 224 self.vm.cmd('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 225 226 # Ensure the speed we set was accepted 227 result = self.vm.qmp('query-block-jobs') 228 self.assert_qmp(result, 'return[0]/device', 'drive0') 229 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 230 231 event = self.cancel_and_wait(resume=True) 232 self.assert_qmp(event, 'data/type', 'backup') 233 234 # Check setting speed option works 235 self.vm.pause_drive('drive0') 236 self.vm.cmd(cmd, device='drive0', 237 target=target, sync='full', speed=4*1024*1024) 238 239 result = self.vm.qmp('query-block-jobs') 240 self.assert_qmp(result, 'return[0]/device', 'drive0') 241 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 242 243 event = self.cancel_and_wait(resume=True) 244 self.assert_qmp(event, 'data/type', 'backup') 245 246 def test_set_speed_drive_backup(self): 247 self.do_test_set_speed('drive-backup', target_img) 248 249 def test_set_speed_blockdev_backup(self): 250 self.do_test_set_speed('blockdev-backup', 'drive1') 251 252 def do_test_set_speed_invalid(self, cmd, target): 253 self.assert_no_active_block_jobs() 254 255 result = self.vm.qmp(cmd, device='drive0', 256 target=target, sync='full', speed=-1) 257 self.assert_qmp(result, 'error/class', 'GenericError') 258 259 self.assert_no_active_block_jobs() 260 261 self.vm.pause_drive('drive0') 262 self.vm.cmd(cmd, device='drive0', 263 target=target, sync='full') 264 265 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 266 self.assert_qmp(result, 'error/class', 'GenericError') 267 268 event = self.cancel_and_wait(resume=True) 269 self.assert_qmp(event, 'data/type', 'backup') 270 271 def test_set_speed_invalid_drive_backup(self): 272 self.do_test_set_speed_invalid('drive-backup', target_img) 273 274 def test_set_speed_invalid_blockdev_backup(self): 275 self.do_test_set_speed_invalid('blockdev-backup', 'drive1') 276 277# Note: We cannot use pause_drive() here, or the transaction command 278# would stall. Instead, we limit the block job speed here. 279class TestSingleTransaction(iotests.QMPTestCase): 280 def setUp(self): 281 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len)) 282 283 self.vm = iotests.VM().add_drive(test_img) 284 self.vm.add_drive(blockdev_target_img, interface="none") 285 if iotests.qemu_default_machine == 'pc': 286 self.vm.add_drive(None, 'media=cdrom', 'ide') 287 self.vm.launch() 288 289 def tearDown(self): 290 self.vm.shutdown() 291 os.remove(blockdev_target_img) 292 try: 293 os.remove(target_img) 294 except OSError: 295 pass 296 297 def do_test_cancel(self, cmd, target): 298 self.assert_no_active_block_jobs() 299 300 self.vm.cmd('transaction', actions=[{ 301 'type': cmd, 302 'data': { 'device': 'drive0', 303 'target': target, 304 'sync': 'full', 305 'speed': 64 * 1024 }, 306 } 307 ]) 308 309 event = self.cancel_and_wait() 310 self.assert_qmp(event, 'data/type', 'backup') 311 312 def test_cancel_drive_backup(self): 313 self.do_test_cancel('drive-backup', target_img) 314 315 def test_cancel_blockdev_backup(self): 316 self.do_test_cancel('blockdev-backup', 'drive1') 317 318 def do_test_pause(self, cmd, target, image): 319 self.assert_no_active_block_jobs() 320 321 self.vm.cmd('transaction', actions=[{ 322 'type': cmd, 323 'data': { 'device': 'drive0', 324 'target': target, 325 'sync': 'full', 326 'speed': 64 * 1024 }, 327 } 328 ]) 329 330 self.pause_job('drive0', wait=False) 331 332 self.vm.cmd('block-job-set-speed', device='drive0', speed=0) 333 334 self.pause_wait('drive0') 335 336 result = self.vm.qmp('query-block-jobs') 337 offset = self.dictpath(result, 'return[0]/offset') 338 339 time.sleep(0.5) 340 result = self.vm.qmp('query-block-jobs') 341 self.assert_qmp(result, 'return[0]/offset', offset) 342 343 self.vm.cmd('block-job-resume', device='drive0') 344 345 self.wait_until_completed() 346 347 self.vm.shutdown() 348 self.assertTrue(iotests.compare_images(test_img, image), 349 'target image does not match source after backup') 350 351 def test_pause_drive_backup(self): 352 self.do_test_pause('drive-backup', target_img, target_img) 353 354 def test_pause_blockdev_backup(self): 355 self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img) 356 357 def do_test_medium_not_found(self, cmd, target): 358 if iotests.qemu_default_machine != 'pc': 359 return 360 361 result = self.vm.qmp('transaction', actions=[{ 362 'type': cmd, 363 'data': { 'device': 'drive2', # CD-ROM 364 'target': target, 365 'sync': 'full' }, 366 } 367 ]) 368 self.assert_qmp(result, 'error/class', 'GenericError') 369 370 def test_medium_not_found_drive_backup(self): 371 self.do_test_medium_not_found('drive-backup', target_img) 372 373 def test_medium_not_found_blockdev_backup(self): 374 self.do_test_medium_not_found('blockdev-backup', 'drive1') 375 376 def test_image_not_found(self): 377 result = self.vm.qmp('transaction', actions=[{ 378 'type': 'drive-backup', 379 'data': { 'device': 'drive0', 380 'mode': 'existing', 381 'target': target_img, 382 'sync': 'full' }, 383 } 384 ]) 385 self.assert_qmp(result, 'error/class', 'GenericError') 386 387 def test_device_not_found(self): 388 result = self.vm.qmp('transaction', actions=[{ 389 'type': 'drive-backup', 390 'data': { 'device': 'nonexistent', 391 'mode': 'existing', 392 'target': target_img, 393 'sync': 'full' }, 394 } 395 ]) 396 self.assert_qmp(result, 'error/class', 'GenericError') 397 398 result = self.vm.qmp('transaction', actions=[{ 399 'type': 'blockdev-backup', 400 'data': { 'device': 'nonexistent', 401 'target': 'drive1', 402 'sync': 'full' }, 403 } 404 ]) 405 self.assert_qmp(result, 'error/class', 'GenericError') 406 407 result = self.vm.qmp('transaction', actions=[{ 408 'type': 'blockdev-backup', 409 'data': { 'device': 'drive0', 410 'target': 'nonexistent', 411 'sync': 'full' }, 412 } 413 ]) 414 self.assert_qmp(result, 'error/class', 'GenericError') 415 416 result = self.vm.qmp('transaction', actions=[{ 417 'type': 'blockdev-backup', 418 'data': { 'device': 'nonexistent', 419 'target': 'nonexistent', 420 'sync': 'full' }, 421 } 422 ]) 423 self.assert_qmp(result, 'error/class', 'GenericError') 424 425 def test_target_is_source(self): 426 result = self.vm.qmp('transaction', actions=[{ 427 'type': 'blockdev-backup', 428 'data': { 'device': 'drive0', 429 'target': 'drive0', 430 'sync': 'full' }, 431 } 432 ]) 433 self.assert_qmp(result, 'error/class', 'GenericError') 434 435 def test_abort(self): 436 result = self.vm.qmp('transaction', actions=[{ 437 'type': 'drive-backup', 438 'data': { 'device': 'nonexistent', 439 'mode': 'existing', 440 'target': target_img, 441 'sync': 'full' }, 442 }, { 443 'type': 'Abort', 444 'data': {}, 445 } 446 ]) 447 self.assert_qmp(result, 'error/class', 'GenericError') 448 self.assert_no_active_block_jobs() 449 450 result = self.vm.qmp('transaction', actions=[{ 451 'type': 'blockdev-backup', 452 'data': { 'device': 'nonexistent', 453 'target': 'drive1', 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': 'drive0', 466 'target': 'nonexistent', 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 477class TestCompressedToQcow2(iotests.QMPTestCase): 478 image_len = 64 * 1024 * 1024 # MB 479 target_fmt = {'type': 'qcow2', 'args': (), 'drive-opts': ''} 480 481 def tearDown(self): 482 self.vm.shutdown() 483 os.remove(blockdev_target_img) 484 try: 485 os.remove(target_img) 486 except OSError: 487 pass 488 489 def do_prepare_drives(self, attach_target): 490 self.vm = iotests.VM().add_drive('blkdebug::' + test_img, 491 opts=self.target_fmt['drive-opts']) 492 493 qemu_img('create', '-f', self.target_fmt['type'], blockdev_target_img, 494 str(self.image_len), *self.target_fmt['args']) 495 if attach_target: 496 self.vm.add_drive(blockdev_target_img, 497 img_format=self.target_fmt['type'], 498 interface="none", 499 opts=self.target_fmt['drive-opts']) 500 501 self.vm.launch() 502 503 def do_test_compress_complete(self, cmd, attach_target, **args): 504 self.do_prepare_drives(attach_target) 505 506 self.assert_no_active_block_jobs() 507 508 self.vm.cmd(cmd, device='drive0', sync='full', compress=True, **args) 509 510 self.wait_until_completed() 511 512 self.vm.shutdown() 513 self.assertTrue(iotests.compare_images(test_img, blockdev_target_img, 514 iotests.imgfmt, 515 self.target_fmt['type']), 516 'target image does not match source after backup') 517 518 def test_complete_compress_drive_backup(self): 519 self.do_test_compress_complete('drive-backup', False, 520 target=blockdev_target_img, 521 mode='existing') 522 523 def test_complete_compress_blockdev_backup(self): 524 self.do_test_compress_complete('blockdev-backup', 525 True, target='drive1') 526 527 def do_test_compress_cancel(self, cmd, attach_target, **args): 528 self.do_prepare_drives(attach_target) 529 530 self.assert_no_active_block_jobs() 531 532 self.vm.pause_drive('drive0') 533 self.vm.cmd(cmd, device='drive0', sync='full', compress=True, **args) 534 535 event = self.cancel_and_wait(resume=True) 536 self.assert_qmp(event, 'data/type', 'backup') 537 538 self.vm.shutdown() 539 540 def test_compress_cancel_drive_backup(self): 541 self.do_test_compress_cancel('drive-backup', False, 542 target=blockdev_target_img, 543 mode='existing') 544 545 def test_compress_cancel_blockdev_backup(self): 546 self.do_test_compress_cancel('blockdev-backup', True, 547 target='drive1') 548 549 def do_test_compress_pause(self, cmd, attach_target, **args): 550 self.do_prepare_drives(attach_target) 551 552 self.assert_no_active_block_jobs() 553 554 self.vm.pause_drive('drive0') 555 self.vm.cmd(cmd, device='drive0', sync='full', compress=True, **args) 556 557 self.pause_job('drive0', wait=False) 558 self.vm.resume_drive('drive0') 559 self.pause_wait('drive0') 560 561 result = self.vm.qmp('query-block-jobs') 562 offset = self.dictpath(result, 'return[0]/offset') 563 564 time.sleep(0.5) 565 result = self.vm.qmp('query-block-jobs') 566 self.assert_qmp(result, 'return[0]/offset', offset) 567 568 self.vm.cmd('block-job-resume', device='drive0') 569 570 self.wait_until_completed() 571 572 self.vm.shutdown() 573 self.assertTrue(iotests.compare_images(test_img, blockdev_target_img, 574 iotests.imgfmt, 575 self.target_fmt['type']), 576 'target image does not match source after backup') 577 578 def test_compress_pause_drive_backup(self): 579 self.do_test_compress_pause('drive-backup', False, 580 target=blockdev_target_img, 581 mode='existing') 582 583 def test_compress_pause_blockdev_backup(self): 584 self.do_test_compress_pause('blockdev-backup', True, 585 target='drive1') 586 587 588class TestCompressedToVmdk(TestCompressedToQcow2): 589 target_fmt = {'type': 'vmdk', 'args': ('-o', 'subformat=streamOptimized'), 590 'drive-opts': 'cache.no-flush=on'} 591 592 @iotests.skip_if_unsupported(['vmdk']) 593 def setUp(self): 594 pass 595 596 597if __name__ == '__main__': 598 iotests.main(supported_fmts=['raw', 'qcow2'], 599 supported_protocols=['file']) 600