]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix RBD mirror schedule inheritance in pool and image APIs 67026/head
authorImran Imtiaz <imran.imtiaz@uk.ibm.com>
Thu, 8 Jan 2026 10:37:32 +0000 (10:37 +0000)
committerImran Imtiaz <imran.imtiaz@uk.ibm.com>
Fri, 23 Jan 2026 11:35:06 +0000 (11:35 +0000)
Signed-off-by: Imran Imtiaz <imran.imtiaz@uk.ibm.com>
Fixes: https://tracker.ceph.com/issues/74494
Fix the bug where the Pool API was reporting random image schedules
instead of pool schedules. Implement proper schedule inheritance
hierarchy (Image > Pool > Cluster) for both Pool and Image APIs.

Signed-off-by: Imran Imtiaz <imran.imtiaz@uk.ibm.com>
src/pybind/mgr/dashboard/controllers/pool.py
src/pybind/mgr/dashboard/services/rbd.py

index ab9319712275f3d854f44ddcbacfc0ee92f359cc..29b7dc91911e0762a98e2c91d1ab65a4c3fe3bb4 100644 (file)
@@ -157,13 +157,27 @@ class Pool(RESTController):
         if not pool:
             raise cherrypy.NotFound('No such pool')
 
-        schedule_info = RbdMirroringService.get_snapshot_schedule_info()
-        if schedule_info:
-            filtered = [
-                info for info in schedule_info
-                if info["name"].split("/", 1)[0] == pool_name
-            ]
-            pool[0]['schedule_info'] = filtered[0] if filtered else {}
+        pool_schedule_spec = f"{pool_name}/"
+        pool_interval = RbdMirroringService.get_schedule_interval(pool_schedule_spec)
+
+        if pool_interval:
+            pool[0]['schedule_info'] = {
+                'name': pool_schedule_spec,
+                'schedule_interval': pool_interval,
+                'schedule_time': None,
+                'inherited': None
+            }
+        else:
+            cluster_interval = RbdMirroringService.get_schedule_interval('')
+            if cluster_interval:
+                pool[0]['schedule_info'] = {
+                    'name': '',
+                    'schedule_interval': cluster_interval,
+                    'schedule_time': None,
+                    'inherited': 'cluster'
+                }
+            else:
+                pool[0]['schedule_info'] = {}
         return pool[0]
 
     def get(self, pool_name: str, attrs: Optional[str] = None, stats: bool = False) -> dict:
index 41e73983bc17b99923cba0c92fad46ba7a85a29a..4227093fc9b2c86b281738c930751067f8e62a3d 100644 (file)
@@ -318,11 +318,38 @@ class RbdService(object):
                 stat['mirror_mode'] = 'journal'
             elif mirror_mode == rbd.RBD_MIRROR_IMAGE_MODE_SNAPSHOT:
                 stat['mirror_mode'] = 'snapshot'
-                schedule_info = RbdMirroringService.get_snapshot_schedule_info(
-                    get_image_spec(pool_name, namespace, image_name)
-                )
-                if schedule_info:
-                    stat['schedule_info'] = schedule_info[0]
+                image_schedule_spec = get_image_spec(pool_name, namespace, image_name)
+                image_schedule_info = RbdMirroringService.get_snapshot_schedule_info(
+                    image_schedule_spec)
+
+                if image_schedule_info and len(image_schedule_info) > 0:
+                    schedule = image_schedule_info[0]
+                    schedule['inherited'] = None
+                    stat['schedule_info'] = schedule
+                else:
+                    image_schedule_time = RbdMirroringService.get_schedule_time_for_image(
+                        image_schedule_spec)
+
+                    pool_schedule_spec = f"{pool_name}/"
+                    pool_interval = RbdMirroringService.get_schedule_interval(
+                        pool_schedule_spec)
+
+                    if pool_interval:
+                        stat['schedule_info'] = {
+                            'name': pool_schedule_spec,
+                            'schedule_interval': pool_interval,
+                            'schedule_time': image_schedule_time,
+                            'inherited': 'pool'
+                        }
+                    else:
+                        cluster_interval = RbdMirroringService.get_schedule_interval('')
+                        if cluster_interval:
+                            stat['schedule_info'] = {
+                                'name': '',
+                                'schedule_interval': cluster_interval,
+                                'schedule_time': image_schedule_time,
+                                'inherited': 'cluster'
+                            }
 
             stat['name'] = image_name
 
@@ -833,6 +860,51 @@ class RbdMirroringService:
 
         return schedule_info if schedule_info else None
 
+    @classmethod
+    def get_schedule_time_for_image(cls, image_spec: str):
+        """Get the scheduled time for a specific image from schedule status."""
+        schedule_status_raw = cls.snapshot_schedule_status('')
+        try:
+            schedule_status = json.loads(
+                schedule_status_raw[1]) if schedule_status_raw and schedule_status_raw[1] else {}
+        except (json.JSONDecodeError, TypeError):
+            return None
+
+        scheduled_images = schedule_status.get("scheduled_images", [])
+        for img in scheduled_images:
+            if img.get("image") == image_spec:
+                return img.get("schedule_time")
+        return None
+
+    @classmethod
+    def get_schedule_interval(cls, schedule_spec: str):
+        """Get just the schedule interval for a given spec (pool or cluster)."""
+        schedule_list_raw = cls.snapshot_schedule_list('')
+        try:
+            schedule_list = json.loads(
+                schedule_list_raw[1]) if schedule_list_raw and schedule_list_raw[1] else {}
+        except (json.JSONDecodeError, TypeError):
+            return None
+
+        if not schedule_list:
+            return None
+
+        for _, schedule in schedule_list.items():
+            name = schedule.get("name")
+            if name and name == schedule_spec:
+                return schedule.get("schedule", [])
+        return None
+
+    @classmethod
+    def get_cluster_schedule(cls):
+        """Get cluster-level schedule with inherited flag set."""
+        cluster_schedule_info = cls.get_snapshot_schedule_info('')
+        if cluster_schedule_info and len(cluster_schedule_info) > 0:
+            schedule = cluster_schedule_info[0]
+            schedule['inherited'] = 'cluster'
+            return schedule
+        return None
+
 
 class RbdImageMetadataService(object):
     def __init__(self, image):