1#!/usr/bin/env python 2# 3# Tests for image mirroring. 4# 5# Copyright (C) 2012 Red Hat, Inc. 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <http://www.gnu.org/licenses/>. 19# 20 21import time 22import os 23import iotests 24from iotests import qemu_img, qemu_io 25import struct 26 27backing_img = os.path.join(iotests.test_dir, 'backing.img') 28target_backing_img = os.path.join(iotests.test_dir, 'target-backing.img') 29test_img = os.path.join(iotests.test_dir, 'test.img') 30target_img = os.path.join(iotests.test_dir, 'target.img') 31 32class ImageMirroringTestCase(iotests.QMPTestCase): 33 '''Abstract base class for image mirroring test cases''' 34 35 def assert_no_active_mirrors(self): 36 result = self.vm.qmp('query-block-jobs') 37 self.assert_qmp(result, 'return', []) 38 39 def cancel_and_wait(self, drive='drive0', wait_ready=True): 40 '''Cancel a block job and wait for it to finish''' 41 if wait_ready: 42 ready = False 43 while not ready: 44 for event in self.vm.get_qmp_events(wait=True): 45 if event['event'] == 'BLOCK_JOB_READY': 46 self.assert_qmp(event, 'data/type', 'mirror') 47 self.assert_qmp(event, 'data/device', drive) 48 ready = True 49 50 result = self.vm.qmp('block-job-cancel', device=drive, 51 force=not wait_ready) 52 self.assert_qmp(result, 'return', {}) 53 54 cancelled = False 55 while not cancelled: 56 for event in self.vm.get_qmp_events(wait=True): 57 if event['event'] == 'BLOCK_JOB_COMPLETED' or \ 58 event['event'] == 'BLOCK_JOB_CANCELLED': 59 self.assert_qmp(event, 'data/type', 'mirror') 60 self.assert_qmp(event, 'data/device', drive) 61 if wait_ready: 62 self.assertEquals(event['event'], 'BLOCK_JOB_COMPLETED') 63 self.assert_qmp(event, 'data/offset', self.image_len) 64 self.assert_qmp(event, 'data/len', self.image_len) 65 cancelled = True 66 67 self.assert_no_active_mirrors() 68 69 def complete_and_wait(self, drive='drive0', wait_ready=True): 70 '''Complete a block job and wait for it to finish''' 71 if wait_ready: 72 ready = False 73 while not ready: 74 for event in self.vm.get_qmp_events(wait=True): 75 if event['event'] == 'BLOCK_JOB_READY': 76 self.assert_qmp(event, 'data/type', 'mirror') 77 self.assert_qmp(event, 'data/device', drive) 78 ready = True 79 80 result = self.vm.qmp('block-job-complete', device=drive) 81 self.assert_qmp(result, 'return', {}) 82 83 completed = False 84 while not completed: 85 for event in self.vm.get_qmp_events(wait=True): 86 if event['event'] == 'BLOCK_JOB_COMPLETED': 87 self.assert_qmp(event, 'data/type', 'mirror') 88 self.assert_qmp(event, 'data/device', drive) 89 self.assert_qmp_absent(event, 'data/error') 90 self.assert_qmp(event, 'data/offset', self.image_len) 91 self.assert_qmp(event, 'data/len', self.image_len) 92 completed = True 93 94 self.assert_no_active_mirrors() 95 96 def create_image(self, name, size): 97 file = open(name, 'w') 98 i = 0 99 while i < size: 100 sector = struct.pack('>l504xl', i / 512, i / 512) 101 file.write(sector) 102 i = i + 512 103 file.close() 104 105 def compare_images(self, img1, img2): 106 try: 107 qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img1, img1 + '.raw') 108 qemu_img('convert', '-f', iotests.imgfmt, '-O', 'raw', img2, img2 + '.raw') 109 file1 = open(img1 + '.raw', 'r') 110 file2 = open(img2 + '.raw', 'r') 111 return file1.read() == file2.read() 112 finally: 113 if file1 is not None: 114 file1.close() 115 if file2 is not None: 116 file2.close() 117 try: 118 os.remove(img1 + '.raw') 119 except OSError: 120 pass 121 try: 122 os.remove(img2 + '.raw') 123 except OSError: 124 pass 125 126class TestSingleDrive(ImageMirroringTestCase): 127 image_len = 1 * 1024 * 1024 # MB 128 129 def setUp(self): 130 self.create_image(backing_img, TestSingleDrive.image_len) 131 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 132 self.vm = iotests.VM().add_drive(test_img) 133 self.vm.launch() 134 135 def tearDown(self): 136 self.vm.shutdown() 137 os.remove(test_img) 138 os.remove(backing_img) 139 try: 140 os.remove(target_img) 141 except OSError: 142 pass 143 144 def test_complete(self): 145 self.assert_no_active_mirrors() 146 147 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 148 target=target_img) 149 self.assert_qmp(result, 'return', {}) 150 151 self.complete_and_wait() 152 result = self.vm.qmp('query-block') 153 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 154 self.vm.shutdown() 155 self.assertTrue(self.compare_images(test_img, target_img), 156 'target image does not match source after mirroring') 157 158 def test_cancel(self): 159 self.assert_no_active_mirrors() 160 161 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 162 target=target_img) 163 self.assert_qmp(result, 'return', {}) 164 165 self.cancel_and_wait(wait_ready=False) 166 result = self.vm.qmp('query-block') 167 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 168 self.vm.shutdown() 169 170 def test_cancel_after_ready(self): 171 self.assert_no_active_mirrors() 172 173 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 174 target=target_img) 175 self.assert_qmp(result, 'return', {}) 176 177 self.cancel_and_wait() 178 result = self.vm.qmp('query-block') 179 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 180 self.vm.shutdown() 181 self.assertTrue(self.compare_images(test_img, target_img), 182 'target image does not match source after mirroring') 183 184 def test_pause(self): 185 self.assert_no_active_mirrors() 186 187 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 188 target=target_img) 189 self.assert_qmp(result, 'return', {}) 190 191 result = self.vm.qmp('block-job-pause', device='drive0') 192 self.assert_qmp(result, 'return', {}) 193 194 time.sleep(1) 195 result = self.vm.qmp('query-block-jobs') 196 offset = self.dictpath(result, 'return[0]/offset') 197 198 time.sleep(1) 199 result = self.vm.qmp('query-block-jobs') 200 self.assert_qmp(result, 'return[0]/offset', offset) 201 202 result = self.vm.qmp('block-job-resume', device='drive0') 203 self.assert_qmp(result, 'return', {}) 204 205 self.complete_and_wait() 206 self.vm.shutdown() 207 self.assertTrue(self.compare_images(test_img, target_img), 208 'target image does not match source after mirroring') 209 210 def test_large_cluster(self): 211 self.assert_no_active_mirrors() 212 213 qemu_img('create', '-f', iotests.imgfmt, '-o', 'cluster_size=%d,backing_file=%s' 214 % (TestSingleDrive.image_len, backing_img), target_img) 215 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 216 mode='existing', target=target_img) 217 self.assert_qmp(result, 'return', {}) 218 219 self.complete_and_wait() 220 result = self.vm.qmp('query-block') 221 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 222 self.vm.shutdown() 223 self.assertTrue(self.compare_images(test_img, target_img), 224 'target image does not match source after mirroring') 225 226 def test_medium_not_found(self): 227 result = self.vm.qmp('drive-mirror', device='ide1-cd0', sync='full', 228 target=target_img) 229 self.assert_qmp(result, 'error/class', 'GenericError') 230 231 def test_image_not_found(self): 232 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 233 mode='existing', target=target_img) 234 self.assert_qmp(result, 'error/class', 'GenericError') 235 236 def test_device_not_found(self): 237 result = self.vm.qmp('drive-mirror', device='nonexistent', sync='full', 238 target=target_img) 239 self.assert_qmp(result, 'error/class', 'DeviceNotFound') 240 241class TestMirrorNoBacking(ImageMirroringTestCase): 242 image_len = 2 * 1024 * 1024 # MB 243 244 def complete_and_wait(self, drive='drive0', wait_ready=True): 245 self.create_image(target_backing_img, TestMirrorNoBacking.image_len) 246 return ImageMirroringTestCase.complete_and_wait(self, drive, wait_ready) 247 248 def compare_images(self, img1, img2): 249 self.create_image(target_backing_img, TestMirrorNoBacking.image_len) 250 return ImageMirroringTestCase.compare_images(self, img1, img2) 251 252 def setUp(self): 253 self.create_image(backing_img, TestMirrorNoBacking.image_len) 254 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 255 self.vm = iotests.VM().add_drive(test_img) 256 self.vm.launch() 257 258 def tearDown(self): 259 self.vm.shutdown() 260 os.remove(test_img) 261 os.remove(backing_img) 262 os.remove(target_backing_img) 263 os.remove(target_img) 264 265 def test_complete(self): 266 self.assert_no_active_mirrors() 267 268 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img) 269 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 270 mode='existing', target=target_img) 271 self.assert_qmp(result, 'return', {}) 272 273 self.complete_and_wait() 274 result = self.vm.qmp('query-block') 275 self.assert_qmp(result, 'return[0]/inserted/file', target_img) 276 self.vm.shutdown() 277 self.assertTrue(self.compare_images(test_img, target_img), 278 'target image does not match source after mirroring') 279 280 def test_cancel(self): 281 self.assert_no_active_mirrors() 282 283 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, target_img) 284 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 285 mode='existing', target=target_img) 286 self.assert_qmp(result, 'return', {}) 287 288 self.cancel_and_wait() 289 result = self.vm.qmp('query-block') 290 self.assert_qmp(result, 'return[0]/inserted/file', test_img) 291 self.vm.shutdown() 292 self.assertTrue(self.compare_images(test_img, target_img), 293 'target image does not match source after mirroring') 294 295class TestReadErrors(ImageMirroringTestCase): 296 image_len = 2 * 1024 * 1024 # MB 297 298 # this should be a multiple of twice the default granularity 299 # so that we hit this offset first in state 1 300 MIRROR_GRANULARITY = 1024 * 1024 301 302 def create_blkdebug_file(self, name, event, errno): 303 file = open(name, 'w') 304 file.write(''' 305[inject-error] 306state = "1" 307event = "%s" 308errno = "%d" 309immediately = "off" 310once = "on" 311sector = "%d" 312 313[set-state] 314state = "1" 315event = "%s" 316new_state = "2" 317 318[set-state] 319state = "2" 320event = "%s" 321new_state = "1" 322''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event)) 323 file.close() 324 325 def setUp(self): 326 self.blkdebug_file = backing_img + ".blkdebug" 327 self.create_image(backing_img, TestReadErrors.image_len) 328 self.create_blkdebug_file(self.blkdebug_file, "read_aio", 5) 329 qemu_img('create', '-f', iotests.imgfmt, 330 '-o', 'backing_file=blkdebug:%s:%s,backing_fmt=raw' 331 % (self.blkdebug_file, backing_img), 332 test_img) 333 self.vm = iotests.VM().add_drive(test_img) 334 self.vm.launch() 335 336 def tearDown(self): 337 self.vm.shutdown() 338 os.remove(test_img) 339 os.remove(backing_img) 340 os.remove(self.blkdebug_file) 341 342 def test_report_read(self): 343 self.assert_no_active_mirrors() 344 345 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 346 target=target_img) 347 self.assert_qmp(result, 'return', {}) 348 349 completed = False 350 error = False 351 while not completed: 352 for event in self.vm.get_qmp_events(wait=True): 353 if event['event'] == 'BLOCK_JOB_ERROR': 354 self.assert_qmp(event, 'data/device', 'drive0') 355 self.assert_qmp(event, 'data/operation', 'read') 356 error = True 357 elif event['event'] == 'BLOCK_JOB_READY': 358 self.assertTrue(False, 'job completed unexpectedly') 359 elif event['event'] == 'BLOCK_JOB_COMPLETED': 360 self.assertTrue(error, 'job completed unexpectedly') 361 self.assert_qmp(event, 'data/type', 'mirror') 362 self.assert_qmp(event, 'data/device', 'drive0') 363 self.assert_qmp(event, 'data/error', 'Input/output error') 364 self.assert_qmp(event, 'data/len', self.image_len) 365 completed = True 366 367 self.assert_no_active_mirrors() 368 self.vm.shutdown() 369 370 def test_ignore_read(self): 371 self.assert_no_active_mirrors() 372 373 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 374 target=target_img, on_source_error='ignore') 375 self.assert_qmp(result, 'return', {}) 376 377 event = self.vm.get_qmp_event(wait=True) 378 self.assertEquals(event['event'], 'BLOCK_JOB_ERROR') 379 self.assert_qmp(event, 'data/device', 'drive0') 380 self.assert_qmp(event, 'data/operation', 'read') 381 result = self.vm.qmp('query-block-jobs') 382 self.assert_qmp(result, 'return[0]/paused', False) 383 self.complete_and_wait() 384 self.vm.shutdown() 385 386 def test_stop_read(self): 387 self.assert_no_active_mirrors() 388 389 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 390 target=target_img, on_source_error='stop') 391 self.assert_qmp(result, 'return', {}) 392 393 error = False 394 ready = False 395 while not ready: 396 for event in self.vm.get_qmp_events(wait=True): 397 if event['event'] == 'BLOCK_JOB_ERROR': 398 self.assert_qmp(event, 'data/device', 'drive0') 399 self.assert_qmp(event, 'data/operation', 'read') 400 401 result = self.vm.qmp('query-block-jobs') 402 self.assert_qmp(result, 'return[0]/paused', True) 403 self.assert_qmp(result, 'return[0]/io-status', 'failed') 404 405 result = self.vm.qmp('block-job-resume', device='drive0') 406 self.assert_qmp(result, 'return', {}) 407 error = True 408 elif event['event'] == 'BLOCK_JOB_READY': 409 self.assertTrue(error, 'job completed unexpectedly') 410 self.assert_qmp(event, 'data/device', 'drive0') 411 ready = True 412 413 result = self.vm.qmp('query-block-jobs') 414 self.assert_qmp(result, 'return[0]/paused', False) 415 self.assert_qmp(result, 'return[0]/io-status', 'ok') 416 417 self.complete_and_wait(wait_ready=False) 418 self.assert_no_active_mirrors() 419 self.vm.shutdown() 420 421class TestWriteErrors(ImageMirroringTestCase): 422 image_len = 2 * 1024 * 1024 # MB 423 424 # this should be a multiple of twice the default granularity 425 # so that we hit this offset first in state 1 426 MIRROR_GRANULARITY = 1024 * 1024 427 428 def create_blkdebug_file(self, name, event, errno): 429 file = open(name, 'w') 430 file.write(''' 431[inject-error] 432state = "1" 433event = "%s" 434errno = "%d" 435immediately = "off" 436once = "on" 437sector = "%d" 438 439[set-state] 440state = "1" 441event = "%s" 442new_state = "2" 443 444[set-state] 445state = "2" 446event = "%s" 447new_state = "1" 448''' % (event, errno, self.MIRROR_GRANULARITY / 512, event, event)) 449 file.close() 450 451 def setUp(self): 452 self.blkdebug_file = target_img + ".blkdebug" 453 self.create_image(backing_img, TestWriteErrors.image_len) 454 self.create_blkdebug_file(self.blkdebug_file, "write_aio", 5) 455 qemu_img('create', '-f', iotests.imgfmt, '-obacking_file=%s' %(backing_img), test_img) 456 self.vm = iotests.VM().add_drive(test_img) 457 self.target_img = 'blkdebug:%s:%s' % (self.blkdebug_file, target_img) 458 qemu_img('create', '-f', iotests.imgfmt, '-osize=%d' %(TestWriteErrors.image_len), target_img) 459 self.vm.launch() 460 461 def tearDown(self): 462 self.vm.shutdown() 463 os.remove(test_img) 464 os.remove(backing_img) 465 os.remove(self.blkdebug_file) 466 467 def test_report_write(self): 468 self.assert_no_active_mirrors() 469 470 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 471 mode='existing', target=self.target_img) 472 self.assert_qmp(result, 'return', {}) 473 474 completed = False 475 error = False 476 while not completed: 477 for event in self.vm.get_qmp_events(wait=True): 478 if event['event'] == 'BLOCK_JOB_ERROR': 479 self.assert_qmp(event, 'data/device', 'drive0') 480 self.assert_qmp(event, 'data/operation', 'write') 481 error = True 482 elif event['event'] == 'BLOCK_JOB_READY': 483 self.assertTrue(False, 'job completed unexpectedly') 484 elif event['event'] == 'BLOCK_JOB_COMPLETED': 485 self.assertTrue(error, 'job completed unexpectedly') 486 self.assert_qmp(event, 'data/type', 'mirror') 487 self.assert_qmp(event, 'data/device', 'drive0') 488 self.assert_qmp(event, 'data/error', 'Input/output error') 489 self.assert_qmp(event, 'data/len', self.image_len) 490 completed = True 491 492 self.assert_no_active_mirrors() 493 self.vm.shutdown() 494 495 def test_ignore_write(self): 496 self.assert_no_active_mirrors() 497 498 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 499 mode='existing', target=self.target_img, 500 on_target_error='ignore') 501 self.assert_qmp(result, 'return', {}) 502 503 event = self.vm.get_qmp_event(wait=True) 504 self.assertEquals(event['event'], 'BLOCK_JOB_ERROR') 505 self.assert_qmp(event, 'data/device', 'drive0') 506 self.assert_qmp(event, 'data/operation', 'write') 507 result = self.vm.qmp('query-block-jobs') 508 self.assert_qmp(result, 'return[0]/paused', False) 509 self.complete_and_wait() 510 self.vm.shutdown() 511 512 def test_stop_write(self): 513 self.assert_no_active_mirrors() 514 515 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 516 mode='existing', target=self.target_img, 517 on_target_error='stop') 518 self.assert_qmp(result, 'return', {}) 519 520 error = False 521 ready = False 522 while not ready: 523 for event in self.vm.get_qmp_events(wait=True): 524 if event['event'] == 'BLOCK_JOB_ERROR': 525 self.assert_qmp(event, 'data/device', 'drive0') 526 self.assert_qmp(event, 'data/operation', 'write') 527 528 result = self.vm.qmp('query-block-jobs') 529 self.assert_qmp(result, 'return[0]/paused', True) 530 self.assert_qmp(result, 'return[0]/io-status', 'failed') 531 532 result = self.vm.qmp('block-job-resume', device='drive0') 533 self.assert_qmp(result, 'return', {}) 534 535 result = self.vm.qmp('query-block-jobs') 536 self.assert_qmp(result, 'return[0]/paused', False) 537 self.assert_qmp(result, 'return[0]/io-status', 'ok') 538 error = True 539 elif event['event'] == 'BLOCK_JOB_READY': 540 self.assertTrue(error, 'job completed unexpectedly') 541 self.assert_qmp(event, 'data/device', 'drive0') 542 ready = True 543 544 self.complete_and_wait(wait_ready=False) 545 self.assert_no_active_mirrors() 546 self.vm.shutdown() 547 548class TestSetSpeed(ImageMirroringTestCase): 549 image_len = 80 * 1024 * 1024 # MB 550 551 def setUp(self): 552 qemu_img('create', backing_img, str(TestSetSpeed.image_len)) 553 qemu_img('create', '-f', iotests.imgfmt, '-o', 'backing_file=%s' % backing_img, test_img) 554 self.vm = iotests.VM().add_drive(test_img) 555 self.vm.launch() 556 557 def tearDown(self): 558 self.vm.shutdown() 559 os.remove(test_img) 560 os.remove(backing_img) 561 os.remove(target_img) 562 563 def test_set_speed(self): 564 self.assert_no_active_mirrors() 565 566 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 567 target=target_img) 568 self.assert_qmp(result, 'return', {}) 569 570 # Default speed is 0 571 result = self.vm.qmp('query-block-jobs') 572 self.assert_qmp(result, 'return[0]/device', 'drive0') 573 self.assert_qmp(result, 'return[0]/speed', 0) 574 575 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=8 * 1024 * 1024) 576 self.assert_qmp(result, 'return', {}) 577 578 # Ensure the speed we set was accepted 579 result = self.vm.qmp('query-block-jobs') 580 self.assert_qmp(result, 'return[0]/device', 'drive0') 581 self.assert_qmp(result, 'return[0]/speed', 8 * 1024 * 1024) 582 583 self.cancel_and_wait() 584 585 # Check setting speed in drive-mirror works 586 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 587 target=target_img, speed=4*1024*1024) 588 self.assert_qmp(result, 'return', {}) 589 590 result = self.vm.qmp('query-block-jobs') 591 self.assert_qmp(result, 'return[0]/device', 'drive0') 592 self.assert_qmp(result, 'return[0]/speed', 4 * 1024 * 1024) 593 594 self.cancel_and_wait() 595 596 def test_set_speed_invalid(self): 597 self.assert_no_active_mirrors() 598 599 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 600 target=target_img, speed=-1) 601 self.assert_qmp(result, 'error/class', 'GenericError') 602 603 self.assert_no_active_mirrors() 604 605 result = self.vm.qmp('drive-mirror', device='drive0', sync='full', 606 target=target_img) 607 self.assert_qmp(result, 'return', {}) 608 609 result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1) 610 self.assert_qmp(result, 'error/class', 'GenericError') 611 612 self.cancel_and_wait() 613 614if __name__ == '__main__': 615 iotests.main(supported_fmts=['qcow2', 'qed']) 616