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