]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/mgr/dashboard/test_rbd.py
import ceph quincy 17.2.4
[ceph.git] / ceph / qa / tasks / mgr / dashboard / test_rbd.py
1 # -*- coding: utf-8 -*-
2 # pylint: disable=too-many-public-methods
3
4 from __future__ import absolute_import
5
6 import time
7
8 from .helper import DashboardTestCase, JLeaf, JList, JObj
9
10
11 class RbdTest(DashboardTestCase):
12 AUTH_ROLES = ['pool-manager', 'block-manager', 'cluster-manager']
13 LIST_VERSION = '2.0'
14
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)
22
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)
33
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)
44
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)
51
52 @classmethod
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}
64 data.update(kwargs)
65 return cls._task_post('/api/block/image', data)
66
67 @classmethod
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):
75 # pylint: disable=too-many-arguments
76 data = {'child_image_name': name, 'child_namespace': namespace, 'child_pool_name': pool}
77 data.update(kwargs)
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),
81 data)
82
83 @classmethod
84 def copy_image(cls, src_pool, src_namespace, src_image, dest_pool, dest_namespace, dest_image,
85 **kwargs):
86 # pylint: disable=too-many-arguments
87 data = {'dest_image_name': dest_image,
88 'dest_pool_name': dest_pool,
89 'dest_namespace': dest_namespace}
90 data.update(kwargs)
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)
94
95 @classmethod
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))
99
100 # pylint: disable=too-many-arguments
101 @classmethod
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)
106
107 @classmethod
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))
111
112 @classmethod
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})
117
118 @classmethod
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))
123
124 @classmethod
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),
129 {'new_snap_name': new_name, 'is_protected': is_protected})
130
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
139 @classmethod
140 def setUpClass(cls):
141 super(RbdTest, cls).setUpClass()
142 cls.create_pool('rbd', 2**3, 'replicated')
143 cls.create_pool('rbd_iscsi', 2**3, 'replicated')
144
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)
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
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))
169
170 self._task_post("/api/block/image/{}%2F{}/move_trash".format(pool, name),
171 {'delay': delay})
172 self.assertStatus([200, 201])
173 return img['id']
174
175 @classmethod
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))
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):
196 for trash_pool in trash:
197 for image in trash_pool['value']:
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,
210 "mirror_mode": "journal",
211 "num_objs": 256,
212 "order": 22,
213 "block_name_prefix": "rbd_data.10ae2ae8944a",
214 "name": "img1",
215 "pool_name": "rbd",
216 "features": 61,
217 "features_name": ["deep-flatten", "exclusive-lock", "fast-diff", "layering",
218 "object-map"]
219 }
220 """
221 schema = JObj(sub_elems={
222 'size': JLeaf(int),
223 'obj_size': JLeaf(int),
224 'num_objs': JLeaf(int),
225 'order': JLeaf(int),
226 'block_name_prefix': JLeaf(str),
227 'name': JLeaf(str),
228 'id': 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={
247 'name': JLeaf(str),
248 'source': JLeaf(int),
249 'value': JLeaf(str),
250 })),
251 'mirror_mode': JLeaf(str),
252 })
253 self.assertSchema(img, schema)
254
255 for k, v in kwargs.items():
256 if isinstance(v, list):
257 self.assertSetEqual(set(img[k]), set(v))
258 else:
259 self.assertEqual(img[k], v)
260
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)
268
269 for k, v in kwargs.items():
270 if isinstance(v, list):
271 self.assertSetEqual(set(snap[k]), set(v))
272 else:
273 self.assertEqual(snap[k], v)
274
275 def _validate_snapshot_list(self, snap_list, snap_name=None, **kwargs):
276 found = False
277 for snap in snap_list:
278 self.assertIn('name', snap)
279 if snap_name and snap['name'] == snap_name:
280 found = True
281 self._validate_snapshot(snap, **kwargs)
282 break
283 if snap_name and not found:
284 self.fail("Snapshot {} not found".format(snap_name))
285
286 def test_list(self):
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)
291
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)
298
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',
307 'exclusive-lock',
308 'fast-diff',
309 'layering',
310 'object-map'])
311 elif img['name'] == 'img2':
312 self._validate_image(img, size=2147483648,
313 num_objs=512, obj_size=4194304,
314 features_name=['deep-flatten',
315 'exclusive-lock',
316 'fast-diff',
317 'layering',
318 'object-map'])
319 else:
320 assert False, "Unexcepted image '{}' in result list".format(img['name'])
321
322 def test_create(self):
323 rbd_name = 'test_rbd'
324 self.create_image('rbd', None, rbd_name, 10240)
325 self.assertStatus(201)
326
327 img = self.get_image('rbd', None, 'test_rbd')
328 self.assertStatus(200)
329
330 self._validate_image(img, name=rbd_name, size=10240,
331 num_objs=1, obj_size=4194304,
332 features_name=['deep-flatten',
333 'exclusive-lock',
334 'fast-diff', 'layering',
335 'object-map'])
336
337 self.remove_image('rbd', None, rbd_name)
338
339 def test_create_with_configuration(self):
340 pool = 'rbd'
341 image_name = 'image_with_config'
342 size = 10240
343 configuration = {
344 'rbd_qos_bps_limit': 10240,
345 'rbd_qos_bps_burst': 10240 * 2,
346 }
347 expected = [{
348 'name': 'rbd_qos_bps_limit',
349 'source': 2,
350 'value': str(10240),
351 }, {
352 'name': 'rbd_qos_bps_burst',
353 'source': 2,
354 'value': str(10240 * 2),
355 }]
356
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'])
363
364 self.remove_image(pool, None, image_name)
365
366 def test_create_rbd_in_data_pool(self):
367 if not self.bluestore_support:
368 self.skipTest('requires bluestore cluster')
369
370 self.create_pool('data_pool', 2**4, 'erasure')
371
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)
375
376 img = self.get_image('rbd', None, 'test_rbd_in_data_pool')
377 self.assertStatus(200)
378
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',
383 'exclusive-lock',
384 'fast-diff', 'layering',
385 'object-map'])
386
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'])
391
392 def test_create_rbd_twice(self):
393 res = self.create_image('rbd', None, 'test_rbd_twice', 10240)
394
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 "
399 "image)",
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)
405
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'])
411
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',
418 'object-map'])
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)
428
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',
437 'object-map'])
438 self.remove_image('rbd_iscsi', None, 'img1_clone')
439 self.assertStatus(204)
440
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)
453
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'}}})
461
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)
469
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)
474
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)
479
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)
484
485 self.remove_image('rbd', None, 'delete_me')
486 self.assertStatus(204)
487
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)
495
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)
500
501 self.remove_image('rbd', None, 'delete_me')
502 self.assertStatus(204)
503
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)
517
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)
531
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',
545 'object-map'])
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)
555
556 def test_image_change_config(self):
557 pool = 'rbd'
558 image = 'image_with_config'
559 initial_conf = {
560 'rbd_qos_bps_limit': 10240,
561 'rbd_qos_write_iops_limit': None
562 }
563 initial_expect = [{
564 'name': 'rbd_qos_bps_limit',
565 'source': 2,
566 'value': '10240',
567 }, {
568 'name': 'rbd_qos_write_iops_limit',
569 'source': 0,
570 'value': '0',
571 }]
572 new_conf = {
573 'rbd_qos_bps_limit': 0,
574 'rbd_qos_bps_burst': 20480,
575 'rbd_qos_write_iops_limit': None
576 }
577 new_expect = [{
578 'name': 'rbd_qos_bps_limit',
579 'source': 2,
580 'value': '0',
581 }, {
582 'name': 'rbd_qos_bps_burst',
583 'source': 2,
584 'value': '20480',
585 }, {
586 'name': 'rbd_qos_write_iops_limit',
587 'source': 0,
588 'value': '0',
589 }]
590
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'])
597
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'])
603
604 self.remove_image(pool, None, image)
605 self.assertStatus(204)
606
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)
612
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)
617
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)
622
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)
627
628 self.remove_snapshot('rbd', None, 'img1', 'snap5')
629 self.assertStatus(204)
630
631 def test_snapshot_rollback(self):
632 self.create_image('rbd', None, 'rollback_img', 2**30,
633 features=["layering", "exclusive-lock", "fast-diff",
634 "object-map"])
635 self.assertStatus(201)
636 self.create_snapshot('rbd', None, 'rollback_img', 'snap1')
637 self.assertStatus(201)
638
639 img = self.get_image('rbd', None, 'rollback_img')
640 self.assertStatus(200)
641 self.assertEqual(img['disk_usage'], 0)
642
643 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M',
644 'rbd/rollback_img'])
645
646 img = self.get_image('rbd', None, 'rollback_img')
647 self.assertStatus(200)
648 self.assertGreater(img['disk_usage'], 0)
649
650 self.rollback_snapshot('rbd', None, 'rollback_img', 'snap1')
651 self.assertStatus([201, 200])
652
653 img = self.get_image('rbd', None, 'rollback_img')
654 self.assertStatus(200)
655 self.assertEqual(img['disk_usage'], 0)
656
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)
661
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",
671 "object-map"])
672 self.assertStatus([200, 201])
673
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',
678 'object-map'],
679 parent={'pool_name': 'rbd', 'pool_namespace': '',
680 'image_name': 'cimg', 'snap_name': 'snap1'})
681
682 res = self.remove_image('rbd', None, 'cimg')
683 self.assertStatus(400)
684 self.assertIn('code', res)
685 self.assertEqual(res['code'], '16')
686
687 self.remove_image('rbd', None, 'cimg-clone')
688 self.assertStatus(204)
689 self.remove_image('rbd', None, 'cimg')
690 self.assertStatus(204)
691
692 def test_copy(self):
693 self.create_image('rbd', None, 'coimg', 2**30,
694 features=["layering", "exclusive-lock", "fast-diff",
695 "object-map"])
696 self.assertStatus(201)
697
698 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M',
699 'rbd/coimg'])
700
701 self.copy_image('rbd', None, 'coimg', 'rbd_iscsi', None, 'coimg-copy',
702 features=["layering", "fast-diff", "exclusive-lock",
703 "object-map"])
704 self.assertStatus([200, 201])
705
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'])
710
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',
714 'object-map'],
715 disk_usage=img['disk_usage'])
716
717 self.remove_image('rbd', None, 'coimg')
718 self.assertStatus(204)
719 self.remove_image('rbd_iscsi', None, 'coimg-copy')
720 self.assertStatus(204)
721
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')
726
727 img = self.get_image('rbd_iscsi', None, 'img1_snapf_clone')
728 self.assertStatus(200)
729 self.assertIsNotNone(img['parent'])
730
731 self.flatten_image('rbd_iscsi', None, 'img1_snapf_clone')
732 self.assertStatus([200, 201])
733
734 img = self.get_image('rbd_iscsi', None, 'img1_snapf_clone')
735 self.assertStatus(200)
736 self.assertIsNone(img['parent'])
737
738 self.update_snapshot('rbd', None, 'img1', 'snapf', None, False)
739 self.remove_snapshot('rbd', None, 'img1', 'snapf')
740 self.assertStatus(204)
741
742 self.remove_image('rbd_iscsi', None, 'img1_snapf_clone')
743 self.assertStatus(204)
744
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'])
749
750 def test_clone_format_version(self):
751 config_name = 'rbd_default_clone_format'
752
753 def _get_config_by_name(conf_name):
754 data = self._get('/api/cluster_conf/{}'.format(conf_name))
755 if 'value' in data:
756 return data['value']
757 return None
758
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)
763
764 # with rbd_default_clone_format = 1
765 value = [{'section': "global", 'value': "1"}]
766 self._post('/api/cluster_conf', {
767 'name': config_name,
768 'value': value
769 })
770 self.wait_until_equal(
771 lambda: _get_config_by_name(config_name),
772 value,
773 timeout=60)
774 clone_format_version = self._get('/api/block/image/clone_format_version')
775 self.assertEqual(clone_format_version, 1)
776 self.assertStatus(200)
777
778 # with rbd_default_clone_format = 2
779 value = [{'section': "global", 'value': "2"}]
780 self._post('/api/cluster_conf', {
781 'name': config_name,
782 'value': value
783 })
784 self.wait_until_equal(
785 lambda: _get_config_by_name(config_name),
786 value,
787 timeout=60)
788 clone_format_version = self._get('/api/block/image/clone_format_version')
789 self.assertEqual(clone_format_version, 2)
790 self.assertStatus(200)
791
792 value = []
793 self._post('/api/cluster_conf', {
794 'name': config_name,
795 'value': value
796 })
797 self.wait_until_equal(
798 lambda: _get_config_by_name(config_name),
799 None,
800 timeout=60)
801
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)
806
807 img = self.get_image('rbd', 'ns', 'test')
808 self.assertStatus(200)
809
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',
814 'exclusive-lock',
815 'fast-diff', 'layering',
816 'object-map'])
817
818 self.remove_image('rbd', 'ns', 'test')
819 self.remove_namespace('rbd', 'ns')
820
821 def test_move_image_to_trash(self):
822 img_id = self.create_image_in_trash('rbd', 'test_rbd')
823
824 self.get_image('rbd', None, 'test_rbd')
825 self.assertStatus(404)
826
827 time.sleep(1)
828
829 image = self.get_trash('rbd', img_id)
830 self.assertIsNotNone(image)
831
832 self.remove_trash('rbd', img_id)
833
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)
840
841 self.remove_trash('rbd', img_id)
842 self.assertStatus(204)
843
844 def test_restore_trash(self):
845 img_id = self.create_image_in_trash('rbd', 'test_rbd')
846
847 self.restore_trash('rbd', None, img_id, 'test_rbd')
848
849 self.get_image('rbd', None, 'test_rbd')
850 self.assertStatus(200)
851
852 image = self.get_trash('rbd', img_id)
853 self.assertIsNone(image)
854
855 self.remove_image('rbd', None, 'test_rbd')
856
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)
861
862 image = self.get_trash('rbd', img_id)
863 self.assertIsNone(image)
864
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)
869
870 time.sleep(1)
871
872 image = self.get_trash('rbd', img_id)
873 self.assertIsNotNone(image)
874
875 self.remove_trash('rbd', img_id, True)
876
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)
881
882 image = self.get_trash('rbd', img_id)
883 self.assertIsNone(image)
884
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)
888
889 time.sleep(1)
890
891 self.purge_trash('rbd')
892 self.assertStatus([200, 201])
893
894 time.sleep(1)
895
896 trash_not_expired = self.get_trash('rbd', id_not_expired)
897 self.assertIsNotNone(trash_not_expired)
898
899 self.wait_until_equal(lambda: self.get_trash('rbd', id_expired), None, 60)
900
901 def test_list_namespaces(self):
902 self.create_namespace('rbd', 'ns')
903
904 namespaces = self._get('/api/block/pool/rbd/namespace')
905 self.assertStatus(200)
906 self.assertEqual(len(namespaces), 1)
907
908 self.remove_namespace('rbd', 'ns')