]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: add API endpoint to add images to consistency groups
authorImran Imtiaz <imran.imtiaz@uk.ibm.com>
Thu, 13 Nov 2025 10:27:28 +0000 (10:27 +0000)
committerImran Imtiaz <imran.imtiaz@uk.ibm.com>
Wed, 19 Nov 2025 11:40:56 +0000 (11:40 +0000)
Signed-off-by: Imran Imtiaz <imran.imtiaz@uk.ibm.com>
Fixes: https://tracker.ceph.com/issues/73840
Create a consistency group dashboard API endpoint that enables adding
RBD images to the group.

src/pybind/mgr/dashboard/controllers/rbd.py
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/rbd.py

index c1d1c01934b5fce534166e1217b780f6afafe63c..fab2bfb1a0424fec400590e0159b762be1b28e5f 100644 (file)
@@ -471,13 +471,16 @@ class RbdGroup(RESTController):
         super().__init__()
         self.rbd_inst = rbd.RBD()
 
+    @handle_rbd_error()
     @EndpointDoc("Display RBD Groups by pool name",
                  parameters={
                      'pool_name': (str, 'Name of the pool'),
                  },
                  responses={200: RBD_GROUP_LIST_SCHEMA})
-    def list(self, pool_name):
+    def list(self, pool_name, namespace=None):
         with mgr.rados.open_ioctx(pool_name) as ioctx:
+            RbdService.validate_namespace(ioctx, namespace)
+            ioctx.set_namespace(namespace)
             result = []
             groups = self.rbd_inst.group_list(ioctx)
             for group in groups:
@@ -487,11 +490,30 @@ class RbdGroup(RESTController):
                 })
             return result
 
+    @handle_rbd_error()
     @EndpointDoc("Create an RBD Group",
                  parameters={
                      'pool_name': (str, 'Name of the pool'),
                      'name': (str, 'Name of the group'),
                  })
-    def create(self, pool_name, name):
+    def create(self, pool_name, name, namespace=None):
         with mgr.rados.open_ioctx(pool_name) as ioctx:
+            RbdService.validate_namespace(ioctx, namespace)
+            ioctx.set_namespace(namespace)
             return self.rbd_inst.group_create(ioctx, name)
+
+    @RESTController.Collection('POST', path='/{group_name}/image')
+    @handle_rbd_error()
+    @EndpointDoc("Add an image to an RBD Group",
+                 parameters={
+                     'pool_name': (str, 'Name of the pool'),
+                     'group_name': (str, 'Name of the group'),
+                     'image_name': (str, 'Name of the image'),
+                 },
+                 responses={200: None})
+    def add_image(self, pool_name, group_name, image_name, namespace=None):
+        with mgr.rados.open_ioctx(pool_name) as ioctx:
+            group = rbd.Group(ioctx, group_name)
+            RbdService.validate_namespace(ioctx, namespace)
+            ioctx.set_namespace(namespace)
+            return group.add_image(ioctx, image_name)
index ca0163d98f69e5a2751f81b3caa148cfb7b37fe5..6ac1c229a9f7fba627823359f74a7a1dc8d878ee 100755 (executable)
@@ -1599,6 +1599,11 @@ paths:
         required: true
         schema:
           type: string
+      - allowEmptyValue: true
+        in: query
+        name: namespace
+        schema:
+          type: string
       responses:
         '200':
           content:
@@ -1648,6 +1653,8 @@ paths:
                 name:
                   description: Name of the group
                   type: string
+                namespace:
+                  type: string
               required:
               - name
               type: object
@@ -1676,6 +1683,59 @@ paths:
       summary: Create an RBD Group
       tags:
       - RbdGroup
+  /api/block/pool/{pool_name}/group/{group_name}/image:
+    post:
+      parameters:
+      - description: Name of the pool
+        in: path
+        name: pool_name
+        required: true
+        schema:
+          type: string
+      - description: Name of the group
+        in: path
+        name: group_name
+        required: true
+        schema:
+          type: string
+      requestBody:
+        content:
+          application/json:
+            schema:
+              properties:
+                image_name:
+                  description: Name of the image
+                  type: string
+                namespace:
+                  type: string
+              required:
+              - image_name
+              type: object
+      responses:
+        '201':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          description: Resource created.
+        '202':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          description: Operation is still executing. Please check the task queue.
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: Add an image to an RBD Group
+      tags:
+      - RbdGroup
   /api/block/pool/{pool_name}/namespace:
     get:
       parameters:
index 07b86988ba39dfd5b76a6cd5ec969afbfca28e53..41e73983bc17b99923cba0c92fad46ba7a85a29a 100644 (file)
@@ -236,6 +236,7 @@ class RbdConfiguration(object):
         """
         Removes an option by name. Will not raise an error, if the option hasn't been found.
         :type option_name str
+
         """
         def _remove(ioctx):
             try:
@@ -267,7 +268,6 @@ class RbdConfiguration(object):
 
 class RbdService(object):
     _rbd_inst = rbd.RBD()
-
     # set of image features that can be enable on existing images
     ALLOW_ENABLE_FEATURES = {"exclusive-lock", "object-map", "fast-diff", "journaling"}
 
@@ -694,6 +694,15 @@ class RbdService(object):
         rbd_inst = cls._rbd_inst
         return rbd_call(pool_name, namespace, rbd_inst.trash_move, image_name, delay)
 
+    @classmethod
+    def validate_namespace(cls, ioctx, namespace):
+        namespaces = cls._rbd_inst.namespace_list(ioctx)
+        if namespace and namespace not in namespaces:
+            raise DashboardException(
+                msg='Namespace not found',
+                code='namespace_not_found',
+                component='rbd')
+
 
 class RbdSnapshotService(object):