From: Imran Imtiaz Date: Thu, 8 Jan 2026 10:37:32 +0000 (+0000) Subject: mgr/dashboard: fix RBD mirror schedule inheritance in pool and image APIs X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=452cfdddbbe30118ff97115dd73cff74fa9276ea;p=ceph.git mgr/dashboard: fix RBD mirror schedule inheritance in pool and image APIs Signed-off-by: Imran Imtiaz 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 --- diff --git a/src/pybind/mgr/dashboard/controllers/pool.py b/src/pybind/mgr/dashboard/controllers/pool.py index ab9319712275..29b7dc91911e 100644 --- a/src/pybind/mgr/dashboard/controllers/pool.py +++ b/src/pybind/mgr/dashboard/controllers/pool.py @@ -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: diff --git a/src/pybind/mgr/dashboard/services/rbd.py b/src/pybind/mgr/dashboard/services/rbd.py index 41e73983bc17..4227093fc9b2 100644 --- a/src/pybind/mgr/dashboard/services/rbd.py +++ b/src/pybind/mgr/dashboard/services/rbd.py @@ -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):