]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add schedule_level to image API for pool/cluster snapshot schedule 67442/head
authorImran Imtiaz <imran.imtiaz@uk.ibm.com>
Fri, 20 Feb 2026 10:57:15 +0000 (10:57 +0000)
committerImran Imtiaz <imran.imtiaz@uk.ibm.com>
Fri, 20 Feb 2026 14:49:28 +0000 (14:49 +0000)
Add optional schedule_level param (image|pool|cluster) to
PUT /api/block/image/{image_spec}. Removes more-specific schedules
before setting at the chosen level. Backward compatible when omitted.

Fixes: https://tracker.ceph.com/issues/75043
Assisted-by: Cursor AI
Signed-off-by: Imran Imtiaz <imran.imtiaz@uk.ibm.com>
src/pybind/mgr/dashboard/controllers/rbd.py
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/rbd.py

index 910c4994dcc88fe75c9b0ab50cee1ff3677b7140..54ec2f75de481986db22b5e285bb9127766c95b4 100644 (file)
@@ -163,11 +163,11 @@ class Rbd(RESTController):
     def set(self, image_spec, name=None, size=None, features=None,
             configuration=None, metadata=None, enable_mirror=None, primary=None,
             force=False, resync=False, mirror_mode=None, image_mirror_mode=None,
-            schedule_interval='', remove_scheduling=False):
+            schedule_interval='', remove_scheduling=False, schedule_level=None):
         return RbdService.set(image_spec, name, size, features,
                               configuration, metadata, enable_mirror, primary,
                               force, resync, mirror_mode, image_mirror_mode,
-                              schedule_interval, remove_scheduling)
+                              schedule_interval, remove_scheduling, schedule_level)
 
     @RbdTask('copy',
              {'src_image_spec': '{image_spec}',
index c20e63e4a6a57bef469c369c0b1b34e18480a052..ebe20de5f75d2b22559569afaebbe7dd9acbcf4c 100755 (executable)
@@ -820,6 +820,8 @@ paths:
                 schedule_interval:
                   default: ''
                   type: string
+                schedule_level:
+                  type: string
                 size:
                   type: integer
               type: object
index 4227093fc9b2c86b281738c930751067f8e62a3d..678cec798064ad7f0e81cee381740c07733066cc 100644 (file)
@@ -115,6 +115,13 @@ def get_image_spec(pool_name, namespace, rbd_name):
     return '{}/{}{}'.format(pool_name, namespace, rbd_name)
 
 
+def get_pool_schedule_spec(pool_name, namespace):
+    """Build the schedule level_spec for pool-level schedule (rbd_support format)."""
+    if namespace:
+        return '{}/{}/'.format(pool_name, namespace)
+    return '{}/'.format(pool_name)
+
+
 def parse_image_spec(image_spec):
     namespace_spec, image_name = image_spec.rsplit('/', 1)
     if '/' in namespace_spec:
@@ -586,10 +593,17 @@ class RbdService(object):
     def set(cls, image_spec, name=None, size=None, features=None,
             configuration=None, metadata=None, enable_mirror=None, primary=None,
             force=False, resync=False, mirror_mode=None, image_mirror_mode=None,
-            schedule_interval='', remove_scheduling=False):
+            schedule_interval='', remove_scheduling=False, schedule_level=None):
         # pylint: disable=too-many-branches
         pool_name, namespace, image_name = parse_image_spec(image_spec)
 
+        if schedule_level is not None and schedule_level not in ('image', 'pool', 'cluster'):
+            raise DashboardException(
+                msg='schedule_level must be one of: image, pool, cluster',
+                code='invalid_schedule_level',
+                component='rbd')
+        effective_schedule_level = schedule_level if schedule_level else 'image'
+
         def _edit(ioctx, image):
             rbd_inst = cls._rbd_inst
             # check rename image
@@ -652,11 +666,30 @@ class RbdService(object):
             if resync:
                 RbdMirroringService.resync_image(image_name, pool_name, namespace)
 
-            if schedule_interval:
-                RbdMirroringService.snapshot_schedule_add(image_spec, schedule_interval)
+            current_image_name = name if name else image_name
+            image_schedule_spec = get_image_spec(pool_name, namespace, current_image_name)
+            pool_schedule_spec = get_pool_schedule_spec(pool_name, namespace)
 
             if remove_scheduling:
-                RbdMirroringService.snapshot_schedule_remove(image_spec)
+                if effective_schedule_level == 'image':
+                    RbdMirroringService.snapshot_schedule_remove(image_schedule_spec)
+                elif effective_schedule_level == 'pool':
+                    RbdMirroringService.snapshot_schedule_remove(pool_schedule_spec)
+                else:
+                    RbdMirroringService.snapshot_schedule_remove('')
+
+            if schedule_interval:
+                if effective_schedule_level == 'image':
+                    RbdMirroringService.snapshot_schedule_add(
+                        image_schedule_spec, schedule_interval)
+                elif effective_schedule_level == 'pool':
+                    RbdMirroringService.snapshot_schedule_remove(image_schedule_spec)
+                    RbdMirroringService.snapshot_schedule_add(
+                        pool_schedule_spec, schedule_interval)
+                else:
+                    RbdMirroringService.snapshot_schedule_remove(image_schedule_spec)
+                    RbdMirroringService.snapshot_schedule_remove(pool_schedule_spec)
+                    RbdMirroringService.snapshot_schedule_add('', schedule_interval)
 
         return rbd_image_call(pool_name, namespace, image_name, _edit)
 
@@ -836,7 +869,7 @@ class RbdMirroringService:
 
         for _, schedule in schedule_list.items():
             name = schedule.get("name")
-            if not name:
+            if name is None:
                 continue
 
             # find status entry for this schedule
@@ -891,7 +924,7 @@ class RbdMirroringService:
 
         for _, schedule in schedule_list.items():
             name = schedule.get("name")
-            if name and name == schedule_spec:
+            if name is not None and name == schedule_spec:
                 return schedule.get("schedule", [])
         return None