]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/mgr/dashboard/test_rbd.py
1 # -*- coding: utf-8 -*-
2 # pylint: disable=too-many-public-methods
4 from __future__
import absolute_import
8 from .helper
import DashboardTestCase
, JLeaf
, JList
, JObj
11 class RbdTest(DashboardTestCase
):
12 AUTH_ROLES
= ['pool-manager', 'block-manager', 'cluster-manager']
15 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['create', 'update', 'delete']}])
16 def test_read_access_permissions(self
):
17 self
._get
('/api/block/image?offset=0&limit=-1&search=&sort=+name',
18 version
=RbdTest
.LIST_VERSION
)
19 self
.assertStatus(403)
20 self
.get_image('pool', None, 'image')
21 self
.assertStatus(403)
23 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'update', 'delete']}])
24 def test_create_access_permissions(self
):
25 self
.create_image('pool', None, 'name', 0)
26 self
.assertStatus(403)
27 self
.create_snapshot('pool', None, 'image', 'snapshot')
28 self
.assertStatus(403)
29 self
.copy_image('src_pool', None, 'src_image', 'dest_pool', None, 'dest_image')
30 self
.assertStatus(403)
31 self
.clone_image('parent_pool', None, 'parent_image', 'parent_snap', 'pool', None, 'name')
32 self
.assertStatus(403)
34 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'create', 'delete']}])
35 def test_update_access_permissions(self
):
36 self
.edit_image('pool', None, 'image')
37 self
.assertStatus(403)
38 self
.update_snapshot('pool', None, 'image', 'snapshot', None, None)
39 self
.assertStatus(403)
40 self
.rollback_snapshot('rbd', None, 'rollback_img', 'snap1')
41 self
.assertStatus(403)
42 self
.flatten_image('pool', None, 'image')
43 self
.assertStatus(403)
45 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'create', 'update']}])
46 def test_delete_access_permissions(self
):
47 self
.remove_image('pool', None, 'image')
48 self
.assertStatus(403)
49 self
.remove_snapshot('pool', None, 'image', 'snapshot')
50 self
.assertStatus(403)
53 def create_namespace(cls
, pool
, namespace
):
54 data
= {'namespace': namespace
}
55 return cls
._post
('/api/block/pool/{}/namespace'.format(pool
), data
)
58 def remove_namespace(cls
, pool
, namespace
):
59 return cls
._delete
('/api/block/pool/{}/namespace/{}'.format(pool
, namespace
))
62 def create_image(cls
, pool
, namespace
, name
, size
, **kwargs
):
63 data
= {'name': name
, 'pool_name': pool
, 'namespace': namespace
, 'size': size
}
65 return cls
._task
_post
('/api/block/image', data
)
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
))
73 def clone_image(cls
, parent_pool
, parent_namespace
, parent_image
, parent_snap
, pool
, namespace
,
75 # pylint: disable=too-many-arguments
76 data
= {'child_image_name': name
, 'child_namespace': namespace
, 'child_pool_name': pool
}
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
),
84 def copy_image(cls
, src_pool
, src_namespace
, src_image
, dest_pool
, dest_namespace
, dest_image
,
86 # pylint: disable=too-many-arguments
87 data
= {'dest_image_name': dest_image
,
88 'dest_pool_name': dest_pool
,
89 'dest_namespace': dest_namespace
}
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
)
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
))
100 # pylint: disable=too-many-arguments
102 def edit_image(cls
, pool
, namespace
, image
, name
=None, size
=None, features
=None, **kwargs
):
103 kwargs
.update({'name': name
, 'size': size
, 'features': features
})
104 namespace
= '{}%2F'.format(namespace
) if namespace
else ''
105 return cls
._task
_put
('/api/block/image/{}%2F{}{}'.format(pool
, namespace
, image
), kwargs
)
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
))
113 def create_snapshot(cls
, pool
, namespace
, image
, snapshot
):
114 namespace
= '{}%2F'.format(namespace
) if namespace
else ''
115 return cls
._task
_post
('/api/block/image/{}%2F{}{}/snap'.format(pool
, namespace
, image
),
116 {'snapshot_name': snapshot
})
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
,
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
,
129 {'new_snap_name': new_name
, 'is_protected': is_protected
})
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
,
141 super(RbdTest
, cls
).setUpClass()
142 cls
.create_pool('rbd', 2**3, 'replicated')
143 cls
.create_pool('rbd_iscsi', 2**3, 'replicated')
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)
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
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'])
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
))
170 self
._task
_post
("/api/block/image/{}%2F{}/move_trash".format(pool
, name
),
172 self
.assertStatus([200, 201])
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
))
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
,
189 def purge_trash(cls
, pool
):
190 return cls
._task
_post
('/api/block/image/trash/purge?pool_name={}'.format(pool
))
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):
196 for trash_pool
in trash
:
197 for image
in trash_pool
['value']:
198 if image
['id'] == image_id
:
203 def _validate_image(self
, img
, **kwargs
):
205 Example of an RBD image json:
210 "mirror_mode": "journal",
213 "block_name_prefix": "rbd_data.10ae2ae8944a",
217 "features_name": ["deep-flatten", "exclusive-lock", "fast-diff", "layering",
221 schema
= JObj(sub_elems
={
223 'obj_size': JLeaf(int),
224 'num_objs': JLeaf(int),
226 'block_name_prefix': JLeaf(str),
229 'unique_id': JLeaf(str),
230 'image_format': JLeaf(int),
231 'pool_name': JLeaf(str),
232 'namespace': JLeaf(str, none
=True),
233 'features': JLeaf(int),
234 'features_name': JList(JLeaf(str)),
235 'stripe_count': JLeaf(int, none
=True),
236 'stripe_unit': JLeaf(int, none
=True),
237 'parent': JObj(sub_elems
={'pool_name': JLeaf(str),
238 'pool_namespace': JLeaf(str, none
=True),
239 'image_name': JLeaf(str),
240 'snap_name': JLeaf(str)}, none
=True),
241 'data_pool': JLeaf(str, none
=True),
242 'snapshots': JList(JLeaf(dict)),
243 'timestamp': JLeaf(str, none
=True),
244 'disk_usage': JLeaf(int, none
=True),
245 'total_disk_usage': JLeaf(int, none
=True),
246 'configuration': JList(JObj(sub_elems
={
248 'source': JLeaf(int),
251 'mirror_mode': JLeaf(str),
253 self
.assertSchema(img
, schema
)
255 for k
, v
in kwargs
.items():
256 if isinstance(v
, list):
257 self
.assertSetEqual(set(img
[k
]), set(v
))
259 self
.assertEqual(img
[k
], v
)
261 def _validate_snapshot(self
, snap
, **kwargs
):
262 self
.assertIn('id', snap
)
263 self
.assertIn('name', snap
)
264 self
.assertIn('is_protected', snap
)
265 self
.assertIn('timestamp', snap
)
266 self
.assertIn('size', snap
)
267 self
.assertIn('children', snap
)
269 for k
, v
in kwargs
.items():
270 if isinstance(v
, list):
271 self
.assertSetEqual(set(snap
[k
]), set(v
))
273 self
.assertEqual(snap
[k
], v
)
275 def _validate_snapshot_list(self
, snap_list
, snap_name
=None, **kwargs
):
277 for snap
in snap_list
:
278 self
.assertIn('name', snap
)
279 if snap_name
and snap
['name'] == snap_name
:
281 self
._validate
_snapshot
(snap
, **kwargs
)
283 if snap_name
and not found
:
284 self
.fail("Snapshot {} not found".format(snap_name
))
287 data
= self
._get
('/api/block/image?offset=0&limit=-1&search=&sort=+name',
288 version
=RbdTest
.LIST_VERSION
)
289 self
.assertStatus(200)
290 self
.assertEqual(len(data
), 2)
292 for pool_view
in data
:
293 self
.assertIsNotNone(pool_view
['value'])
294 self
.assertIn('pool_name', pool_view
)
295 self
.assertIn(pool_view
['pool_name'], ['rbd', 'rbd_iscsi'])
296 image_list
= pool_view
['value']
297 self
.assertEqual(len(image_list
), 2)
299 for img
in image_list
:
300 self
.assertIn('name', img
)
301 self
.assertIn('pool_name', img
)
302 self
.assertIn(img
['pool_name'], ['rbd', 'rbd_iscsi'])
303 if img
['name'] == 'img1':
304 self
._validate
_image
(img
, size
=1073741824,
305 num_objs
=256, obj_size
=4194304,
306 features_name
=['deep-flatten',
311 elif img
['name'] == 'img2':
312 self
._validate
_image
(img
, size
=2147483648,
313 num_objs
=512, obj_size
=4194304,
314 features_name
=['deep-flatten',
320 assert False, "Unexcepted image '{}' in result list".format(img
['name'])
322 def test_create(self
):
323 rbd_name
= 'test_rbd'
324 self
.create_image('rbd', None, rbd_name
, 10240)
325 self
.assertStatus(201)
327 img
= self
.get_image('rbd', None, 'test_rbd')
328 self
.assertStatus(200)
330 self
._validate
_image
(img
, name
=rbd_name
, size
=10240,
331 num_objs
=1, obj_size
=4194304,
332 features_name
=['deep-flatten',
334 'fast-diff', 'layering',
337 self
.remove_image('rbd', None, rbd_name
)
339 def test_create_with_configuration(self
):
341 image_name
= 'image_with_config'
344 'rbd_qos_bps_limit': 10240,
345 'rbd_qos_bps_burst': 10240 * 2,
348 'name': 'rbd_qos_bps_limit',
352 'name': 'rbd_qos_bps_burst',
354 'value': str(10240 * 2),
357 self
.create_image(pool
, None, image_name
, size
, configuration
=configuration
)
358 self
.assertStatus(201)
359 img
= self
.get_image('rbd', None, image_name
)
360 self
.assertStatus(200)
361 for conf
in expected
:
362 self
.assertIn(conf
, img
['configuration'])
364 self
.remove_image(pool
, None, image_name
)
366 def test_create_rbd_in_data_pool(self
):
367 if not self
.bluestore_support
:
368 self
.skipTest('requires bluestore cluster')
370 self
.create_pool('data_pool', 2**4, 'erasure')
372 rbd_name
= 'test_rbd_in_data_pool'
373 self
.create_image('rbd', None, rbd_name
, 10240, data_pool
='data_pool')
374 self
.assertStatus(201)
376 img
= self
.get_image('rbd', None, 'test_rbd_in_data_pool')
377 self
.assertStatus(200)
379 self
._validate
_image
(img
, name
=rbd_name
, size
=10240,
380 num_objs
=1, obj_size
=4194304,
381 data_pool
='data_pool',
382 features_name
=['data-pool', 'deep-flatten',
384 'fast-diff', 'layering',
387 self
.remove_image('rbd', None, rbd_name
)
388 self
.assertStatus(204)
389 self
._ceph
_cmd
(['osd', 'pool', 'delete', 'data_pool', 'data_pool',
390 '--yes-i-really-really-mean-it'])
392 def test_create_rbd_twice(self
):
393 res
= self
.create_image('rbd', None, 'test_rbd_twice', 10240)
395 res
= self
.create_image('rbd', None, 'test_rbd_twice', 10240)
396 self
.assertStatus(400)
397 self
.assertEqual(res
, {"code": '17', 'status': 400, "component": "rbd",
398 "detail": "[errno 17] RBD image already exists (error creating "
400 'task': {'name': 'rbd/create',
401 'metadata': {'pool_name': 'rbd', 'namespace': None,
402 'image_name': 'test_rbd_twice'}}})
403 self
.remove_image('rbd', None, 'test_rbd_twice')
404 self
.assertStatus(204)
406 def test_snapshots_and_clone_info(self
):
407 self
.create_snapshot('rbd', None, 'img1', 'snap1')
408 self
.create_snapshot('rbd', None, 'img1', 'snap2')
409 self
._rbd
_cmd
(['snap', 'protect', 'rbd/img1@snap1'])
410 self
._rbd
_cmd
(['clone', 'rbd/img1@snap1', 'rbd_iscsi/img1_clone'])
412 img
= self
.get_image('rbd', None, 'img1')
413 self
.assertStatus(200)
414 self
._validate
_image
(img
, name
='img1', size
=1073741824,
415 num_objs
=256, obj_size
=4194304, parent
=None,
416 features_name
=['deep-flatten', 'exclusive-lock',
417 'fast-diff', 'layering',
419 for snap
in img
['snapshots']:
420 if snap
['name'] == 'snap1':
421 self
._validate
_snapshot
(snap
, is_protected
=True)
422 self
.assertEqual(len(snap
['children']), 1)
423 self
.assertDictEqual(snap
['children'][0],
424 {'pool_name': 'rbd_iscsi',
425 'image_name': 'img1_clone'})
426 elif snap
['name'] == 'snap2':
427 self
._validate
_snapshot
(snap
, is_protected
=False)
429 img
= self
.get_image('rbd_iscsi', None, 'img1_clone')
430 self
.assertStatus(200)
431 self
._validate
_image
(img
, name
='img1_clone', size
=1073741824,
432 num_objs
=256, obj_size
=4194304,
433 parent
={'pool_name': 'rbd', 'pool_namespace': '',
434 'image_name': 'img1', 'snap_name': 'snap1'},
435 features_name
=['deep-flatten', 'exclusive-lock',
436 'fast-diff', 'layering',
438 self
.remove_image('rbd_iscsi', None, 'img1_clone')
439 self
.assertStatus(204)
441 def test_disk_usage(self
):
442 self
._rbd
_cmd
(['bench', '--io-type', 'write', '--io-total', '50M', 'rbd/img2'])
443 self
.create_snapshot('rbd', None, 'img2', 'snap1')
444 self
._rbd
_cmd
(['bench', '--io-type', 'write', '--io-total', '20M', 'rbd/img2'])
445 self
.create_snapshot('rbd', None, 'img2', 'snap2')
446 self
._rbd
_cmd
(['bench', '--io-type', 'write', '--io-total', '10M', 'rbd/img2'])
447 self
.create_snapshot('rbd', None, 'img2', 'snap3')
448 self
._rbd
_cmd
(['bench', '--io-type', 'write', '--io-total', '5M', 'rbd/img2'])
449 img
= self
.get_image('rbd', None, 'img2')
450 self
.assertStatus(200)
451 self
._validate
_image
(img
, name
='img2', size
=2147483648,
452 total_disk_usage
=268435456, disk_usage
=67108864)
454 def test_delete_non_existent_image(self
):
455 res
= self
.remove_image('rbd', None, 'i_dont_exist')
456 self
.assertStatus(404)
457 self
.assertEqual(res
, {u
'code': 404, "status": 404, "component": None,
458 "detail": "(404, 'Image not found')",
459 'task': {'name': 'rbd/delete',
460 'metadata': {'image_spec': 'rbd/i_dont_exist'}}})
462 def test_image_delete(self
):
463 self
.create_image('rbd', None, 'delete_me', 2**30)
464 self
.assertStatus(201)
465 self
.create_snapshot('rbd', None, 'delete_me', 'snap1')
466 self
.assertStatus(201)
467 self
.create_snapshot('rbd', None, 'delete_me', 'snap2')
468 self
.assertStatus(201)
470 img
= self
.get_image('rbd', None, 'delete_me')
471 self
.assertStatus(200)
472 self
._validate
_image
(img
, name
='delete_me', size
=2**30)
473 self
.assertEqual(len(img
['snapshots']), 2)
475 self
.remove_snapshot('rbd', None, 'delete_me', 'snap1')
476 self
.assertStatus(204)
477 self
.remove_snapshot('rbd', None, 'delete_me', 'snap2')
478 self
.assertStatus(204)
480 img
= self
.get_image('rbd', None, 'delete_me')
481 self
.assertStatus(200)
482 self
._validate
_image
(img
, name
='delete_me', size
=2**30)
483 self
.assertEqual(len(img
['snapshots']), 0)
485 self
.remove_image('rbd', None, 'delete_me')
486 self
.assertStatus(204)
488 def test_image_delete_with_snapshot(self
):
489 self
.create_image('rbd', None, 'delete_me', 2**30)
490 self
.assertStatus(201)
491 self
.create_snapshot('rbd', None, 'delete_me', 'snap1')
492 self
.assertStatus(201)
493 self
.create_snapshot('rbd', None, 'delete_me', 'snap2')
494 self
.assertStatus(201)
496 img
= self
.get_image('rbd', None, 'delete_me')
497 self
.assertStatus(200)
498 self
._validate
_image
(img
, name
='delete_me', size
=2**30)
499 self
.assertEqual(len(img
['snapshots']), 2)
501 self
.remove_image('rbd', None, 'delete_me')
502 self
.assertStatus(204)
504 def test_image_rename(self
):
505 self
.create_image('rbd', None, 'edit_img', 2**30)
506 self
.assertStatus(201)
507 self
.get_image('rbd', None, 'edit_img')
508 self
.assertStatus(200)
509 self
.edit_image('rbd', None, 'edit_img', 'new_edit_img')
510 self
.assertStatus(200)
511 self
.get_image('rbd', None, 'edit_img')
512 self
.assertStatus(404)
513 self
.get_image('rbd', None, 'new_edit_img')
514 self
.assertStatus(200)
515 self
.remove_image('rbd', None, 'new_edit_img')
516 self
.assertStatus(204)
518 def test_image_resize(self
):
519 self
.create_image('rbd', None, 'edit_img', 2**30)
520 self
.assertStatus(201)
521 img
= self
.get_image('rbd', None, 'edit_img')
522 self
.assertStatus(200)
523 self
._validate
_image
(img
, size
=2**30)
524 self
.edit_image('rbd', None, 'edit_img', size
=2*2**30)
525 self
.assertStatus(200)
526 img
= self
.get_image('rbd', None, 'edit_img')
527 self
.assertStatus(200)
528 self
._validate
_image
(img
, size
=2*2**30)
529 self
.remove_image('rbd', None, 'edit_img')
530 self
.assertStatus(204)
532 def test_image_change_features(self
):
533 self
.create_image('rbd', None, 'edit_img', 2**30, features
=["layering"])
534 self
.assertStatus(201)
535 img
= self
.get_image('rbd', None, 'edit_img')
536 self
.assertStatus(200)
537 self
._validate
_image
(img
, features_name
=["layering"])
538 self
.edit_image('rbd', None, 'edit_img',
539 features
=["fast-diff", "object-map", "exclusive-lock"])
540 self
.assertStatus(200)
541 img
= self
.get_image('rbd', None, 'edit_img')
542 self
.assertStatus(200)
543 self
._validate
_image
(img
, features_name
=['exclusive-lock',
544 'fast-diff', 'layering',
546 self
.edit_image('rbd', None, 'edit_img',
547 features
=["journaling", "exclusive-lock"])
548 self
.assertStatus(200)
549 img
= self
.get_image('rbd', None, 'edit_img')
550 self
.assertStatus(200)
551 self
._validate
_image
(img
, features_name
=['exclusive-lock',
552 'journaling', 'layering'])
553 self
.remove_image('rbd', None, 'edit_img')
554 self
.assertStatus(204)
556 def test_image_change_config(self
):
558 image
= 'image_with_config'
560 'rbd_qos_bps_limit': 10240,
561 'rbd_qos_write_iops_limit': None
564 'name': 'rbd_qos_bps_limit',
568 'name': 'rbd_qos_write_iops_limit',
573 'rbd_qos_bps_limit': 0,
574 'rbd_qos_bps_burst': 20480,
575 'rbd_qos_write_iops_limit': None
578 'name': 'rbd_qos_bps_limit',
582 'name': 'rbd_qos_bps_burst',
586 'name': 'rbd_qos_write_iops_limit',
591 self
.create_image(pool
, None, image
, 2**30, configuration
=initial_conf
)
592 self
.assertStatus(201)
593 img
= self
.get_image(pool
, None, image
)
594 self
.assertStatus(200)
595 for conf
in initial_expect
:
596 self
.assertIn(conf
, img
['configuration'])
598 self
.edit_image(pool
, None, image
, configuration
=new_conf
)
599 img
= self
.get_image(pool
, None, image
)
600 self
.assertStatus(200)
601 for conf
in new_expect
:
602 self
.assertIn(conf
, img
['configuration'])
604 self
.remove_image(pool
, None, image
)
605 self
.assertStatus(204)
607 def test_update_snapshot(self
):
608 self
.create_snapshot('rbd', None, 'img1', 'snap5')
609 self
.assertStatus(201)
610 img
= self
.get_image('rbd', None, 'img1')
611 self
._validate
_snapshot
_list
(img
['snapshots'], 'snap5', is_protected
=False)
613 self
.update_snapshot('rbd', None, 'img1', 'snap5', 'snap6', None)
614 self
.assertStatus(200)
615 img
= self
.get_image('rbd', None, 'img1')
616 self
._validate
_snapshot
_list
(img
['snapshots'], 'snap6', is_protected
=False)
618 self
.update_snapshot('rbd', None, 'img1', 'snap6', None, True)
619 self
.assertStatus(200)
620 img
= self
.get_image('rbd', None, 'img1')
621 self
._validate
_snapshot
_list
(img
['snapshots'], 'snap6', is_protected
=True)
623 self
.update_snapshot('rbd', None, 'img1', 'snap6', 'snap5', False)
624 self
.assertStatus(200)
625 img
= self
.get_image('rbd', None, 'img1')
626 self
._validate
_snapshot
_list
(img
['snapshots'], 'snap5', is_protected
=False)
628 self
.remove_snapshot('rbd', None, 'img1', 'snap5')
629 self
.assertStatus(204)
631 def test_snapshot_rollback(self
):
632 self
.create_image('rbd', None, 'rollback_img', 2**30,
633 features
=["layering", "exclusive-lock", "fast-diff",
635 self
.assertStatus(201)
636 self
.create_snapshot('rbd', None, 'rollback_img', 'snap1')
637 self
.assertStatus(201)
639 img
= self
.get_image('rbd', None, 'rollback_img')
640 self
.assertStatus(200)
641 self
.assertEqual(img
['disk_usage'], 0)
643 self
._rbd
_cmd
(['bench', '--io-type', 'write', '--io-total', '5M',
646 img
= self
.get_image('rbd', None, 'rollback_img')
647 self
.assertStatus(200)
648 self
.assertGreater(img
['disk_usage'], 0)
650 self
.rollback_snapshot('rbd', None, 'rollback_img', 'snap1')
651 self
.assertStatus([201, 200])
653 img
= self
.get_image('rbd', None, 'rollback_img')
654 self
.assertStatus(200)
655 self
.assertEqual(img
['disk_usage'], 0)
657 self
.remove_snapshot('rbd', None, 'rollback_img', 'snap1')
658 self
.assertStatus(204)
659 self
.remove_image('rbd', None, 'rollback_img')
660 self
.assertStatus(204)
662 def test_clone(self
):
663 self
.create_image('rbd', None, 'cimg', 2**30, features
=["layering"])
664 self
.assertStatus(201)
665 self
.create_snapshot('rbd', None, 'cimg', 'snap1')
666 self
.assertStatus(201)
667 self
.update_snapshot('rbd', None, 'cimg', 'snap1', None, True)
668 self
.assertStatus(200)
669 self
.clone_image('rbd', None, 'cimg', 'snap1', 'rbd', None, 'cimg-clone',
670 features
=["layering", "exclusive-lock", "fast-diff",
672 self
.assertStatus([200, 201])
674 img
= self
.get_image('rbd', None, 'cimg-clone')
675 self
.assertStatus(200)
676 self
._validate
_image
(img
, features_name
=['exclusive-lock',
677 'fast-diff', 'layering',
679 parent
={'pool_name': 'rbd', 'pool_namespace': '',
680 'image_name': 'cimg', 'snap_name': 'snap1'})
682 res
= self
.remove_image('rbd', None, 'cimg')
683 self
.assertStatus(400)
684 self
.assertIn('code', res
)
685 self
.assertEqual(res
['code'], '16')
687 self
.remove_image('rbd', None, 'cimg-clone')
688 self
.assertStatus(204)
689 self
.remove_image('rbd', None, 'cimg')
690 self
.assertStatus(204)
693 self
.create_image('rbd', None, 'coimg', 2**30,
694 features
=["layering", "exclusive-lock", "fast-diff",
696 self
.assertStatus(201)
698 self
._rbd
_cmd
(['bench', '--io-type', 'write', '--io-total', '5M',
701 self
.copy_image('rbd', None, 'coimg', 'rbd_iscsi', None, 'coimg-copy',
702 features
=["layering", "fast-diff", "exclusive-lock",
704 self
.assertStatus([200, 201])
706 img
= self
.get_image('rbd', None, 'coimg')
707 self
.assertStatus(200)
708 self
._validate
_image
(img
, features_name
=['layering', 'exclusive-lock',
709 'fast-diff', 'object-map'])
711 img_copy
= self
.get_image('rbd_iscsi', None, 'coimg-copy')
712 self
._validate
_image
(img_copy
, features_name
=['exclusive-lock',
713 'fast-diff', 'layering',
715 disk_usage
=img
['disk_usage'])
717 self
.remove_image('rbd', None, 'coimg')
718 self
.assertStatus(204)
719 self
.remove_image('rbd_iscsi', None, 'coimg-copy')
720 self
.assertStatus(204)
722 def test_flatten(self
):
723 self
.create_snapshot('rbd', None, 'img1', 'snapf')
724 self
.update_snapshot('rbd', None, 'img1', 'snapf', None, True)
725 self
.clone_image('rbd', None, 'img1', 'snapf', 'rbd_iscsi', None, 'img1_snapf_clone')
727 img
= self
.get_image('rbd_iscsi', None, 'img1_snapf_clone')
728 self
.assertStatus(200)
729 self
.assertIsNotNone(img
['parent'])
731 self
.flatten_image('rbd_iscsi', None, 'img1_snapf_clone')
732 self
.assertStatus([200, 201])
734 img
= self
.get_image('rbd_iscsi', None, 'img1_snapf_clone')
735 self
.assertStatus(200)
736 self
.assertIsNone(img
['parent'])
738 self
.update_snapshot('rbd', None, 'img1', 'snapf', None, False)
739 self
.remove_snapshot('rbd', None, 'img1', 'snapf')
740 self
.assertStatus(204)
742 self
.remove_image('rbd_iscsi', None, 'img1_snapf_clone')
743 self
.assertStatus(204)
745 def test_default_features(self
):
746 default_features
= self
._get
('/api/block/image/default_features')
747 self
.assertEqual(default_features
, [
748 'deep-flatten', 'exclusive-lock', 'fast-diff', 'layering', 'object-map'])
750 def test_clone_format_version(self
):
751 config_name
= 'rbd_default_clone_format'
753 def _get_config_by_name(conf_name
):
754 data
= self
._get
('/api/cluster_conf/{}'.format(conf_name
))
759 # with rbd_default_clone_format = auto
760 clone_format_version
= self
._get
('/api/block/image/clone_format_version')
761 self
.assertEqual(clone_format_version
, 1)
762 self
.assertStatus(200)
764 # with rbd_default_clone_format = 1
765 value
= [{'section': "global", 'value': "1"}]
766 self
._post
('/api/cluster_conf', {
770 self
.wait_until_equal(
771 lambda: _get_config_by_name(config_name
),
774 clone_format_version
= self
._get
('/api/block/image/clone_format_version')
775 self
.assertEqual(clone_format_version
, 1)
776 self
.assertStatus(200)
778 # with rbd_default_clone_format = 2
779 value
= [{'section': "global", 'value': "2"}]
780 self
._post
('/api/cluster_conf', {
784 self
.wait_until_equal(
785 lambda: _get_config_by_name(config_name
),
788 clone_format_version
= self
._get
('/api/block/image/clone_format_version')
789 self
.assertEqual(clone_format_version
, 2)
790 self
.assertStatus(200)
793 self
._post
('/api/cluster_conf', {
797 self
.wait_until_equal(
798 lambda: _get_config_by_name(config_name
),
802 def test_image_with_namespace(self
):
803 self
.create_namespace('rbd', 'ns')
804 self
.create_image('rbd', 'ns', 'test', 10240)
805 self
.assertStatus(201)
807 img
= self
.get_image('rbd', 'ns', 'test')
808 self
.assertStatus(200)
810 self
._validate
_image
(img
, name
='test', size
=10240,
811 pool_name
='rbd', namespace
='ns',
812 num_objs
=1, obj_size
=4194304,
813 features_name
=['deep-flatten',
815 'fast-diff', 'layering',
818 self
.remove_image('rbd', 'ns', 'test')
819 self
.remove_namespace('rbd', 'ns')
821 def test_move_image_to_trash(self
):
822 img_id
= self
.create_image_in_trash('rbd', 'test_rbd')
824 self
.get_image('rbd', None, 'test_rbd')
825 self
.assertStatus(404)
829 image
= self
.get_trash('rbd', img_id
)
830 self
.assertIsNotNone(image
)
832 self
.remove_trash('rbd', img_id
)
834 def test_list_trash(self
):
835 img_id
= self
.create_image_in_trash('rbd', 'test_rbd', 0)
836 data
= self
._get
('/api/block/image/trash/?pool_name={}'.format('rbd'))
837 self
.assertStatus(200)
838 self
.assertIsInstance(data
, list)
839 self
.assertIsNotNone(data
)
841 self
.remove_trash('rbd', img_id
)
842 self
.assertStatus(204)
844 def test_restore_trash(self
):
845 img_id
= self
.create_image_in_trash('rbd', 'test_rbd')
847 self
.restore_trash('rbd', None, img_id
, 'test_rbd')
849 self
.get_image('rbd', None, 'test_rbd')
850 self
.assertStatus(200)
852 image
= self
.get_trash('rbd', img_id
)
853 self
.assertIsNone(image
)
855 self
.remove_image('rbd', None, 'test_rbd')
857 def test_remove_expired_trash(self
):
858 img_id
= self
.create_image_in_trash('rbd', 'test_rbd', 0)
859 self
.remove_trash('rbd', img_id
, False)
860 self
.assertStatus(204)
862 image
= self
.get_trash('rbd', img_id
)
863 self
.assertIsNone(image
)
865 def test_remove_not_expired_trash(self
):
866 img_id
= self
.create_image_in_trash('rbd', 'test_rbd', 9999)
867 self
.remove_trash('rbd', img_id
, False)
868 self
.assertStatus(400)
872 image
= self
.get_trash('rbd', img_id
)
873 self
.assertIsNotNone(image
)
875 self
.remove_trash('rbd', img_id
, True)
877 def test_remove_not_expired_trash_with_force(self
):
878 img_id
= self
.create_image_in_trash('rbd', 'test_rbd', 9999)
879 self
.remove_trash('rbd', img_id
, True)
880 self
.assertStatus(204)
882 image
= self
.get_trash('rbd', img_id
)
883 self
.assertIsNone(image
)
885 def test_purge_trash(self
):
886 id_expired
= self
.create_image_in_trash('rbd', 'test_rbd_expired', 0)
887 id_not_expired
= self
.create_image_in_trash('rbd', 'test_rbd', 9999)
891 self
.purge_trash('rbd')
892 self
.assertStatus([200, 201])
896 trash_not_expired
= self
.get_trash('rbd', id_not_expired
)
897 self
.assertIsNotNone(trash_not_expired
)
899 self
.wait_until_equal(lambda: self
.get_trash('rbd', id_expired
), None, 60)
901 def test_list_namespaces(self
):
902 self
.create_namespace('rbd', 'ns')
904 namespaces
= self
._get
('/api/block/pool/rbd/namespace')
905 self
.assertStatus(200)
906 self
.assertEqual(len(namespaces
), 1)
908 self
.remove_namespace('rbd', 'ns')