]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add RBD create functionality 20751/head
authorTatjana Dehler <tdehler@suse.com>
Tue, 27 Feb 2018 16:04:38 +0000 (17:04 +0100)
committerTatjana Dehler <tdehler@suse.com>
Wed, 14 Mar 2018 09:38:43 +0000 (10:38 +0100)
This commit adds a basic RBD create functionality to the REST API. It enables
the POST request of the http://<host>:<port>/api/rbd API endpoint.

The commit adds also related tests.

Signed-off-by: Tatjana Dehler <tdehler@suse.com>
qa/tasks/mgr/dashboard_v2/test_rbd.py
src/pybind/mgr/dashboard_v2/controllers/rbd.py

index eeae1d289bc857aa5339516c89434111beda3956..0835fc27fff1f3ed9d0f7f109705205e7d97c32b 100644 (file)
@@ -2,6 +2,8 @@
 
 from __future__ import absolute_import
 
+import unittest
+
 from .helper import DashboardTestCase, authenticate
 
 
@@ -40,3 +42,72 @@ class RbdTest(DashboardTestCase):
         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"})
index 87a5d7678289254c9c51efec1302e77459f098f6..b73697b0a16047ece45a496822a13a0dc4f9448a 100644 (file)
@@ -1,6 +1,8 @@
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import
 
+import math
+import cherrypy
 import rbd
 
 from .. import mgr
@@ -11,6 +13,18 @@ from ..tools import ApiController, AuthRequired, RESTController, ViewCache
 @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
 
@@ -22,21 +36,33 @@ class Rbd(RESTController):
         >>> 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)
@@ -68,3 +94,36 @@ class Rbd(RESTController):
         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}