| 1 | #!/usr/bin/env python3 |
| 2 | # group: rw |
| 3 | # |
| 4 | # Tests for internal snapshot. |
| 5 | # |
| 6 | # Copyright (C) 2013 IBM, Inc. |
| 7 | # |
| 8 | # Based on 055. |
| 9 | # |
| 10 | # This program is free software; you can redistribute it and/or modify |
| 11 | # it under the terms of the GNU General Public License as published by |
| 12 | # the Free Software Foundation; either version 2 of the License, or |
| 13 | # (at your option) any later version. |
| 14 | # |
| 15 | # This program is distributed in the hope that it will be useful, |
| 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | # GNU General Public License for more details. |
| 19 | # |
| 20 | # You should have received a copy of the GNU General Public License |
| 21 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 22 | # |
| 23 | |
| 24 | import time |
| 25 | import os |
| 26 | import iotests |
| 27 | from iotests import qemu_img, qemu_io |
| 28 | |
| 29 | test_drv_base_name = 'drive' |
| 30 | |
| 31 | class ImageSnapshotTestCase(iotests.QMPTestCase): |
| 32 | image_len = 120 * 1024 * 1024 # MB |
| 33 | |
| 34 | def __init__(self, *args): |
| 35 | self.expect = [] |
| 36 | super(ImageSnapshotTestCase, self).__init__(*args) |
| 37 | |
| 38 | def _setUp(self, test_img_base_name, image_num): |
| 39 | self.vm = iotests.VM() |
| 40 | for i in range(0, image_num): |
| 41 | filename = '%s%d' % (test_img_base_name, i) |
| 42 | img = os.path.join(iotests.test_dir, filename) |
| 43 | device = '%s%d' % (test_drv_base_name, i) |
| 44 | qemu_img('create', '-f', iotests.imgfmt, img, str(self.image_len)) |
| 45 | self.vm.add_drive(img) |
| 46 | self.expect.append({'image': img, 'device': device, |
| 47 | 'snapshots': [], |
| 48 | 'snapshots_name_counter': 0}) |
| 49 | self.vm.launch() |
| 50 | |
| 51 | def tearDown(self): |
| 52 | self.vm.shutdown() |
| 53 | for dev_expect in self.expect: |
| 54 | os.remove(dev_expect['image']) |
| 55 | |
| 56 | def createSnapshotInTransaction(self, snapshot_num, abort = False): |
| 57 | actions = [] |
| 58 | for dev_expect in self.expect: |
| 59 | num = dev_expect['snapshots_name_counter'] |
| 60 | for j in range(0, snapshot_num): |
| 61 | name = '%s_sn%d' % (dev_expect['device'], num) |
| 62 | num = num + 1 |
| 63 | if abort == False: |
| 64 | dev_expect['snapshots'].append({'name': name}) |
| 65 | dev_expect['snapshots_name_counter'] = num |
| 66 | actions.append({ |
| 67 | 'type': 'blockdev-snapshot-internal-sync', |
| 68 | 'data': { 'device': dev_expect['device'], |
| 69 | 'name': name }, |
| 70 | }) |
| 71 | |
| 72 | if abort == True: |
| 73 | actions.append({ |
| 74 | 'type': 'abort', |
| 75 | 'data': {}, |
| 76 | }) |
| 77 | |
| 78 | result = self.vm.qmp('transaction', actions = actions) |
| 79 | |
| 80 | if abort == True: |
| 81 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 82 | else: |
| 83 | self.assert_qmp(result, 'return', {}) |
| 84 | |
| 85 | def verifySnapshotInfo(self): |
| 86 | result = self.vm.qmp('query-block') |
| 87 | |
| 88 | # Verify each expected result |
| 89 | for dev_expect in self.expect: |
| 90 | # 1. Find the returned image value and snapshot info |
| 91 | image_result = None |
| 92 | for device in result['return']: |
| 93 | if device['device'] == dev_expect['device']: |
| 94 | image_result = device['inserted']['image'] |
| 95 | break |
| 96 | self.assertTrue(image_result != None) |
| 97 | # Do not consider zero snapshot case now |
| 98 | sn_list_result = image_result['snapshots'] |
| 99 | sn_list_expect = dev_expect['snapshots'] |
| 100 | |
| 101 | # 2. Verify it with expect |
| 102 | self.assertTrue(len(sn_list_result) == len(sn_list_expect)) |
| 103 | |
| 104 | for sn_expect in sn_list_expect: |
| 105 | sn_result = None |
| 106 | for sn in sn_list_result: |
| 107 | if sn_expect['name'] == sn['name']: |
| 108 | sn_result = sn |
| 109 | break |
| 110 | self.assertTrue(sn_result != None) |
| 111 | # Fill in the detail info |
| 112 | sn_expect.update(sn_result) |
| 113 | |
| 114 | def deleteSnapshot(self, device, id = None, name = None): |
| 115 | sn_list_expect = None |
| 116 | sn_expect = None |
| 117 | |
| 118 | self.assertTrue(id != None or name != None) |
| 119 | |
| 120 | # Fill in the detail info include ID |
| 121 | self.verifySnapshotInfo() |
| 122 | |
| 123 | #find the expected snapshot list |
| 124 | for dev_expect in self.expect: |
| 125 | if dev_expect['device'] == device: |
| 126 | sn_list_expect = dev_expect['snapshots'] |
| 127 | break |
| 128 | self.assertTrue(sn_list_expect != None) |
| 129 | |
| 130 | if id != None and name != None: |
| 131 | for sn in sn_list_expect: |
| 132 | if sn['id'] == id and sn['name'] == name: |
| 133 | sn_expect = sn |
| 134 | result = \ |
| 135 | self.vm.qmp('blockdev-snapshot-delete-internal-sync', |
| 136 | device = device, |
| 137 | id = id, |
| 138 | name = name) |
| 139 | break |
| 140 | elif id != None: |
| 141 | for sn in sn_list_expect: |
| 142 | if sn['id'] == id: |
| 143 | sn_expect = sn |
| 144 | result = \ |
| 145 | self.vm.qmp('blockdev-snapshot-delete-internal-sync', |
| 146 | device = device, |
| 147 | id = id) |
| 148 | break |
| 149 | else: |
| 150 | for sn in sn_list_expect: |
| 151 | if sn['name'] == name: |
| 152 | sn_expect = sn |
| 153 | result = \ |
| 154 | self.vm.qmp('blockdev-snapshot-delete-internal-sync', |
| 155 | device = device, |
| 156 | name = name) |
| 157 | break |
| 158 | |
| 159 | self.assertTrue(sn_expect != None) |
| 160 | |
| 161 | self.assert_qmp(result, 'return', sn_expect) |
| 162 | sn_list_expect.remove(sn_expect) |
| 163 | |
| 164 | class TestSingleTransaction(ImageSnapshotTestCase): |
| 165 | def setUp(self): |
| 166 | self._setUp('test_a.img', 1) |
| 167 | |
| 168 | def test_create(self): |
| 169 | self.createSnapshotInTransaction(1) |
| 170 | self.verifySnapshotInfo() |
| 171 | |
| 172 | def test_error_name_empty(self): |
| 173 | actions = [{'type': 'blockdev-snapshot-internal-sync', |
| 174 | 'data': { 'device': self.expect[0]['device'], |
| 175 | 'name': '' }, |
| 176 | }] |
| 177 | result = self.vm.qmp('transaction', actions = actions) |
| 178 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 179 | |
| 180 | def test_error_device(self): |
| 181 | actions = [{'type': 'blockdev-snapshot-internal-sync', |
| 182 | 'data': { 'device': 'drive_error', |
| 183 | 'name': 'a' }, |
| 184 | }] |
| 185 | result = self.vm.qmp('transaction', actions = actions) |
| 186 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 187 | |
| 188 | def test_error_exist(self): |
| 189 | self.createSnapshotInTransaction(1) |
| 190 | self.verifySnapshotInfo() |
| 191 | actions = [{'type': 'blockdev-snapshot-internal-sync', |
| 192 | 'data': { 'device': self.expect[0]['device'], |
| 193 | 'name': self.expect[0]['snapshots'][0] }, |
| 194 | }] |
| 195 | result = self.vm.qmp('transaction', actions = actions) |
| 196 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 197 | |
| 198 | class TestMultipleTransaction(ImageSnapshotTestCase): |
| 199 | def setUp(self): |
| 200 | self._setUp('test_b.img', 2) |
| 201 | |
| 202 | def test_create(self): |
| 203 | self.createSnapshotInTransaction(3) |
| 204 | self.verifySnapshotInfo() |
| 205 | |
| 206 | def test_abort(self): |
| 207 | self.createSnapshotInTransaction(2) |
| 208 | self.verifySnapshotInfo() |
| 209 | self.createSnapshotInTransaction(3, abort = True) |
| 210 | self.verifySnapshotInfo() |
| 211 | |
| 212 | class TestSnapshotDelete(ImageSnapshotTestCase): |
| 213 | def setUp(self): |
| 214 | self._setUp('test_c.img', 1) |
| 215 | |
| 216 | def test_delete_with_id(self): |
| 217 | self.createSnapshotInTransaction(2) |
| 218 | self.verifySnapshotInfo() |
| 219 | self.deleteSnapshot(self.expect[0]['device'], |
| 220 | id = self.expect[0]['snapshots'][0]['id']) |
| 221 | self.verifySnapshotInfo() |
| 222 | |
| 223 | def test_delete_with_name(self): |
| 224 | self.createSnapshotInTransaction(3) |
| 225 | self.verifySnapshotInfo() |
| 226 | self.deleteSnapshot(self.expect[0]['device'], |
| 227 | name = self.expect[0]['snapshots'][1]['name']) |
| 228 | self.verifySnapshotInfo() |
| 229 | |
| 230 | def test_delete_with_id_and_name(self): |
| 231 | self.createSnapshotInTransaction(4) |
| 232 | self.verifySnapshotInfo() |
| 233 | self.deleteSnapshot(self.expect[0]['device'], |
| 234 | id = self.expect[0]['snapshots'][2]['id'], |
| 235 | name = self.expect[0]['snapshots'][2]['name']) |
| 236 | self.verifySnapshotInfo() |
| 237 | |
| 238 | |
| 239 | def test_error_device(self): |
| 240 | result = self.vm.qmp('blockdev-snapshot-delete-internal-sync', |
| 241 | device = 'drive_error', |
| 242 | id = '0') |
| 243 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 244 | |
| 245 | def test_error_no_id_and_name(self): |
| 246 | result = self.vm.qmp('blockdev-snapshot-delete-internal-sync', |
| 247 | device = self.expect[0]['device']) |
| 248 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 249 | |
| 250 | def test_error_snapshot_not_exist(self): |
| 251 | self.createSnapshotInTransaction(2) |
| 252 | self.verifySnapshotInfo() |
| 253 | result = self.vm.qmp('blockdev-snapshot-delete-internal-sync', |
| 254 | device = self.expect[0]['device'], |
| 255 | id = self.expect[0]['snapshots'][0]['id'], |
| 256 | name = self.expect[0]['snapshots'][1]['name']) |
| 257 | self.assert_qmp(result, 'error/class', 'GenericError') |
| 258 | |
| 259 | if __name__ == '__main__': |
| 260 | iotests.main(supported_fmts=['qcow2'], |
| 261 | supported_protocols=['file']) |