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 time.sleep(1) 93 result = self.vm.qmp('query-block-jobs') 94 offset = self.dictpath(result, 'return[0]/offset') 95 96 time.sleep(1) 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(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 248class TestSingleTransaction(iotests.QMPTestCase): 249 def setUp(self): 250 qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(image_len)) 251 252 self.vm = iotests.VM().add_drive(test_img) 253 self.vm.add_drive(blockdev_target_img, interface="none") 254 if iotests.qemu_default_machine == 'pc': 255 self.vm.add_drive(None, 'media=cdrom', 'ide') 256 self.vm.launch() 257 258 def tearDown(self): 259 self.vm.shutdown() 260 os.remove(blockdev_target_img) 261 try: 262 os.remove(target_img) 263 except OSError: 264 pass 265 266 def do_test_cancel(self, cmd, target): 267 self.assert_no_active_block_jobs() 268 269 result = self.vm.qmp('transaction', actions=[{ 270 'type': cmd, 271 'data': { 'device': 'drive0', 272 'target': target, 273 'sync': 'full' }, 274 } 275 ]) 276 277 self.assert_qmp(result, 'return', {}) 278 279 event = self.cancel_and_wait() 280 self.assert_qmp(event, 'data/type', 'backup') 281 282 def test_cancel_drive_backup(self): 283 self.do_test_cancel('drive-backup', target_img) 284 285 def test_cancel_blockdev_backup(self): 286 self.do_test_cancel('blockdev-backup', 'drive1') 287 288 def do_test_pause(self, cmd, target, image): 289 self.assert_no_active_block_jobs() 290 291 self.vm.pause_drive('drive0') 292 result = self.vm.qmp('transaction', actions=[{ 293 'type': cmd, 294 'data': { 'device': 'drive0', 295 'target': target, 296 'sync': 'full' }, 297 } 298 ]) 299 self.assert_qmp(result, 'return', {}) 300 301 result = self.vm.qmp('block-job-pause', device='drive0') 302 self.assert_qmp(result, 'return', {}) 303 304 self.vm.resume_drive('drive0') 305 time.sleep(1) 306 result = self.vm.qmp('query-block-jobs') 307 offset = self.dictpath(result, 'return[0]/offset') 308 309 time.sleep(1) 310 result = self.vm.qmp('query-block-jobs') 311 self.assert_qmp(result, 'return[0]/offset', offset) 312 313 result = self.vm.qmp('block-job-resume', device='drive0') 314 self.assert_qmp(result, 'return', {}) 315 316 self.wait_until_completed() 317 318 self.vm.shutdown() 319 self.assertTrue(iotests.compare_images(test_img, image), 320 'target image does not match source after backup') 321 322 def test_pause_drive_backup(self): 323 self.do_test_pause('drive-backup', target_img, target_img) 324 325 def test_pause_blockdev_backup(self): 326 self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img) 327 328 def do_test_medium_not_found(self, cmd, target): 329 if iotests.qemu_default_machine != 'pc': 330 return 331 332 result = self.vm.qmp('transaction', actions=[{ 333 'type': cmd, 334 'data': { 'device': 'drive2', # CD-ROM 335 'target': target, 336 'sync': 'full' }, 337 } 338 ]) 339 self.assert_qmp(result, 'error/class', 'GenericError') 340 341 def test_medium_not_found_drive_backup(self): 342 self.do_test_medium_not_found('drive-backup', target_img) 343 344 def test_medium_not_found_blockdev_backup(self): 345 self.do_test_medium_not_found('blockdev-backup', 'drive1') 346 347 def test_image_not_found(self): 348 result = self.vm.qmp('transaction', actions=[{ 349 'type': 'drive-backup', 350 'data': { 'device': 'drive0', 351 'mode': 'existing', 352 'target': target_img, 353 'sync': 'full' }, 354 } 355 ]) 356 self.assert_qmp(result, 'error/class', 'GenericError') 357 358 def test_device_not_found(self): 359 result = self.vm.qmp('transaction', actions=[{ 360 'type': 'drive-backup', 361 'data': { 'device': 'nonexistent', 362 'mode': 'existing', 363 'target': target_img, 364 'sync': 'full' }, 365 } 366 ]) 367 self.assert_qmp(result, 'error/class', 'GenericError') 368 369 result = self.vm.qmp('transaction', actions=[{ 370 'type': 'blockdev-backup', 371 'data': { 'device': 'nonexistent', 372 'target': 'drive1', 373 'sync': 'full' }, 374 } 375 ]) 376 self.assert_qmp(result, 'error/class', 'GenericError') 377 378 result = self.vm.qmp('transaction', actions=[{ 379 'type': 'blockdev-backup', 380 'data': { 'device': 'drive0', 381 'target': 'nonexistent', 382 'sync': 'full' }, 383 } 384 ]) 385 self.assert_qmp(result, 'error/class', 'GenericError') 386 387 result = self.vm.qmp('transaction', actions=[{ 388 'type': 'blockdev-backup', 389 'data': { 'device': 'nonexistent', 390 'target': 'nonexistent', 391 'sync': 'full' }, 392 } 393 ]) 394 self.assert_qmp(result, 'error/class', 'GenericError') 395 396 def test_target_is_source(self): 397 result = self.vm.qmp('transaction', actions=[{ 398 'type': 'blockdev-backup', 399 'data': { 'device': 'drive0', 400 'target': 'drive0', 401 'sync': 'full' }, 402 } 403 ]) 404 self.assert_qmp(result, 'error/class', 'GenericError') 405 406 def test_abort(self): 407 result = self.vm.qmp('transaction', actions=[{ 408 'type': 'drive-backup', 409 'data': { 'device': 'nonexistent', 410 'mode': 'existing', 411 'target': target_img, 412 'sync': 'full' }, 413 }, { 414 'type': 'Abort', 415 'data': {}, 416 } 417 ]) 418 self.assert_qmp(result, 'error/class', 'GenericError') 419 self.assert_no_active_block_jobs() 420 421 result = self.vm.qmp('transaction', actions=[{ 422 'type': 'blockdev-backup', 423 'data': { 'device': 'nonexistent', 424 'target': 'drive1', 425 'sync': 'full' }, 426 }, { 427 'type': 'Abort', 428 'data': {}, 429 } 430 ]) 431 self.assert_qmp(result, 'error/class', 'GenericError') 432 self.assert_no_active_block_jobs() 433 434 result = self.vm.qmp('transaction', actions=[{ 435 'type': 'blockdev-backup', 436 'data': { 'device': 'drive0', 437 'target': 'nonexistent', 438 'sync': 'full' }, 439 }, { 440 'type': 'Abort', 441 'data': {}, 442 } 443 ]) 444 self.assert_qmp(result, 'error/class', 'GenericError') 445 self.assert_no_active_block_jobs() 446 447 448class TestDriveCompression(iotests.QMPTestCase): 449 image_len = 64 * 1024 * 1024 # MB 450 fmt_supports_compression = [{'type': 'qcow2', 'args': ()}, 451 {'type': 'vmdk', 'args': ('-o', 'subformat=streamOptimized')}] 452 453 def tearDown(self): 454 self.vm.shutdown() 455 os.remove(blockdev_target_img) 456 try: 457 os.remove(target_img) 458 except OSError: 459 pass 460 461 def do_prepare_drives(self, fmt, args, attach_target): 462 self.vm = iotests.VM().add_drive(test_img) 463 464 qemu_img('create', '-f', fmt, blockdev_target_img, 465 str(TestDriveCompression.image_len), *args) 466 if attach_target: 467 self.vm.add_drive(blockdev_target_img, format=fmt, interface="none") 468 469 self.vm.launch() 470 471 def do_test_compress_complete(self, cmd, format, attach_target, **args): 472 self.do_prepare_drives(format['type'], format['args'], attach_target) 473 474 self.assert_no_active_block_jobs() 475 476 result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) 477 self.assert_qmp(result, 'return', {}) 478 479 self.wait_until_completed() 480 481 self.vm.shutdown() 482 self.assertTrue(iotests.compare_images(test_img, blockdev_target_img, 483 iotests.imgfmt, format['type']), 484 'target image does not match source after backup') 485 486 def test_complete_compress_drive_backup(self): 487 for format in TestDriveCompression.fmt_supports_compression: 488 self.do_test_compress_complete('drive-backup', format, False, 489 target=blockdev_target_img, mode='existing') 490 491 def test_complete_compress_blockdev_backup(self): 492 for format in TestDriveCompression.fmt_supports_compression: 493 self.do_test_compress_complete('blockdev-backup', format, True, 494 target='drive1') 495 496 def do_test_compress_cancel(self, cmd, format, attach_target, **args): 497 self.do_prepare_drives(format['type'], format['args'], attach_target) 498 499 self.assert_no_active_block_jobs() 500 501 result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) 502 self.assert_qmp(result, 'return', {}) 503 504 event = self.cancel_and_wait() 505 self.assert_qmp(event, 'data/type', 'backup') 506 507 self.vm.shutdown() 508 509 def test_compress_cancel_drive_backup(self): 510 for format in TestDriveCompression.fmt_supports_compression: 511 self.do_test_compress_cancel('drive-backup', format, False, 512 target=blockdev_target_img, mode='existing') 513 514 def test_compress_cancel_blockdev_backup(self): 515 for format in TestDriveCompression.fmt_supports_compression: 516 self.do_test_compress_cancel('blockdev-backup', format, True, 517 target='drive1') 518 519 def do_test_compress_pause(self, cmd, format, attach_target, **args): 520 self.do_prepare_drives(format['type'], format['args'], attach_target) 521 522 self.assert_no_active_block_jobs() 523 524 self.vm.pause_drive('drive0') 525 result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) 526 self.assert_qmp(result, 'return', {}) 527 528 result = self.vm.qmp('block-job-pause', device='drive0') 529 self.assert_qmp(result, 'return', {}) 530 531 self.vm.resume_drive('drive0') 532 time.sleep(1) 533 result = self.vm.qmp('query-block-jobs') 534 offset = self.dictpath(result, 'return[0]/offset') 535 536 time.sleep(1) 537 result = self.vm.qmp('query-block-jobs') 538 self.assert_qmp(result, 'return[0]/offset', offset) 539 540 result = self.vm.qmp('block-job-resume', device='drive0') 541 self.assert_qmp(result, 'return', {}) 542 543 self.wait_until_completed() 544 545 self.vm.shutdown() 546 self.assertTrue(iotests.compare_images(test_img, blockdev_target_img, 547 iotests.imgfmt, format['type']), 548 'target image does not match source after backup') 549 550 def test_compress_pause_drive_backup(self): 551 for format in TestDriveCompression.fmt_supports_compression: 552 self.do_test_compress_pause('drive-backup', format, False, 553 target=blockdev_target_img, mode='existing') 554 555 def test_compress_pause_blockdev_backup(self): 556 for format in TestDriveCompression.fmt_supports_compression: 557 self.do_test_compress_pause('blockdev-backup', format, True, 558 target='drive1') 559 560if __name__ == '__main__': 561 iotests.main(supported_fmts=['raw', 'qcow2']) 562