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