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