]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/mgr/dashboard/test_rbd.py
import ceph 14.2.5
[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, JObj, JLeaf, JList
9
10
11 class RbdTest(DashboardTestCase):
12 AUTH_ROLES = ['pool-manager', 'block-manager']
13
14 @classmethod
15 def create_pool(cls, name, pg_num, pool_type, application='rbd'):
16 data = {
17 'pool': name,
18 'pg_num': pg_num,
19 'pool_type': pool_type,
20 'application_metadata': [application]
21 }
22 if pool_type == 'erasure':
23 data['flags'] = ['ec_overwrites']
24 cls._task_post("/api/pool", data)
25
26 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['create', 'update', 'delete']}])
27 def test_read_access_permissions(self):
28 self._get('/api/block/image')
29 self.assertStatus(403)
30 self._get('/api/block/image/pool/image')
31 self.assertStatus(403)
32
33 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'update', 'delete']}])
34 def test_create_access_permissions(self):
35 self.create_image('pool', 'name', 0)
36 self.assertStatus(403)
37 self.create_snapshot('pool', 'image', 'snapshot')
38 self.assertStatus(403)
39 self.copy_image('src_pool', 'src_image', 'dest_pool', 'dest_image')
40 self.assertStatus(403)
41 self.clone_image('parent_pool', 'parent_image', 'parent_snap', 'pool', 'name')
42 self.assertStatus(403)
43
44 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'create', 'delete']}])
45 def test_update_access_permissions(self):
46 self.edit_image('pool', 'image')
47 self.assertStatus(403)
48 self.update_snapshot('pool', 'image', 'snapshot', None, None)
49 self.assertStatus(403)
50 self._task_post('/api/block/image/rbd/rollback_img/snap/snap1/rollback')
51 self.assertStatus(403)
52 self.flatten_image('pool', 'image')
53 self.assertStatus(403)
54
55 @DashboardTestCase.RunAs('test', 'test', [{'rbd-image': ['read', 'create', 'update']}])
56 def test_delete_access_permissions(self):
57 self.remove_image('pool', 'image')
58 self.assertStatus(403)
59 self.remove_snapshot('pool', 'image', 'snapshot')
60 self.assertStatus(403)
61
62 @classmethod
63 def create_image(cls, pool, name, size, **kwargs):
64 data = {'name': name, 'pool_name': pool, 'size': size}
65 data.update(kwargs)
66 return cls._task_post('/api/block/image', data)
67
68 @classmethod
69 def clone_image(cls, parent_pool, parent_image, parent_snap, pool, name,
70 **kwargs):
71 # pylint: disable=too-many-arguments
72 data = {'child_image_name': name, 'child_pool_name': pool}
73 data.update(kwargs)
74 return cls._task_post('/api/block/image/{}/{}/snap/{}/clone'
75 .format(parent_pool, parent_image, parent_snap),
76 data)
77
78 @classmethod
79 def copy_image(cls, src_pool, src_image, dest_pool, dest_image, **kwargs):
80 # pylint: disable=too-many-arguments
81 data = {'dest_image_name': dest_image, 'dest_pool_name': dest_pool}
82 data.update(kwargs)
83 return cls._task_post('/api/block/image/{}/{}/copy'
84 .format(src_pool, src_image), data)
85
86 @classmethod
87 def remove_image(cls, pool, image):
88 return cls._task_delete('/api/block/image/{}/{}'.format(pool, image))
89
90 # pylint: disable=too-many-arguments
91 @classmethod
92 def edit_image(cls, pool, image, name=None, size=None, features=None, **kwargs):
93 kwargs.update({'name': name, 'size': size, 'features': features})
94 return cls._task_put('/api/block/image/{}/{}'.format(pool, image), kwargs)
95
96 @classmethod
97 def flatten_image(cls, pool, image):
98 return cls._task_post('/api/block/image/{}/{}/flatten'.format(pool, image))
99
100 @classmethod
101 def create_snapshot(cls, pool, image, snapshot):
102 return cls._task_post('/api/block/image/{}/{}/snap'.format(pool, image),
103 {'snapshot_name': snapshot})
104
105 @classmethod
106 def remove_snapshot(cls, pool, image, snapshot):
107 return cls._task_delete('/api/block/image/{}/{}/snap/{}'.format(pool, image, snapshot))
108
109 @classmethod
110 def update_snapshot(cls, pool, image, snapshot, new_name, is_protected):
111 return cls._task_put('/api/block/image/{}/{}/snap/{}'.format(pool, image, snapshot),
112 {'new_snap_name': new_name, 'is_protected': is_protected})
113
114 @classmethod
115 def setUpClass(cls):
116 super(RbdTest, cls).setUpClass()
117 cls.create_pool('rbd', 2**3, 'replicated')
118 cls.create_pool('rbd_iscsi', 2**3, 'replicated')
119
120 cls.create_image('rbd', 'img1', 2**30)
121 cls.create_image('rbd', 'img2', 2*2**30)
122 cls.create_image('rbd_iscsi', 'img1', 2**30)
123 cls.create_image('rbd_iscsi', 'img2', 2*2**30)
124
125 osd_metadata = cls.ceph_cluster.mon_manager.get_osd_metadata()
126 cls.bluestore_support = True
127 for osd in osd_metadata:
128 if osd['osd_objectstore'] != 'bluestore':
129 cls.bluestore_support = False
130 break
131
132 @classmethod
133 def tearDownClass(cls):
134 super(RbdTest, cls).tearDownClass()
135 cls._ceph_cmd(['osd', 'pool', 'delete', 'rbd', 'rbd', '--yes-i-really-really-mean-it'])
136 cls._ceph_cmd(['osd', 'pool', 'delete', 'rbd_iscsi', 'rbd_iscsi',
137 '--yes-i-really-really-mean-it'])
138 cls._ceph_cmd(['osd', 'pool', 'delete', 'rbd_data', 'rbd_data',
139 '--yes-i-really-really-mean-it'])
140
141 @classmethod
142 def create_image_in_trash(cls, pool, name, delay=0):
143 cls.create_image(pool, name, 10240)
144 img = cls._get('/api/block/image/{}/{}'.format(pool, name))
145
146 cls._task_post("/api/block/image/{}/{}/move_trash".format(pool, name),
147 {'delay': delay})
148
149 return img['id']
150
151 @classmethod
152 def remove_trash(cls, pool, image_id, image_name, force=False):
153 return cls._task_delete('/api/block/image/trash/{}/{}/?image_name={}&force={}'.format('rbd', image_id, image_name, force))
154
155 @classmethod
156 def get_trash(cls, pool, image_id):
157 trash = cls._get('/api/block/image/trash/?pool_name={}'.format(pool))
158 if isinstance(trash, list):
159 for pool in trash:
160 for image in pool['value']:
161 if image['id'] == image_id:
162 return image
163
164 return None
165
166 def _validate_image(self, img, **kwargs):
167 """
168 Example of an RBD image json:
169
170 {
171 "size": 1073741824,
172 "obj_size": 4194304,
173 "num_objs": 256,
174 "order": 22,
175 "block_name_prefix": "rbd_data.10ae2ae8944a",
176 "name": "img1",
177 "pool_name": "rbd",
178 "features": 61,
179 "features_name": ["deep-flatten", "exclusive-lock", "fast-diff", "layering",
180 "object-map"]
181 }
182 """
183 schema = JObj(sub_elems={
184 'size': JLeaf(int),
185 'obj_size': JLeaf(int),
186 'num_objs': JLeaf(int),
187 'order': JLeaf(int),
188 'block_name_prefix': JLeaf(str),
189 'name': JLeaf(str),
190 'id': JLeaf(str),
191 'pool_name': JLeaf(str),
192 'features': JLeaf(int),
193 'features_name': JList(JLeaf(str)),
194 'stripe_count': JLeaf(int, none=True),
195 'stripe_unit': JLeaf(int, none=True),
196 'parent': JObj(sub_elems={'pool_name': JLeaf(str),
197 'image_name': JLeaf(str),
198 'snap_name': JLeaf(str)}, none=True),
199 'data_pool': JLeaf(str, none=True),
200 'snapshots': JList(JLeaf(dict)),
201 'timestamp': JLeaf(str, none=True),
202 'disk_usage': JLeaf(int, none=True),
203 'total_disk_usage': JLeaf(int, none=True),
204 'configuration': JList(JObj(sub_elems={
205 'name': JLeaf(str),
206 'source': JLeaf(int),
207 'value': JLeaf(str),
208 })),
209 })
210 self.assertSchema(img, schema)
211
212 for k, v in kwargs.items():
213 if isinstance(v, list):
214 self.assertSetEqual(set(img[k]), set(v))
215 else:
216 self.assertEqual(img[k], v)
217
218 def _validate_snapshot(self, snap, **kwargs):
219 self.assertIn('id', snap)
220 self.assertIn('name', snap)
221 self.assertIn('is_protected', snap)
222 self.assertIn('timestamp', snap)
223 self.assertIn('size', snap)
224 self.assertIn('children', snap)
225
226 for k, v in kwargs.items():
227 if isinstance(v, list):
228 self.assertSetEqual(set(snap[k]), set(v))
229 else:
230 self.assertEqual(snap[k], v)
231
232 def _validate_snapshot_list(self, snap_list, snap_name=None, **kwargs):
233 found = False
234 for snap in snap_list:
235 self.assertIn('name', snap)
236 if snap_name and snap['name'] == snap_name:
237 found = True
238 self._validate_snapshot(snap, **kwargs)
239 break
240 if snap_name and not found:
241 self.fail("Snapshot {} not found".format(snap_name))
242
243 def test_list(self):
244 data = self._view_cache_get('/api/block/image')
245 self.assertStatus(200)
246 self.assertEqual(len(data), 2)
247
248 for pool_view in data:
249 self.assertEqual(pool_view['status'], 0)
250 self.assertIsNotNone(pool_view['value'])
251 self.assertIn('pool_name', pool_view)
252 self.assertIn(pool_view['pool_name'], ['rbd', 'rbd_iscsi'])
253 image_list = pool_view['value']
254 self.assertEqual(len(image_list), 2)
255
256 for img in image_list:
257 self.assertIn('name', img)
258 self.assertIn('pool_name', img)
259 self.assertIn(img['pool_name'], ['rbd', 'rbd_iscsi'])
260 if img['name'] == 'img1':
261 self._validate_image(img, size=1073741824,
262 num_objs=256, obj_size=4194304,
263 features_name=['deep-flatten',
264 'exclusive-lock',
265 'fast-diff',
266 'layering',
267 'object-map'])
268 elif img['name'] == 'img2':
269 self._validate_image(img, size=2147483648,
270 num_objs=512, obj_size=4194304,
271 features_name=['deep-flatten',
272 'exclusive-lock',
273 'fast-diff',
274 'layering',
275 'object-map'])
276 else:
277 assert False, "Unexcepted image '{}' in result list".format(img['name'])
278
279 def test_create(self):
280 rbd_name = 'test_rbd'
281 self.create_image('rbd', rbd_name, 10240)
282 self.assertStatus(201)
283
284 img = self._get('/api/block/image/rbd/test_rbd')
285 self.assertStatus(200)
286
287 self._validate_image(img, name=rbd_name, size=10240,
288 num_objs=1, obj_size=4194304,
289 features_name=['deep-flatten',
290 'exclusive-lock',
291 'fast-diff', 'layering',
292 'object-map'])
293
294 self.remove_image('rbd', rbd_name)
295
296 def test_create_with_configuration(self):
297 pool = 'rbd'
298 image_name = 'image_with_config'
299 size = 10240
300 configuration = {
301 'rbd_qos_bps_limit': 10240,
302 'rbd_qos_bps_burst': 10240 * 2,
303 }
304 expected = [{
305 'name': 'rbd_qos_bps_limit',
306 'source': 2,
307 'value': str(10240),
308 }, {
309 'name': 'rbd_qos_bps_burst',
310 'source': 2,
311 'value': str(10240 * 2),
312 }]
313
314 self.create_image(pool, image_name, size, configuration=configuration)
315 self.assertStatus(201)
316 img = self._get('/api/block/image/rbd/{}'.format(image_name))
317 self.assertStatus(200)
318 for conf in expected:
319 self.assertIn(conf, img['configuration'])
320
321 self.remove_image(pool, image_name)
322
323 def test_create_rbd_in_data_pool(self):
324 if not self.bluestore_support:
325 self.skipTest('requires bluestore cluster')
326
327 self.create_pool('data_pool', 2**4, 'erasure')
328
329 rbd_name = 'test_rbd_in_data_pool'
330 self.create_image('rbd', rbd_name, 10240, data_pool='data_pool')
331 self.assertStatus(201)
332
333 img = self._get('/api/block/image/rbd/test_rbd_in_data_pool')
334 self.assertStatus(200)
335
336 self._validate_image(img, name=rbd_name, size=10240,
337 num_objs=1, obj_size=4194304,
338 data_pool='data_pool',
339 features_name=['data-pool', 'deep-flatten',
340 'exclusive-lock',
341 'fast-diff', 'layering',
342 'object-map'])
343
344 self.remove_image('rbd', rbd_name)
345 self.assertStatus(204)
346 self._ceph_cmd(['osd', 'pool', 'delete', 'data_pool', 'data_pool',
347 '--yes-i-really-really-mean-it'])
348
349 def test_create_rbd_twice(self):
350 res = self.create_image('rbd', 'test_rbd_twice', 10240)
351
352 res = self.create_image('rbd', 'test_rbd_twice', 10240)
353 self.assertStatus(400)
354 self.assertEqual(res, {"code": '17', 'status': 400, "component": "rbd",
355 "detail": "[errno 17] error creating image",
356 'task': {'name': 'rbd/create',
357 'metadata': {'pool_name': 'rbd',
358 'image_name': 'test_rbd_twice'}}})
359 self.remove_image('rbd', 'test_rbd_twice')
360 self.assertStatus(204)
361
362 def test_snapshots_and_clone_info(self):
363 self.create_snapshot('rbd', 'img1', 'snap1')
364 self.create_snapshot('rbd', 'img1', 'snap2')
365 self._rbd_cmd(['snap', 'protect', 'rbd/img1@snap1'])
366 self._rbd_cmd(['clone', 'rbd/img1@snap1', 'rbd_iscsi/img1_clone'])
367
368 img = self._get('/api/block/image/rbd/img1')
369 self.assertStatus(200)
370 self._validate_image(img, name='img1', size=1073741824,
371 num_objs=256, obj_size=4194304, parent=None,
372 features_name=['deep-flatten', 'exclusive-lock',
373 'fast-diff', 'layering',
374 'object-map'])
375 for snap in img['snapshots']:
376 if snap['name'] == 'snap1':
377 self._validate_snapshot(snap, is_protected=True)
378 self.assertEqual(len(snap['children']), 1)
379 self.assertDictEqual(snap['children'][0],
380 {'pool_name': 'rbd_iscsi',
381 'image_name': 'img1_clone'})
382 elif snap['name'] == 'snap2':
383 self._validate_snapshot(snap, is_protected=False)
384
385 img = self._get('/api/block/image/rbd_iscsi/img1_clone')
386 self.assertStatus(200)
387 self._validate_image(img, name='img1_clone', size=1073741824,
388 num_objs=256, obj_size=4194304,
389 parent={'pool_name': 'rbd', 'image_name': 'img1',
390 'snap_name': 'snap1'},
391 features_name=['deep-flatten', 'exclusive-lock',
392 'fast-diff', 'layering',
393 'object-map'])
394 self.remove_image('rbd_iscsi', 'img1_clone')
395 self.assertStatus(204)
396
397 def test_disk_usage(self):
398 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '50M', 'rbd/img2'])
399 self.create_snapshot('rbd', 'img2', 'snap1')
400 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '20M', 'rbd/img2'])
401 self.create_snapshot('rbd', 'img2', 'snap2')
402 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '10M', 'rbd/img2'])
403 self.create_snapshot('rbd', 'img2', 'snap3')
404 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M', 'rbd/img2'])
405 img = self._get('/api/block/image/rbd/img2')
406 self.assertStatus(200)
407 self._validate_image(img, name='img2', size=2147483648,
408 total_disk_usage=268435456, disk_usage=67108864)
409
410 def test_delete_non_existent_image(self):
411 res = self.remove_image('rbd', 'i_dont_exist')
412 self.assertStatus(400)
413 self.assertEqual(res, {u'code': u'2', "status": 400, "component": "rbd",
414 "detail": "[errno 2] error removing image",
415 'task': {'name': 'rbd/delete',
416 'metadata': {'pool_name': 'rbd',
417 'image_name': 'i_dont_exist'}}})
418
419 def test_image_delete(self):
420 self.create_image('rbd', 'delete_me', 2**30)
421 self.assertStatus(201)
422 self.create_snapshot('rbd', 'delete_me', 'snap1')
423 self.assertStatus(201)
424 self.create_snapshot('rbd', 'delete_me', 'snap2')
425 self.assertStatus(201)
426
427 img = self._get('/api/block/image/rbd/delete_me')
428 self.assertStatus(200)
429 self._validate_image(img, name='delete_me', size=2**30)
430 self.assertEqual(len(img['snapshots']), 2)
431
432 self.remove_snapshot('rbd', 'delete_me', 'snap1')
433 self.assertStatus(204)
434 self.remove_snapshot('rbd', 'delete_me', 'snap2')
435 self.assertStatus(204)
436
437 img = self._get('/api/block/image/rbd/delete_me')
438 self.assertStatus(200)
439 self._validate_image(img, name='delete_me', size=2**30)
440 self.assertEqual(len(img['snapshots']), 0)
441
442 self.remove_image('rbd', 'delete_me')
443 self.assertStatus(204)
444
445 def test_image_rename(self):
446 self.create_image('rbd', 'edit_img', 2**30)
447 self.assertStatus(201)
448 self._get('/api/block/image/rbd/edit_img')
449 self.assertStatus(200)
450 self.edit_image('rbd', 'edit_img', 'new_edit_img')
451 self.assertStatus(200)
452 self._get('/api/block/image/rbd/edit_img')
453 self.assertStatus(404)
454 self._get('/api/block/image/rbd/new_edit_img')
455 self.assertStatus(200)
456 self.remove_image('rbd', 'new_edit_img')
457 self.assertStatus(204)
458
459 def test_image_resize(self):
460 self.create_image('rbd', 'edit_img', 2**30)
461 self.assertStatus(201)
462 img = self._get('/api/block/image/rbd/edit_img')
463 self.assertStatus(200)
464 self._validate_image(img, size=2**30)
465 self.edit_image('rbd', 'edit_img', size=2*2**30)
466 self.assertStatus(200)
467 img = self._get('/api/block/image/rbd/edit_img')
468 self.assertStatus(200)
469 self._validate_image(img, size=2*2**30)
470 self.remove_image('rbd', 'edit_img')
471 self.assertStatus(204)
472
473 def test_image_change_features(self):
474 self.create_image('rbd', 'edit_img', 2**30, features=["layering"])
475 self.assertStatus(201)
476 img = self._get('/api/block/image/rbd/edit_img')
477 self.assertStatus(200)
478 self._validate_image(img, features_name=["layering"])
479 self.edit_image('rbd', 'edit_img',
480 features=["fast-diff", "object-map", "exclusive-lock"])
481 self.assertStatus(200)
482 img = self._get('/api/block/image/rbd/edit_img')
483 self.assertStatus(200)
484 self._validate_image(img, features_name=['exclusive-lock',
485 'fast-diff', 'layering',
486 'object-map'])
487 self.edit_image('rbd', 'edit_img',
488 features=["journaling", "exclusive-lock"])
489 self.assertStatus(200)
490 img = self._get('/api/block/image/rbd/edit_img')
491 self.assertStatus(200)
492 self._validate_image(img, features_name=['exclusive-lock',
493 'journaling', 'layering'])
494 self.remove_image('rbd', 'edit_img')
495 self.assertStatus(204)
496
497 def test_image_change_config(self):
498 pool = 'rbd'
499 image = 'image_with_config'
500 initial_conf = {
501 'rbd_qos_bps_limit': 10240,
502 'rbd_qos_write_iops_limit': None
503 }
504 initial_expect = [{
505 'name': 'rbd_qos_bps_limit',
506 'source': 2,
507 'value': '10240',
508 }, {
509 'name': 'rbd_qos_write_iops_limit',
510 'source': 0,
511 'value': '0',
512 }]
513 new_conf = {
514 'rbd_qos_bps_limit': 0,
515 'rbd_qos_bps_burst': 20480,
516 'rbd_qos_write_iops_limit': None
517 }
518 new_expect = [{
519 'name': 'rbd_qos_bps_limit',
520 'source': 2,
521 'value': '0',
522 }, {
523 'name': 'rbd_qos_bps_burst',
524 'source': 2,
525 'value': '20480',
526 }, {
527 'name': 'rbd_qos_write_iops_limit',
528 'source': 0,
529 'value': '0',
530 }]
531
532 self.create_image(pool, image, 2**30, configuration=initial_conf)
533 self.assertStatus(201)
534 img = self._get('/api/block/image/{}/{}'.format(pool, image))
535 self.assertStatus(200)
536 for conf in initial_expect:
537 self.assertIn(conf, img['configuration'])
538
539 self.edit_image(pool, image, configuration=new_conf)
540 img = self._get('/api/block/image/{}/{}'.format(pool, image))
541 self.assertStatus(200)
542 for conf in new_expect:
543 self.assertIn(conf, img['configuration'])
544
545 self.remove_image(pool, image)
546 self.assertStatus(204)
547
548 def test_update_snapshot(self):
549 self.create_snapshot('rbd', 'img1', 'snap5')
550 self.assertStatus(201)
551 img = self._get('/api/block/image/rbd/img1')
552 self._validate_snapshot_list(img['snapshots'], 'snap5', is_protected=False)
553
554 self.update_snapshot('rbd', 'img1', 'snap5', 'snap6', None)
555 self.assertStatus(200)
556 img = self._get('/api/block/image/rbd/img1')
557 self._validate_snapshot_list(img['snapshots'], 'snap6', is_protected=False)
558
559 self.update_snapshot('rbd', 'img1', 'snap6', None, True)
560 self.assertStatus(200)
561 img = self._get('/api/block/image/rbd/img1')
562 self._validate_snapshot_list(img['snapshots'], 'snap6', is_protected=True)
563
564 self.update_snapshot('rbd', 'img1', 'snap6', 'snap5', False)
565 self.assertStatus(200)
566 img = self._get('/api/block/image/rbd/img1')
567 self._validate_snapshot_list(img['snapshots'], 'snap5', is_protected=False)
568
569 self.remove_snapshot('rbd', 'img1', 'snap5')
570 self.assertStatus(204)
571
572 def test_snapshot_rollback(self):
573 self.create_image('rbd', 'rollback_img', 2**30,
574 features=["layering", "exclusive-lock", "fast-diff",
575 "object-map"])
576 self.assertStatus(201)
577 self.create_snapshot('rbd', 'rollback_img', 'snap1')
578 self.assertStatus(201)
579
580 img = self._get('/api/block/image/rbd/rollback_img')
581 self.assertStatus(200)
582 self.assertEqual(img['disk_usage'], 0)
583
584 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M',
585 'rbd/rollback_img'])
586
587 img = self._get('/api/block/image/rbd/rollback_img')
588 self.assertStatus(200)
589 self.assertGreater(img['disk_usage'], 0)
590
591 self._task_post('/api/block/image/rbd/rollback_img/snap/snap1/rollback')
592 self.assertStatus([201, 200])
593
594 img = self._get('/api/block/image/rbd/rollback_img')
595 self.assertStatus(200)
596 self.assertEqual(img['disk_usage'], 0)
597
598 self.remove_snapshot('rbd', 'rollback_img', 'snap1')
599 self.assertStatus(204)
600 self.remove_image('rbd', 'rollback_img')
601 self.assertStatus(204)
602
603 def test_clone(self):
604 self.create_image('rbd', 'cimg', 2**30, features=["layering"])
605 self.assertStatus(201)
606 self.create_snapshot('rbd', 'cimg', 'snap1')
607 self.assertStatus(201)
608 self.update_snapshot('rbd', 'cimg', 'snap1', None, True)
609 self.assertStatus(200)
610 self.clone_image('rbd', 'cimg', 'snap1', 'rbd', 'cimg-clone',
611 features=["layering", "exclusive-lock", "fast-diff",
612 "object-map"])
613 self.assertStatus([200, 201])
614
615 img = self._get('/api/block/image/rbd/cimg-clone')
616 self.assertStatus(200)
617 self._validate_image(img, features_name=['exclusive-lock',
618 'fast-diff', 'layering',
619 'object-map'],
620 parent={'pool_name': 'rbd', 'image_name': 'cimg',
621 'snap_name': 'snap1'})
622
623 res = self.remove_image('rbd', 'cimg')
624 self.assertStatus(400)
625 self.assertIn('code', res)
626 self.assertEqual(res['code'], '39')
627
628 self.remove_image('rbd', 'cimg-clone')
629 self.assertStatus(204)
630 self.update_snapshot('rbd', 'cimg', 'snap1', None, False)
631 self.assertStatus(200)
632 self.remove_snapshot('rbd', 'cimg', 'snap1')
633 self.assertStatus(204)
634 self.remove_image('rbd', 'cimg')
635 self.assertStatus(204)
636
637 def test_copy(self):
638 self.create_image('rbd', 'coimg', 2**30,
639 features=["layering", "exclusive-lock", "fast-diff",
640 "object-map"])
641 self.assertStatus(201)
642
643 self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '5M',
644 'rbd/coimg'])
645
646 self.copy_image('rbd', 'coimg', 'rbd_iscsi', 'coimg-copy',
647 features=["layering", "fast-diff", "exclusive-lock",
648 "object-map"])
649 self.assertStatus([200, 201])
650
651 img = self._get('/api/block/image/rbd/coimg')
652 self.assertStatus(200)
653 self._validate_image(img, features_name=['layering', 'exclusive-lock',
654 'fast-diff', 'object-map'])
655
656 img_copy = self._get('/api/block/image/rbd_iscsi/coimg-copy')
657 self._validate_image(img_copy, features_name=['exclusive-lock',
658 'fast-diff', 'layering',
659 'object-map'],
660 disk_usage=img['disk_usage'])
661
662 self.remove_image('rbd', 'coimg')
663 self.assertStatus(204)
664 self.remove_image('rbd_iscsi', 'coimg-copy')
665 self.assertStatus(204)
666
667 def test_flatten(self):
668 self.create_snapshot('rbd', 'img1', 'snapf')
669 self.update_snapshot('rbd', 'img1', 'snapf', None, True)
670 self.clone_image('rbd', 'img1', 'snapf', 'rbd_iscsi', 'img1_snapf_clone')
671
672 img = self._get('/api/block/image/rbd_iscsi/img1_snapf_clone')
673 self.assertStatus(200)
674 self.assertIsNotNone(img['parent'])
675
676 self.flatten_image('rbd_iscsi', 'img1_snapf_clone')
677 self.assertStatus([200, 201])
678
679 img = self._get('/api/block/image/rbd_iscsi/img1_snapf_clone')
680 self.assertStatus(200)
681 self.assertIsNone(img['parent'])
682
683 self.update_snapshot('rbd', 'img1', 'snapf', None, False)
684 self.remove_snapshot('rbd', 'img1', 'snapf')
685 self.assertStatus(204)
686
687 self.remove_image('rbd_iscsi', 'img1_snapf_clone')
688 self.assertStatus(204)
689
690 def test_default_features(self):
691 default_features = self._get('/api/block/image/default_features')
692 self.assertEqual(default_features, ['deep-flatten', 'exclusive-lock',
693 'fast-diff', 'layering',
694 'object-map'])
695
696 def test_image_with_special_name(self):
697 rbd_name = 'test/rbd'
698 rbd_name_encoded = 'test%2Frbd'
699
700 self.create_image('rbd', rbd_name, 10240)
701 self.assertStatus(201)
702
703 img = self._get("/api/block/image/rbd/" + rbd_name_encoded)
704 self.assertStatus(200)
705
706 self._validate_image(img, name=rbd_name, size=10240,
707 num_objs=1, obj_size=4194304,
708 features_name=['deep-flatten',
709 'exclusive-lock',
710 'fast-diff', 'layering',
711 'object-map'])
712
713 self.remove_image('rbd', rbd_name_encoded)
714
715 def test_move_image_to_trash(self):
716 id = self.create_image_in_trash('rbd', 'test_rbd')
717 self.assertStatus(200)
718
719 self._get('/api/block/image/rbd/test_rbd')
720 self.assertStatus(404)
721
722 time.sleep(1)
723
724 image = self.get_trash('rbd', id)
725 self.assertIsNotNone(image)
726
727 self.remove_trash('rbd', id, 'test_rbd')
728
729 def test_list_trash(self):
730 id = self.create_image_in_trash('rbd', 'test_rbd', 0)
731 data = self._get('/api/block/image/trash/?pool_name={}'.format('rbd'))
732 self.assertStatus(200)
733 self.assertIsInstance(data, list)
734 self.assertIsNotNone(data)
735
736 self.remove_trash('rbd', id, 'test_rbd')
737 self.assertStatus(204)
738
739 def test_restore_trash(self):
740 id = self.create_image_in_trash('rbd', 'test_rbd')
741
742 self._task_post('/api/block/image/trash/{}/{}/restore'.format('rbd', id), {'new_image_name': 'test_rbd'})
743
744 self._get('/api/block/image/rbd/test_rbd')
745 self.assertStatus(200)
746
747 image = self.get_trash('rbd', id)
748 self.assertIsNone(image)
749
750 self.remove_image('rbd', 'test_rbd')
751
752 def test_remove_expired_trash(self):
753 id = self.create_image_in_trash('rbd', 'test_rbd', 0)
754 self.remove_trash('rbd', id, 'test_rbd', False)
755 self.assertStatus(204)
756
757 image = self.get_trash('rbd', id)
758 self.assertIsNone(image)
759
760 def test_remove_not_expired_trash(self):
761 id = self.create_image_in_trash('rbd', 'test_rbd', 9999)
762 self.remove_trash('rbd', id, 'test_rbd', False)
763 self.assertStatus(400)
764
765 time.sleep(1)
766
767 image = self.get_trash('rbd', id)
768 self.assertIsNotNone(image)
769
770 self.remove_trash('rbd', id, 'test_rbd', True)
771
772 def test_remove_not_expired_trash_with_force(self):
773 id = self.create_image_in_trash('rbd', 'test_rbd', 9999)
774 self.remove_trash('rbd', id, 'test_rbd', True)
775 self.assertStatus(204)
776
777 image = self.get_trash('rbd', id)
778 self.assertIsNone(image)
779
780 def test_purge_trash(self):
781 id_expired = self.create_image_in_trash('rbd', 'test_rbd_expired', 0)
782 id_not_expired = self.create_image_in_trash('rbd', 'test_rbd', 9999)
783
784 time.sleep(1)
785
786 self._task_post('/api/block/image/trash/purge?pool_name={}'.format('rbd'))
787 self.assertStatus(200)
788
789 time.sleep(1)
790
791 trash_not_expired = self.get_trash('rbd', id_not_expired)
792 self.assertIsNotNone(trash_not_expired)
793
794 trash_expired = self.get_trash('rbd', id_expired)
795 self.assertIsNone(trash_expired)