]> git.proxmox.com Git - ceph.git/blame - ceph/qa/tasks/mgr/dashboard/test_rbd.py
bump version to 19.2.0-pve1
[ceph.git] / ceph / qa / tasks / mgr / dashboard / test_rbd.py
CommitLineData
11fdf7f2
TL
1# -*- coding: utf-8 -*-
2# pylint: disable=too-many-public-methods
3
4from __future__ import absolute_import
5
6import time
7
f67539c2 8from .helper import DashboardTestCase, JLeaf, JList, JObj
11fdf7f2
TL
9
10
11class RbdTest(DashboardTestCase):
f67539c2 12 AUTH_ROLES = ['pool-manager', 'block-manager', 'cluster-manager']
2a845540 13 LIST_VERSION = '2.0'
11fdf7f2
TL
14
15 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['create', 'update', 'delete']}])
16 def test_read_access_permissions(self):
2a845540
TL
17 self._get('/api/block/image?offset=0&limit=-1&search=&sort=+name',
18 version=RbdTest.LIST_VERSION)
11fdf7f2 19 self.assertStatus(403)
9f95a23c 20 self.get_image('pool', None, 'image')
11fdf7f2
TL
21 self.assertStatus(403)
22
23 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'update', 'delete']}])
24 def test_create_access_permissions(self):
9f95a23c 25 self.create_image('pool', None, 'name', 0)
11fdf7f2 26 self.assertStatus(403)
1e59de90 27 self.create_snapshot('pool', None, 'image', 'snapshot', False)
11fdf7f2 28 self.assertStatus(403)
9f95a23c 29 self.copy_image('src_pool', None, 'src_image', 'dest_pool', None, 'dest_image')
11fdf7f2 30 self.assertStatus(403)
9f95a23c 31 self.clone_image('parent_pool', None, 'parent_image', 'parent_snap', 'pool', None, 'name')
11fdf7f2
TL
32 self.assertStatus(403)
33
34 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'create', 'delete']}])
35 def test_update_access_permissions(self):
9f95a23c 36 self.edit_image('pool', None, 'image')
11fdf7f2 37 self.assertStatus(403)
9f95a23c 38 self.update_snapshot('pool', None, 'image', 'snapshot', None, None)
11fdf7f2 39 self.assertStatus(403)
9f95a23c 40 self.rollback_snapshot('rbd', None, 'rollback_img', 'snap1')
11fdf7f2 41 self.assertStatus(403)
9f95a23c 42 self.flatten_image('pool', None, 'image')
11fdf7f2
TL
43 self.assertStatus(403)
44
45 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'create', 'update']}])
46 def test_delete_access_permissions(self):
9f95a23c 47 self.remove_image('pool', None, 'image')
11fdf7f2 48 self.assertStatus(403)
9f95a23c 49 self.remove_snapshot('pool', None, 'image', 'snapshot')
11fdf7f2
TL
50 self.assertStatus(403)
51
52 @classmethod
9f95a23c
TL
53 def create_namespace(cls, pool, namespace):
54 data = {'namespace': namespace}
55 return cls._post('/api/block/pool/{}/namespace'.format(pool), data)
56
57 @classmethod
58 def remove_namespace(cls, pool, namespace):
59 return cls._delete('/api/block/pool/{}/namespace/{}'.format(pool, namespace))
60
61 @classmethod
62 def create_image(cls, pool, namespace, name, size, **kwargs):
63 data = {'name': name, 'pool_name': pool, 'namespace': namespace, 'size': size}
11fdf7f2
TL
64 data.update(kwargs)
65 return cls._task_post('/api/block/image', data)
66
67 @classmethod
9f95a23c
TL
68 def get_image(cls, pool, namespace, name):
69 namespace = '{}%2F'.format(namespace) if namespace else ''
70 return cls._get('/api/block/image/{}%2F{}{}'.format(pool, namespace, name))
71
72 @classmethod
73 def clone_image(cls, parent_pool, parent_namespace, parent_image, parent_snap, pool, namespace,
74 name, **kwargs):
11fdf7f2 75 # pylint: disable=too-many-arguments
9f95a23c 76 data = {'child_image_name': name, 'child_namespace': namespace, 'child_pool_name': pool}
11fdf7f2 77 data.update(kwargs)
9f95a23c
TL
78 parent_namespace = '{}%2F'.format(parent_namespace) if parent_namespace else ''
79 return cls._task_post('/api/block/image/{}%2F{}{}/snap/{}/clone'
80 .format(parent_pool, parent_namespace, parent_image, parent_snap),
11fdf7f2
TL
81 data)
82
83 @classmethod
9f95a23c
TL
84 def copy_image(cls, src_pool, src_namespace, src_image, dest_pool, dest_namespace, dest_image,
85 **kwargs):
11fdf7f2 86 # pylint: disable=too-many-arguments
9f95a23c
TL
87 data = {'dest_image_name': dest_image,
88 'dest_pool_name': dest_pool,
89 'dest_namespace': dest_namespace}
11fdf7f2 90 data.update(kwargs)
9f95a23c
TL
91 src_namespace = '{}%2F'.format(src_namespace) if src_namespace else ''
92 return cls._task_post('/api/block/image/{}%2F{}{}/copy'
93 .format(src_pool, src_namespace, src_image), data)
11fdf7f2
TL
94
95 @classmethod
9f95a23c
TL
96 def remove_image(cls, pool, namespace, image):
97 namespace = '{}%2F'.format(namespace) if namespace else ''
98 return cls._task_delete('/api/block/image/{}%2F{}{}'.format(pool, namespace, image))
11fdf7f2
TL
99
100 # pylint: disable=too-many-arguments
101 @classmethod
9f95a23c 102 def edit_image(cls, pool, namespace, image, name=None, size=None, features=None, **kwargs):
11fdf7f2 103 kwargs.update({'name': name, 'size': size, 'features': features})
9f95a23c
TL
104 namespace = '{}%2F'.format(namespace) if namespace else ''
105 return cls._task_put('/api/block/image/{}%2F{}{}'.format(pool, namespace, image), kwargs)
11fdf7f2
TL
106
107 @classmethod
9f95a23c
TL
108 def flatten_image(cls, pool, namespace, image):
109 namespace = '{}%2F'.format(namespace) if namespace else ''
110 return cls._task_post('/api/block/image/{}%2F{}{}/flatten'.format(pool, namespace, image))
11fdf7f2
TL
111
112 @classmethod
1e59de90 113 def create_snapshot(cls, pool, namespace, image, snapshot, mirrorImageSnapshot):
9f95a23c
TL
114 namespace = '{}%2F'.format(namespace) if namespace else ''
115 return cls._task_post('/api/block/image/{}%2F{}{}/snap'.format(pool, namespace, image),
1e59de90 116 {'snapshot_name': snapshot, 'mirrorImageSnapshot': mirrorImageSnapshot}) # noqa E501 #pylint: disable=line-too-long
11fdf7f2
TL
117
118 @classmethod
9f95a23c
TL
119 def remove_snapshot(cls, pool, namespace, image, snapshot):
120 namespace = '{}%2F'.format(namespace) if namespace else ''
121 return cls._task_delete('/api/block/image/{}%2F{}{}/snap/{}'.format(pool, namespace, image,
122 snapshot))
11fdf7f2
TL
123
124 @classmethod
9f95a23c
TL
125 def update_snapshot(cls, pool, namespace, image, snapshot, new_name, is_protected):
126 namespace = '{}%2F'.format(namespace) if namespace else ''
127 return cls._task_put('/api/block/image/{}%2F{}{}/snap/{}'.format(pool, namespace, image,
128 snapshot),
11fdf7f2
TL
129 {'new_snap_name': new_name, 'is_protected': is_protected})
130
9f95a23c
TL
131 @classmethod
132 def rollback_snapshot(cls, pool, namespace, image, snapshot):
133 namespace = '{}%2F'.format(namespace) if namespace else ''
134 return cls._task_post('/api/block/image/{}%2F{}{}/snap/{}/rollback'.format(pool,
135 namespace,
136 image,
137 snapshot))
138
11fdf7f2
TL
139 @classmethod
140 def setUpClass(cls):
141 super(RbdTest, cls).setUpClass()
eafe8130
TL
142 cls.create_pool('rbd', 2**3, 'replicated')
143 cls.create_pool('rbd_iscsi', 2**3, 'replicated')
11fdf7f2 144
9f95a23c
TL
145 cls.create_image('rbd', None, 'img1', 2**30)
146 cls.create_image('rbd', None, 'img2', 2*2**30)
147 cls.create_image('rbd_iscsi', None, 'img1', 2**30)
148 cls.create_image('rbd_iscsi', None, 'img2', 2*2**30)
11fdf7f2
TL
149
150 osd_metadata = cls.ceph_cluster.mon_manager.get_osd_metadata()
151 cls.bluestore_support = True
152 for osd in osd_metadata:
153 if osd['osd_objectstore'] != 'bluestore':
154 cls.bluestore_support = False
155 break
156
157 @classmethod
158 def tearDownClass(cls):
159 super(RbdTest, cls).tearDownClass()
160 cls._ceph_cmd(['osd', 'pool', 'delete', 'rbd', 'rbd', '--yes-i-really-really-mean-it'])
161 cls._ceph_cmd(['osd', 'pool', 'delete', 'rbd_iscsi', 'rbd_iscsi',
162 '--yes-i-really-really-mean-it'])
163 cls._ceph_cmd(['osd', 'pool', 'delete', 'rbd_data', 'rbd_data',
164 '--yes-i-really-really-mean-it'])
165
f6b5b4d7
TL
166 def create_image_in_trash(self, pool, name, delay=0):
167 self.create_image(pool, None, name, 10240)
168 img = self._get('/api/block/image/{}%2F{}'.format(pool, name))
11fdf7f2 169
f6b5b4d7 170 self._task_post("/api/block/image/{}%2F{}/move_trash".format(pool, name),
f67539c2 171 {'delay': delay})
f6b5b4d7 172 self.assertStatus([200, 201])
11fdf7f2
TL
173 return img['id']
174
175 @classmethod
9f95a23c
TL
176 def remove_trash(cls, pool, image_id, force=False):
177 return cls._task_delete('/api/block/image/trash/{}%2F{}/?force={}'.format(
178 pool, image_id, force))
179
180 @classmethod
181 def restore_trash(cls, pool, namespace, image_id, new_image_name):
182 data = {'new_image_name': new_image_name}
183 namespace = '{}%2F'.format(namespace) if namespace else ''
184 return cls._task_post('/api/block/image/trash/{}%2F{}{}/restore'.format(pool,
185 namespace,
186 image_id), data)
187
188 @classmethod
189 def purge_trash(cls, pool):
190 return cls._task_post('/api/block/image/trash/purge?pool_name={}'.format(pool))
11fdf7f2
TL
191
192 @classmethod
193 def get_trash(cls, pool, image_id):
194 trash = cls._get('/api/block/image/trash/?pool_name={}'.format(pool))
195 if isinstance(trash, list):
f67539c2
TL
196 for trash_pool in trash:
197 for image in trash_pool['value']:
11fdf7f2
TL
198 if image['id'] == image_id:
199 return image
200
201 return None
202
203 def _validate_image(self, img, **kwargs):
204 """
205 Example of an RBD image json:
206
207 {
208 "size": 1073741824,
209 "obj_size": 4194304,
2a845540 210 "mirror_mode": "journal",
11fdf7f2
TL
211 "num_objs": 256,
212 "order": 22,
213 "block_name_prefix": "rbd_data.10ae2ae8944a",
214 "name": "img1",
215 "pool_name": "rbd",
216 "features": 61,
1e59de90 217 "primary": true,
11fdf7f2
TL
218 "features_name": ["deep-flatten", "exclusive-lock", "fast-diff", "layering",
219 "object-map"]
220 }
221 """
222 schema = JObj(sub_elems={
223 'size': JLeaf(int),
224 'obj_size': JLeaf(int),
225 'num_objs': JLeaf(int),
226 'order': JLeaf(int),
227 'block_name_prefix': JLeaf(str),
228 'name': JLeaf(str),
229 'id': JLeaf(str),
f6b5b4d7
TL
230 'unique_id': JLeaf(str),
231 'image_format': JLeaf(int),
11fdf7f2 232 'pool_name': JLeaf(str),
9f95a23c 233 'namespace': JLeaf(str, none=True),
1e59de90 234 'primary': JLeaf(bool, none=True),
11fdf7f2
TL
235 'features': JLeaf(int),
236 'features_name': JList(JLeaf(str)),
237 'stripe_count': JLeaf(int, none=True),
238 'stripe_unit': JLeaf(int, none=True),
f78120f9
TL
239 'parent': JObj(sub_elems={'pool_id': JLeaf(int),
240 'pool_name': JLeaf(str),
9f95a23c 241 'pool_namespace': JLeaf(str, none=True),
f78120f9 242 'image_id': JLeaf(str),
11fdf7f2 243 'image_name': JLeaf(str),
f78120f9
TL
244 'trash': JLeaf(bool),
245 'snap_id': JLeaf(int),
246 'snap_namespace_type': JLeaf(int),
11fdf7f2
TL
247 'snap_name': JLeaf(str)}, none=True),
248 'data_pool': JLeaf(str, none=True),
249 'snapshots': JList(JLeaf(dict)),
250 'timestamp': JLeaf(str, none=True),
251 'disk_usage': JLeaf(int, none=True),
252 'total_disk_usage': JLeaf(int, none=True),
253 'configuration': JList(JObj(sub_elems={
254 'name': JLeaf(str),
255 'source': JLeaf(int),
256 'value': JLeaf(str),
257 })),
1e59de90 258 'metadata': JObj({}, allow_unknown=True),
2a845540 259 'mirror_mode': JLeaf(str),
11fdf7f2
TL
260 })
261 self.assertSchema(img, schema)
262
263 for k, v in kwargs.items():
f78120f9
TL
264 if k == 'parent' and v is not None:
265 # check that img['parent'] contains (is a superset of) v
266 actual = {pk: img['parent'][pk]
267 for pk in v.keys() if pk in img['parent']}
268 self.assertEqual(actual, v)
269 elif isinstance(v, list):
11fdf7f2
TL
270 self.assertSetEqual(set(img[k]), set(v))
271 else:
272 self.assertEqual(img[k], v)
273
274 def _validate_snapshot(self, snap, **kwargs):
275 self.assertIn('id', snap)
276 self.assertIn('name', snap)
277 self.assertIn('is_protected', snap)
278 self.assertIn('timestamp', snap)
279 self.assertIn('size', snap)
280 self.assertIn('children', snap)
281
282 for k, v in kwargs.items():
283 if isinstance(v, list):
284 self.assertSetEqual(set(snap[k]), set(v))
285 else:
286 self.assertEqual(snap[k], v)
287
288 def _validate_snapshot_list(self, snap_list, snap_name=None, **kwargs):
289 found = False
290 for snap in snap_list:
291 self.assertIn('name', snap)
292 if snap_name and snap['name'] == snap_name:
293 found = True
294 self._validate_snapshot(snap, **kwargs)
295 break
296 if snap_name and not found:
297 self.fail("Snapshot {} not found".format(snap_name))
298
299 def test_list(self):
2a845540
TL
300 data = self._get('/api/block/image?offset=0&limit=-1&search=&sort=+name',
301 version=RbdTest.LIST_VERSION)
11fdf7f2
TL
302 self.assertStatus(200)
303 self.assertEqual(len(data), 2)
304
305 for pool_view in data:
11fdf7f2
TL
306 self.assertIsNotNone(pool_view['value'])
307 self.assertIn('pool_name', pool_view)
308 self.assertIn(pool_view['pool_name'], ['rbd', 'rbd_iscsi'])
309 image_list = pool_view['value']
310 self.assertEqual(len(image_list), 2)
311
312 for img in image_list:
313 self.assertIn('name', img)
314 self.assertIn('pool_name', img)
315 self.assertIn(img['pool_name'], ['rbd', 'rbd_iscsi'])
316 if img['name'] == 'img1':
317 self._validate_image(img, size=1073741824,
318 num_objs=256, obj_size=4194304,
319 features_name=['deep-flatten',
320 'exclusive-lock',
321 'fast-diff',
322 'layering',
323 'object-map'])
324 elif img['name'] == 'img2':
325 self._validate_image(img, size=2147483648,
326 num_objs=512, obj_size=4194304,
327 features_name=['deep-flatten',
328 'exclusive-lock',
329 'fast-diff',
330 'layering',
331 'object-map'])
332 else:
333 assert False, "Unexcepted image '{}' in result list".format(img['name'])
334
335 def test_create(self):
336 rbd_name = 'test_rbd'
9f95a23c 337 self.create_image('rbd', None, rbd_name, 10240)
11fdf7f2
TL
338 self.assertStatus(201)
339
9f95a23c 340 img = self.get_image('rbd', None, 'test_rbd')
11fdf7f2
TL
341 self.assertStatus(200)
342
343 self._validate_image(img, name=rbd_name, size=10240,
344 num_objs=1, obj_size=4194304,
345 features_name=['deep-flatten',
346 'exclusive-lock',
347 'fast-diff', 'layering',
348 'object-map'])
349
9f95a23c 350 self.remove_image('rbd', None, rbd_name)
11fdf7f2
TL
351
352 def test_create_with_configuration(self):
353 pool = 'rbd'
354 image_name = 'image_with_config'
355 size = 10240
356 configuration = {
357 'rbd_qos_bps_limit': 10240,
358 'rbd_qos_bps_burst': 10240 * 2,
359 }
360 expected = [{
361 'name': 'rbd_qos_bps_limit',
362 'source': 2,
363 'value': str(10240),
364 }, {
365 'name': 'rbd_qos_bps_burst',
366 'source': 2,
367 'value': str(10240 * 2),
368 }]
369
9f95a23c 370 self.create_image(pool, None, image_name, size, configuration=configuration)
11fdf7f2 371 self.assertStatus(201)
9f95a23c 372 img = self.get_image('rbd', None, image_name)
11fdf7f2
TL
373 self.assertStatus(200)
374 for conf in expected:
375 self.assertIn(conf, img['configuration'])
376
9f95a23c 377 self.remove_image(pool, None, image_name)
11fdf7f2 378
1e59de90
TL
379 def test_create_with_metadata(self):
380 pool = 'rbd'
381 image_name = 'image_with_meta'
382 size = 10240
383 metadata = {
384 'test1': 'test',
385 'test2': 'value',
386 }
387
388 self.create_image(pool, None, image_name, size, metadata=metadata)
389 self.assertStatus(201)
390 img = self.get_image('rbd', None, image_name)
391 self.assertStatus(200)
392 self.assertEqual(len(metadata), len(img['metadata']))
393 for meta in metadata:
394 self.assertIn(meta, img['metadata'])
395
396 self.remove_image(pool, None, image_name)
397
11fdf7f2
TL
398 def test_create_rbd_in_data_pool(self):
399 if not self.bluestore_support:
400 self.skipTest('requires bluestore cluster')
401
eafe8130 402 self.create_pool('data_pool', 2**4, 'erasure')
11fdf7f2
TL
403
404 rbd_name = 'test_rbd_in_data_pool'
9f95a23c 405 self.create_image('rbd', None, rbd_name, 10240, data_pool='data_pool')
11fdf7f2
TL
406 self.assertStatus(201)
407
9f95a23c 408 img = self.get_image('rbd', None, 'test_rbd_in_data_pool')
11fdf7f2
TL
409 self.assertStatus(200)
410
411 self._validate_image(img, name=rbd_name, size=10240,
412 num_objs=1, obj_size=4194304,
413 data_pool='data_pool',
414 features_name=['data-pool', 'deep-flatten',
415 'exclusive-lock',
416 'fast-diff', 'layering',
417 'object-map'])
418
9f95a23c 419 self.remove_image('rbd', None, rbd_name)
11fdf7f2
TL
420 self.assertStatus(204)
421 self._ceph_cmd(['osd', 'pool', 'delete', 'data_pool', 'data_pool',
422 '--yes-i-really-really-mean-it'])
423
424 def test_create_rbd_twice(self):
9f95a23c 425 res = self.create_image('rbd', None, 'test_rbd_twice', 10240)
11fdf7f2 426
9f95a23c 427 res = self.create_image('rbd', None, 'test_rbd_twice', 10240)
11fdf7f2
TL
428 self.assertStatus(400)
429 self.assertEqual(res, {"code": '17', 'status': 400, "component": "rbd",
f67539c2
TL
430 "detail": "[errno 17] RBD image already exists (error creating "
431 "image)",
11fdf7f2 432 'task': {'name': 'rbd/create',
9f95a23c 433 'metadata': {'pool_name': 'rbd', 'namespace': None,
11fdf7f2 434 'image_name': 'test_rbd_twice'}}})
9f95a23c 435 self.remove_image('rbd', None, 'test_rbd_twice')
11fdf7f2
TL
436 self.assertStatus(204)
437
438 def test_snapshots_and_clone_info(self):
1e59de90
TL
439 self.create_snapshot('rbd', None, 'img1', 'snap1', False)
440 self.create_snapshot('rbd', None, 'img1', 'snap2', False)
11fdf7f2
TL
441 self._rbd_cmd(['snap', 'protect', 'rbd/img1@snap1'])
442 self._rbd_cmd(['clone', 'rbd/img1@snap1', 'rbd_iscsi/img1_clone'])
443
9f95a23c 444 img = self.get_image('rbd', None, 'img1')
11fdf7f2
TL
445 self.assertStatus(200)
446 self._validate_image(img, name='img1', size=1073741824,
447 num_objs=256, obj_size=4194304, parent=None,
448 features_name=['deep-flatten', 'exclusive-lock',
449 'fast-diff', 'layering',
450 'object-map'])
451 for snap in img['snapshots']:
452 if snap['name'] == 'snap1':
453 self._validate_snapshot(snap, is_protected=True)
454 self.assertEqual(len(snap['children']), 1)
455 self.assertDictEqual(snap['children'][0],
456 {'pool_name': 'rbd_iscsi',
457 'image_name': 'img1_clone'})
458 elif snap['name'] == 'snap2':
459 self._validate_snapshot(snap, is_protected=False)
460
9f95a23c 461 img = self.get_image('rbd_iscsi', None, 'img1_clone')
11fdf7f2
TL
462 self.assertStatus(200)
463 self._validate_image(img, name='img1_clone', size=1073741824,
464 num_objs=256, obj_size=4194304,
9f95a23c
TL
465 parent={'pool_name': 'rbd', 'pool_namespace': '',
466 'image_name': 'img1', 'snap_name': 'snap1'},
11fdf7f2
TL
467 features_name=['deep-flatten', 'exclusive-lock',
468 'fast-diff', 'layering',
469 'object-map'])
9f95a23c 470 self.remove_image('rbd_iscsi', None, 'img1_clone')
11fdf7f2
TL
471 self.assertStatus(204)
472
473 def test_disk_usage(self):
474 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '50M', 'rbd/img2'])
1e59de90 475 self.create_snapshot('rbd', None, 'img2', 'snap1', False)
11fdf7f2 476 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '20M', 'rbd/img2'])
1e59de90 477 self.create_snapshot('rbd', None, 'img2', 'snap2', False)
11fdf7f2 478 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '10M', 'rbd/img2'])
1e59de90 479 self.create_snapshot('rbd', None, 'img2', 'snap3', False)
11fdf7f2 480 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M', 'rbd/img2'])
9f95a23c 481 img = self.get_image('rbd', None, 'img2')
11fdf7f2
TL
482 self.assertStatus(200)
483 self._validate_image(img, name='img2', size=2147483648,
484 total_disk_usage=268435456, disk_usage=67108864)
485
486 def test_delete_non_existent_image(self):
9f95a23c
TL
487 res = self.remove_image('rbd', None, 'i_dont_exist')
488 self.assertStatus(404)
489 self.assertEqual(res, {u'code': 404, "status": 404, "component": None,
490 "detail": "(404, 'Image not found')",
11fdf7f2 491 'task': {'name': 'rbd/delete',
9f95a23c 492 'metadata': {'image_spec': 'rbd/i_dont_exist'}}})
11fdf7f2
TL
493
494 def test_image_delete(self):
9f95a23c 495 self.create_image('rbd', None, 'delete_me', 2**30)
11fdf7f2 496 self.assertStatus(201)
1e59de90 497 self.create_snapshot('rbd', None, 'delete_me', 'snap1', False)
11fdf7f2 498 self.assertStatus(201)
1e59de90 499 self.create_snapshot('rbd', None, 'delete_me', 'snap2', False)
11fdf7f2
TL
500 self.assertStatus(201)
501
9f95a23c 502 img = self.get_image('rbd', None, 'delete_me')
11fdf7f2
TL
503 self.assertStatus(200)
504 self._validate_image(img, name='delete_me', size=2**30)
505 self.assertEqual(len(img['snapshots']), 2)
506
9f95a23c 507 self.remove_snapshot('rbd', None, 'delete_me', 'snap1')
11fdf7f2 508 self.assertStatus(204)
9f95a23c 509 self.remove_snapshot('rbd', None, 'delete_me', 'snap2')
11fdf7f2
TL
510 self.assertStatus(204)
511
9f95a23c 512 img = self.get_image('rbd', None, 'delete_me')
11fdf7f2
TL
513 self.assertStatus(200)
514 self._validate_image(img, name='delete_me', size=2**30)
515 self.assertEqual(len(img['snapshots']), 0)
516
9f95a23c
TL
517 self.remove_image('rbd', None, 'delete_me')
518 self.assertStatus(204)
519
520 def test_image_delete_with_snapshot(self):
521 self.create_image('rbd', None, 'delete_me', 2**30)
522 self.assertStatus(201)
1e59de90 523 self.create_snapshot('rbd', None, 'delete_me', 'snap1', False)
9f95a23c 524 self.assertStatus(201)
1e59de90 525 self.create_snapshot('rbd', None, 'delete_me', 'snap2', False)
9f95a23c
TL
526 self.assertStatus(201)
527
528 img = self.get_image('rbd', None, 'delete_me')
529 self.assertStatus(200)
530 self._validate_image(img, name='delete_me', size=2**30)
531 self.assertEqual(len(img['snapshots']), 2)
532
533 self.remove_image('rbd', None, 'delete_me')
11fdf7f2
TL
534 self.assertStatus(204)
535
536 def test_image_rename(self):
9f95a23c 537 self.create_image('rbd', None, 'edit_img', 2**30)
11fdf7f2 538 self.assertStatus(201)
9f95a23c 539 self.get_image('rbd', None, 'edit_img')
11fdf7f2 540 self.assertStatus(200)
9f95a23c 541 self.edit_image('rbd', None, 'edit_img', 'new_edit_img')
11fdf7f2 542 self.assertStatus(200)
9f95a23c 543 self.get_image('rbd', None, 'edit_img')
11fdf7f2 544 self.assertStatus(404)
9f95a23c 545 self.get_image('rbd', None, 'new_edit_img')
11fdf7f2 546 self.assertStatus(200)
9f95a23c 547 self.remove_image('rbd', None, 'new_edit_img')
11fdf7f2
TL
548 self.assertStatus(204)
549
550 def test_image_resize(self):
9f95a23c 551 self.create_image('rbd', None, 'edit_img', 2**30)
11fdf7f2 552 self.assertStatus(201)
9f95a23c 553 img = self.get_image('rbd', None, 'edit_img')
11fdf7f2
TL
554 self.assertStatus(200)
555 self._validate_image(img, size=2**30)
9f95a23c 556 self.edit_image('rbd', None, 'edit_img', size=2*2**30)
11fdf7f2 557 self.assertStatus(200)
9f95a23c 558 img = self.get_image('rbd', None, 'edit_img')
11fdf7f2
TL
559 self.assertStatus(200)
560 self._validate_image(img, size=2*2**30)
9f95a23c 561 self.remove_image('rbd', None, 'edit_img')
11fdf7f2
TL
562 self.assertStatus(204)
563
564 def test_image_change_features(self):
9f95a23c 565 self.create_image('rbd', None, 'edit_img', 2**30, features=["layering"])
11fdf7f2 566 self.assertStatus(201)
9f95a23c 567 img = self.get_image('rbd', None, 'edit_img')
11fdf7f2
TL
568 self.assertStatus(200)
569 self._validate_image(img, features_name=["layering"])
9f95a23c 570 self.edit_image('rbd', None, 'edit_img',
11fdf7f2 571 features=["fast-diff", "object-map", "exclusive-lock"])
81eedcae 572 self.assertStatus(200)
9f95a23c 573 img = self.get_image('rbd', None, 'edit_img')
11fdf7f2
TL
574 self.assertStatus(200)
575 self._validate_image(img, features_name=['exclusive-lock',
576 'fast-diff', 'layering',
577 'object-map'])
9f95a23c 578 self.edit_image('rbd', None, 'edit_img',
11fdf7f2 579 features=["journaling", "exclusive-lock"])
81eedcae 580 self.assertStatus(200)
9f95a23c 581 img = self.get_image('rbd', None, 'edit_img')
11fdf7f2
TL
582 self.assertStatus(200)
583 self._validate_image(img, features_name=['exclusive-lock',
584 'journaling', 'layering'])
9f95a23c 585 self.remove_image('rbd', None, 'edit_img')
11fdf7f2
TL
586 self.assertStatus(204)
587
588 def test_image_change_config(self):
589 pool = 'rbd'
590 image = 'image_with_config'
591 initial_conf = {
592 'rbd_qos_bps_limit': 10240,
593 'rbd_qos_write_iops_limit': None
594 }
595 initial_expect = [{
596 'name': 'rbd_qos_bps_limit',
597 'source': 2,
598 'value': '10240',
599 }, {
600 'name': 'rbd_qos_write_iops_limit',
601 'source': 0,
602 'value': '0',
603 }]
604 new_conf = {
605 'rbd_qos_bps_limit': 0,
606 'rbd_qos_bps_burst': 20480,
607 'rbd_qos_write_iops_limit': None
608 }
609 new_expect = [{
610 'name': 'rbd_qos_bps_limit',
611 'source': 2,
612 'value': '0',
613 }, {
614 'name': 'rbd_qos_bps_burst',
615 'source': 2,
616 'value': '20480',
617 }, {
618 'name': 'rbd_qos_write_iops_limit',
619 'source': 0,
620 'value': '0',
621 }]
622
9f95a23c 623 self.create_image(pool, None, image, 2**30, configuration=initial_conf)
11fdf7f2 624 self.assertStatus(201)
9f95a23c 625 img = self.get_image(pool, None, image)
11fdf7f2
TL
626 self.assertStatus(200)
627 for conf in initial_expect:
628 self.assertIn(conf, img['configuration'])
629
9f95a23c
TL
630 self.edit_image(pool, None, image, configuration=new_conf)
631 img = self.get_image(pool, None, image)
11fdf7f2
TL
632 self.assertStatus(200)
633 for conf in new_expect:
634 self.assertIn(conf, img['configuration'])
635
9f95a23c 636 self.remove_image(pool, None, image)
11fdf7f2
TL
637 self.assertStatus(204)
638
1e59de90
TL
639 def test_image_change_meta(self):
640 pool = 'rbd'
641 image = 'image_with_meta'
642 initial_meta = {
643 'test1': 'test',
644 'test2': 'value',
645 'test3': None,
646 }
647 initial_expect = {
648 'test1': 'test',
649 'test2': 'value',
650 }
651 new_meta = {
652 'test1': None,
653 'test2': 'new_value',
654 'test3': 'value',
655 'test4': None,
656 }
657 new_expect = {
658 'test2': 'new_value',
659 'test3': 'value',
660 }
661
662 self.create_image(pool, None, image, 2**30, metadata=initial_meta)
663 self.assertStatus(201)
664 img = self.get_image(pool, None, image)
665 self.assertStatus(200)
666 self.assertEqual(len(initial_expect), len(img['metadata']))
667 for meta in initial_expect:
668 self.assertIn(meta, img['metadata'])
669
670 self.edit_image(pool, None, image, metadata=new_meta)
671 img = self.get_image(pool, None, image)
672 self.assertStatus(200)
673 self.assertEqual(len(new_expect), len(img['metadata']))
674 for meta in new_expect:
675 self.assertIn(meta, img['metadata'])
676
677 self.remove_image(pool, None, image)
678 self.assertStatus(204)
679
11fdf7f2 680 def test_update_snapshot(self):
1e59de90 681 self.create_snapshot('rbd', None, 'img1', 'snap5', False)
11fdf7f2 682 self.assertStatus(201)
9f95a23c 683 img = self.get_image('rbd', None, 'img1')
11fdf7f2
TL
684 self._validate_snapshot_list(img['snapshots'], 'snap5', is_protected=False)
685
9f95a23c 686 self.update_snapshot('rbd', None, 'img1', 'snap5', 'snap6', None)
11fdf7f2 687 self.assertStatus(200)
9f95a23c 688 img = self.get_image('rbd', None, 'img1')
11fdf7f2
TL
689 self._validate_snapshot_list(img['snapshots'], 'snap6', is_protected=False)
690
9f95a23c 691 self.update_snapshot('rbd', None, 'img1', 'snap6', None, True)
11fdf7f2 692 self.assertStatus(200)
9f95a23c 693 img = self.get_image('rbd', None, 'img1')
11fdf7f2
TL
694 self._validate_snapshot_list(img['snapshots'], 'snap6', is_protected=True)
695
9f95a23c 696 self.update_snapshot('rbd', None, 'img1', 'snap6', 'snap5', False)
11fdf7f2 697 self.assertStatus(200)
9f95a23c 698 img = self.get_image('rbd', None, 'img1')
11fdf7f2
TL
699 self._validate_snapshot_list(img['snapshots'], 'snap5', is_protected=False)
700
9f95a23c 701 self.remove_snapshot('rbd', None, 'img1', 'snap5')
11fdf7f2
TL
702 self.assertStatus(204)
703
704 def test_snapshot_rollback(self):
9f95a23c 705 self.create_image('rbd', None, 'rollback_img', 2**30,
11fdf7f2
TL
706 features=["layering", "exclusive-lock", "fast-diff",
707 "object-map"])
708 self.assertStatus(201)
1e59de90 709 self.create_snapshot('rbd', None, 'rollback_img', 'snap1', False)
11fdf7f2
TL
710 self.assertStatus(201)
711
9f95a23c 712 img = self.get_image('rbd', None, 'rollback_img')
11fdf7f2
TL
713 self.assertStatus(200)
714 self.assertEqual(img['disk_usage'], 0)
715
716 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M',
717 'rbd/rollback_img'])
718
9f95a23c 719 img = self.get_image('rbd', None, 'rollback_img')
11fdf7f2
TL
720 self.assertStatus(200)
721 self.assertGreater(img['disk_usage'], 0)
722
9f95a23c 723 self.rollback_snapshot('rbd', None, 'rollback_img', 'snap1')
11fdf7f2
TL
724 self.assertStatus([201, 200])
725
9f95a23c 726 img = self.get_image('rbd', None, 'rollback_img')
11fdf7f2
TL
727 self.assertStatus(200)
728 self.assertEqual(img['disk_usage'], 0)
729
9f95a23c 730 self.remove_snapshot('rbd', None, 'rollback_img', 'snap1')
11fdf7f2 731 self.assertStatus(204)
9f95a23c 732 self.remove_image('rbd', None, 'rollback_img')
11fdf7f2
TL
733 self.assertStatus(204)
734
735 def test_clone(self):
1e59de90
TL
736 self.create_image('rbd', None, 'cimg', 2**30, features=["layering"],
737 metadata={'key1': 'val1'})
11fdf7f2 738 self.assertStatus(201)
1e59de90 739 self.create_snapshot('rbd', None, 'cimg', 'snap1', False)
11fdf7f2 740 self.assertStatus(201)
9f95a23c 741 self.update_snapshot('rbd', None, 'cimg', 'snap1', None, True)
11fdf7f2 742 self.assertStatus(200)
9f95a23c 743 self.clone_image('rbd', None, 'cimg', 'snap1', 'rbd', None, 'cimg-clone',
11fdf7f2 744 features=["layering", "exclusive-lock", "fast-diff",
1e59de90
TL
745 "object-map"],
746 metadata={'key1': None, 'key2': 'val2'})
11fdf7f2
TL
747 self.assertStatus([200, 201])
748
9f95a23c 749 img = self.get_image('rbd', None, 'cimg-clone')
11fdf7f2
TL
750 self.assertStatus(200)
751 self._validate_image(img, features_name=['exclusive-lock',
752 'fast-diff', 'layering',
753 'object-map'],
9f95a23c 754 parent={'pool_name': 'rbd', 'pool_namespace': '',
1e59de90
TL
755 'image_name': 'cimg', 'snap_name': 'snap1'},
756 metadata={'key2': 'val2'})
11fdf7f2 757
9f95a23c 758 res = self.remove_image('rbd', None, 'cimg')
11fdf7f2
TL
759 self.assertStatus(400)
760 self.assertIn('code', res)
9f95a23c 761 self.assertEqual(res['code'], '16')
11fdf7f2 762
9f95a23c 763 self.remove_image('rbd', None, 'cimg-clone')
11fdf7f2 764 self.assertStatus(204)
9f95a23c 765 self.remove_image('rbd', None, 'cimg')
11fdf7f2
TL
766 self.assertStatus(204)
767
768 def test_copy(self):
9f95a23c 769 self.create_image('rbd', None, 'coimg', 2**30,
11fdf7f2 770 features=["layering", "exclusive-lock", "fast-diff",
1e59de90
TL
771 "object-map"],
772 metadata={'key1': 'val1'})
11fdf7f2
TL
773 self.assertStatus(201)
774
775 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M',
776 'rbd/coimg'])
777
9f95a23c 778 self.copy_image('rbd', None, 'coimg', 'rbd_iscsi', None, 'coimg-copy',
11fdf7f2 779 features=["layering", "fast-diff", "exclusive-lock",
1e59de90
TL
780 "object-map"],
781 metadata={'key1': None, 'key2': 'val2'})
11fdf7f2
TL
782 self.assertStatus([200, 201])
783
9f95a23c 784 img = self.get_image('rbd', None, 'coimg')
11fdf7f2
TL
785 self.assertStatus(200)
786 self._validate_image(img, features_name=['layering', 'exclusive-lock',
1e59de90
TL
787 'fast-diff', 'object-map'],
788 metadata={'key1': 'val1'})
11fdf7f2 789
9f95a23c 790 img_copy = self.get_image('rbd_iscsi', None, 'coimg-copy')
11fdf7f2
TL
791 self._validate_image(img_copy, features_name=['exclusive-lock',
792 'fast-diff', 'layering',
793 'object-map'],
1e59de90 794 metadata={'key2': 'val2'},
11fdf7f2
TL
795 disk_usage=img['disk_usage'])
796
9f95a23c 797 self.remove_image('rbd', None, 'coimg')
11fdf7f2 798 self.assertStatus(204)
9f95a23c 799 self.remove_image('rbd_iscsi', None, 'coimg-copy')
11fdf7f2
TL
800 self.assertStatus(204)
801
802 def test_flatten(self):
1e59de90 803 self.create_snapshot('rbd', None, 'img1', 'snapf', False)
9f95a23c
TL
804 self.update_snapshot('rbd', None, 'img1', 'snapf', None, True)
805 self.clone_image('rbd', None, 'img1', 'snapf', 'rbd_iscsi', None, 'img1_snapf_clone')
11fdf7f2 806
9f95a23c 807 img = self.get_image('rbd_iscsi', None, 'img1_snapf_clone')
11fdf7f2
TL
808 self.assertStatus(200)
809 self.assertIsNotNone(img['parent'])
810
9f95a23c 811 self.flatten_image('rbd_iscsi', None, 'img1_snapf_clone')
11fdf7f2
TL
812 self.assertStatus([200, 201])
813
9f95a23c 814 img = self.get_image('rbd_iscsi', None, 'img1_snapf_clone')
11fdf7f2
TL
815 self.assertStatus(200)
816 self.assertIsNone(img['parent'])
817
9f95a23c
TL
818 self.update_snapshot('rbd', None, 'img1', 'snapf', None, False)
819 self.remove_snapshot('rbd', None, 'img1', 'snapf')
11fdf7f2
TL
820 self.assertStatus(204)
821
9f95a23c 822 self.remove_image('rbd_iscsi', None, 'img1_snapf_clone')
11fdf7f2
TL
823 self.assertStatus(204)
824
825 def test_default_features(self):
826 default_features = self._get('/api/block/image/default_features')
9f95a23c
TL
827 self.assertEqual(default_features, [
828 'deep-flatten', 'exclusive-lock', 'fast-diff', 'layering', 'object-map'])
11fdf7f2 829
f67539c2
TL
830 def test_clone_format_version(self):
831 config_name = 'rbd_default_clone_format'
832
833 def _get_config_by_name(conf_name):
834 data = self._get('/api/cluster_conf/{}'.format(conf_name))
835 if 'value' in data:
836 return data['value']
837 return None
838
839 # with rbd_default_clone_format = auto
840 clone_format_version = self._get('/api/block/image/clone_format_version')
841 self.assertEqual(clone_format_version, 1)
842 self.assertStatus(200)
843
844 # with rbd_default_clone_format = 1
845 value = [{'section': "global", 'value': "1"}]
846 self._post('/api/cluster_conf', {
847 'name': config_name,
848 'value': value
849 })
850 self.wait_until_equal(
851 lambda: _get_config_by_name(config_name),
852 value,
853 timeout=60)
854 clone_format_version = self._get('/api/block/image/clone_format_version')
855 self.assertEqual(clone_format_version, 1)
856 self.assertStatus(200)
857
858 # with rbd_default_clone_format = 2
859 value = [{'section': "global", 'value': "2"}]
860 self._post('/api/cluster_conf', {
861 'name': config_name,
862 'value': value
863 })
864 self.wait_until_equal(
865 lambda: _get_config_by_name(config_name),
866 value,
867 timeout=60)
868 clone_format_version = self._get('/api/block/image/clone_format_version')
869 self.assertEqual(clone_format_version, 2)
870 self.assertStatus(200)
871
872 value = []
873 self._post('/api/cluster_conf', {
874 'name': config_name,
875 'value': value
876 })
877 self.wait_until_equal(
878 lambda: _get_config_by_name(config_name),
879 None,
880 timeout=60)
881
9f95a23c
TL
882 def test_image_with_namespace(self):
883 self.create_namespace('rbd', 'ns')
884 self.create_image('rbd', 'ns', 'test', 10240)
11fdf7f2
TL
885 self.assertStatus(201)
886
9f95a23c 887 img = self.get_image('rbd', 'ns', 'test')
11fdf7f2
TL
888 self.assertStatus(200)
889
9f95a23c
TL
890 self._validate_image(img, name='test', size=10240,
891 pool_name='rbd', namespace='ns',
11fdf7f2
TL
892 num_objs=1, obj_size=4194304,
893 features_name=['deep-flatten',
894 'exclusive-lock',
895 'fast-diff', 'layering',
896 'object-map'])
897
9f95a23c
TL
898 self.remove_image('rbd', 'ns', 'test')
899 self.remove_namespace('rbd', 'ns')
11fdf7f2
TL
900
901 def test_move_image_to_trash(self):
f67539c2 902 img_id = self.create_image_in_trash('rbd', 'test_rbd')
11fdf7f2 903
9f95a23c 904 self.get_image('rbd', None, 'test_rbd')
11fdf7f2
TL
905 self.assertStatus(404)
906
907 time.sleep(1)
908
f67539c2 909 image = self.get_trash('rbd', img_id)
11fdf7f2
TL
910 self.assertIsNotNone(image)
911
f67539c2 912 self.remove_trash('rbd', img_id)
11fdf7f2
TL
913
914 def test_list_trash(self):
f67539c2 915 img_id = self.create_image_in_trash('rbd', 'test_rbd', 0)
11fdf7f2
TL
916 data = self._get('/api/block/image/trash/?pool_name={}'.format('rbd'))
917 self.assertStatus(200)
918 self.assertIsInstance(data, list)
919 self.assertIsNotNone(data)
920
f67539c2 921 self.remove_trash('rbd', img_id)
11fdf7f2
TL
922 self.assertStatus(204)
923
924 def test_restore_trash(self):
f67539c2 925 img_id = self.create_image_in_trash('rbd', 'test_rbd')
11fdf7f2 926
f67539c2 927 self.restore_trash('rbd', None, img_id, 'test_rbd')
11fdf7f2 928
9f95a23c 929 self.get_image('rbd', None, 'test_rbd')
11fdf7f2
TL
930 self.assertStatus(200)
931
f67539c2 932 image = self.get_trash('rbd', img_id)
11fdf7f2
TL
933 self.assertIsNone(image)
934
9f95a23c 935 self.remove_image('rbd', None, 'test_rbd')
11fdf7f2
TL
936
937 def test_remove_expired_trash(self):
f67539c2
TL
938 img_id = self.create_image_in_trash('rbd', 'test_rbd', 0)
939 self.remove_trash('rbd', img_id, False)
11fdf7f2
TL
940 self.assertStatus(204)
941
f67539c2 942 image = self.get_trash('rbd', img_id)
11fdf7f2
TL
943 self.assertIsNone(image)
944
945 def test_remove_not_expired_trash(self):
f67539c2
TL
946 img_id = self.create_image_in_trash('rbd', 'test_rbd', 9999)
947 self.remove_trash('rbd', img_id, False)
11fdf7f2
TL
948 self.assertStatus(400)
949
950 time.sleep(1)
951
f67539c2 952 image = self.get_trash('rbd', img_id)
11fdf7f2
TL
953 self.assertIsNotNone(image)
954
f67539c2 955 self.remove_trash('rbd', img_id, True)
11fdf7f2
TL
956
957 def test_remove_not_expired_trash_with_force(self):
f67539c2
TL
958 img_id = self.create_image_in_trash('rbd', 'test_rbd', 9999)
959 self.remove_trash('rbd', img_id, True)
11fdf7f2
TL
960 self.assertStatus(204)
961
f67539c2 962 image = self.get_trash('rbd', img_id)
11fdf7f2
TL
963 self.assertIsNone(image)
964
965 def test_purge_trash(self):
966 id_expired = self.create_image_in_trash('rbd', 'test_rbd_expired', 0)
967 id_not_expired = self.create_image_in_trash('rbd', 'test_rbd', 9999)
968
969 time.sleep(1)
970
9f95a23c 971 self.purge_trash('rbd')
801d1391 972 self.assertStatus([200, 201])
11fdf7f2
TL
973
974 time.sleep(1)
975
976 trash_not_expired = self.get_trash('rbd', id_not_expired)
977 self.assertIsNotNone(trash_not_expired)
978
801d1391 979 self.wait_until_equal(lambda: self.get_trash('rbd', id_expired), None, 60)
9f95a23c
TL
980
981 def test_list_namespaces(self):
982 self.create_namespace('rbd', 'ns')
983
984 namespaces = self._get('/api/block/pool/rbd/namespace')
985 self.assertStatus(200)
986 self.assertEqual(len(namespaces), 1)
987
988 self.remove_namespace('rbd', 'ns')