from __future__ import absolute_import
+import unittest
+
from .helper import DashboardTestCase, authenticate
self.assertEqual(img2['obj_size'], 4194304)
self.assertEqual(img2['features_name'],
'deep-flatten, exclusive-lock, fast-diff, layering, object-map')
+
+ @authenticate
+ def test_create(self):
+ rbd_name = 'test_rbd'
+ data = {'pool_name': 'rbd',
+ 'name': rbd_name,
+ 'size': 10240}
+ self._post('/api/rbd', data)
+ self.assertStatus(201)
+ self.assertJsonBody({"success": True})
+
+ # TODO: change to GET the specific RBD instead of the list as soon as it is available?
+ get_res = self._get('/api/rbd/rbd')
+ self.assertStatus(200)
+
+ for rbd in get_res['value']:
+ if rbd['name'] == rbd_name:
+ self.assertEqual(rbd['size'], 10240)
+ self.assertEqual(rbd['num_objs'], 1)
+ self.assertEqual(rbd['obj_size'], 4194304)
+ self.assertEqual(rbd['features_name'],
+ 'deep-flatten, exclusive-lock, fast-diff, layering, object-map')
+ break
+
+ # TODO: Re-enable this test for bluestore cluster by figuring out how to skip none-bluestore
+ # ones automatically
+ @unittest.skip("requires bluestore cluster")
+ @authenticate
+ def test_create_rbd_in_data_pool(self):
+ self._ceph_cmd(['osd', 'pool', 'create', 'data_pool', '12', '12', 'erasure'])
+ self._ceph_cmd(['osd', 'pool', 'application', 'enable', 'data_pool', 'rbd'])
+ self._ceph_cmd(['osd', 'pool', 'set', 'data_pool', 'allow_ec_overwrites', 'true'])
+
+ rbd_name = 'test_rbd_in_data_pool'
+ data = {'pool_name': 'rbd',
+ 'name': rbd_name,
+ 'size': 10240,
+ 'data_pool': 'data_pool'}
+ self._post('/api/rbd', data)
+ self.assertStatus(201)
+ self.assertJsonBody({"success": True})
+
+ # TODO: possibly change to GET the specific RBD (see above)
+ get_res = self._get('/api/rbd/rbd')
+ self.assertStatus(200)
+
+ for rbd in get_res['value']:
+ if rbd['name'] == rbd_name:
+ self.assertEqual(rbd['size'], 10240)
+ self.assertEqual(rbd['num_objs'], 1)
+ self.assertEqual(rbd['obj_size'], 4194304)
+ self.assertEqual(rbd['features_name'], 'data-pool, deep-flatten, exclusive-lock, '
+ 'fast-diff, layering, object-map')
+ break
+
+ self._ceph_cmd(['osd', 'pool', 'delete', 'data_pool', 'data_pool',
+ '--yes-i-really-really-mean-it'])
+
+ @authenticate
+ def test_create_rbd_twice(self):
+ data = {'pool_name': 'rbd',
+ 'name': 'test_rbd_twice',
+ 'size': 10240}
+ self._post('/api/rbd', data)
+
+ self._post('/api/rbd', data)
+ self.assertStatus(400)
+ self.assertJsonBody({"success": False, "errno": 17,
+ "detail": "[errno 17] error creating image"})
# -*- coding: utf-8 -*-
from __future__ import absolute_import
+import math
+import cherrypy
import rbd
from .. import mgr
@AuthRequired()
class Rbd(RESTController):
+ RBD_FEATURES_NAME_MAPPING = {
+ rbd.RBD_FEATURE_LAYERING: "layering",
+ rbd.RBD_FEATURE_STRIPINGV2: "striping",
+ rbd.RBD_FEATURE_EXCLUSIVE_LOCK: "exclusive-lock",
+ rbd.RBD_FEATURE_OBJECT_MAP: "object-map",
+ rbd.RBD_FEATURE_FAST_DIFF: "fast-diff",
+ rbd.RBD_FEATURE_DEEP_FLATTEN: "deep-flatten",
+ rbd.RBD_FEATURE_JOURNALING: "journaling",
+ rbd.RBD_FEATURE_DATA_POOL: "data-pool",
+ rbd.RBD_FEATURE_OPERATIONS: "operations",
+ }
+
def __init__(self):
self.rbd = None
>>> Rbd._format_bitmask(45)
'deep-flatten, exclusive-lock, layering, object-map'
"""
- RBD_FEATURES_NAME_MAPPING = {
- rbd.RBD_FEATURE_LAYERING: "layering",
- rbd.RBD_FEATURE_STRIPINGV2: "striping",
- rbd.RBD_FEATURE_EXCLUSIVE_LOCK: "exclusive-lock",
- rbd.RBD_FEATURE_OBJECT_MAP: "object-map",
- rbd.RBD_FEATURE_FAST_DIFF: "fast-diff",
- rbd.RBD_FEATURE_DEEP_FLATTEN: "deep-flatten",
- rbd.RBD_FEATURE_JOURNALING: "journaling",
- rbd.RBD_FEATURE_DATA_POOL: "data-pool",
- rbd.RBD_FEATURE_OPERATIONS: "operations",
- }
- names = [val for key, val in RBD_FEATURES_NAME_MAPPING.items()
+ names = [val for key, val in Rbd.RBD_FEATURES_NAME_MAPPING.items()
if key & features == key]
return ', '.join(sorted(names))
+ @staticmethod
+ def _format_features(features):
+ """
+ Converts the features list to bitmask:
+
+ >>> Rbd._format_features(['deep-flatten', 'exclusive-lock', 'layering', 'object-map'])
+ 45
+
+ >>> Rbd._format_features(None) is None
+ True
+
+ >>> Rbd._format_features('not a list') is None
+ True
+ """
+ if not features or not isinstance(features, list):
+ return None
+
+ res = 0
+ for key, value in Rbd.RBD_FEATURES_NAME_MAPPING.items():
+ if value in features:
+ res = key | res
+ return res
+
@ViewCache()
def _rbd_list(self, pool_name):
ioctx = mgr.rados.open_ioctx(pool_name)
if status == ViewCache.VALUE_EXCEPTION:
raise value
return {'status': status, 'value': value}
+
+ def create(self, data):
+ if not self.rbd:
+ self.rbd = rbd.RBD()
+
+ # Get input values
+ name = data.get('name')
+ pool_name = data.get('pool_name')
+ size = data.get('size')
+ obj_size = data.get('obj_size')
+ features = data.get('features')
+ stripe_unit = data.get('stripe_unit')
+ stripe_count = data.get('stripe_count')
+ data_pool = data.get('data_pool')
+
+ # Set order
+ order = None
+ if obj_size and obj_size > 0:
+ order = int(round(math.log(float(obj_size), 2)))
+
+ # Set features
+ feature_bitmask = self._format_features(features)
+
+ ioctx = mgr.rados.open_ioctx(pool_name)
+
+ try:
+ self.rbd.create(ioctx, name, size, order=order, old_format=False,
+ features=feature_bitmask, stripe_unit=stripe_unit,
+ stripe_count=stripe_count, data_pool=data_pool)
+ except rbd.OSError as e:
+ cherrypy.response.status = 400
+ return {'success': False, 'detail': str(e), 'errno': e.errno}
+ return {'success': True}