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