Skip to content

Commit 98d9be3

Browse files
authored
Merge pull request ceph#65481 from rhcs-dashboard/snapshot-schdule-interval
mgr/dashboard: fix missing schedule interval in rbd API
2 parents 297f7e5 + 72cebf0 commit 98d9be3

File tree

5 files changed

+101
-9
lines changed

5 files changed

+101
-9
lines changed

src/pybind/mgr/dashboard/controllers/pool.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from ..security import Scope
1111
from ..services.ceph_service import CephService
1212
from ..services.exception import handle_send_command_error
13-
from ..services.rbd import RbdConfiguration
13+
from ..services.rbd import RbdConfiguration, RbdMirroringService
1414
from ..tools import TaskManager, str_to_bool
1515
from . import APIDoc, APIRouter, Endpoint, EndpointDoc, ReadPermission, \
1616
RESTController, Task, UIRouter
@@ -156,6 +156,14 @@ def _get(cls, pool_name: str, attrs: Optional[str] = None, stats: bool = False)
156156
pool = [p for p in pools if p['pool_name'] == pool_name]
157157
if not pool:
158158
raise cherrypy.NotFound('No such pool')
159+
160+
schedule_info = RbdMirroringService.get_snapshot_schedule_info()
161+
if schedule_info:
162+
filtered = [
163+
info for info in schedule_info
164+
if info["name"].split("/", 1)[0] == pool_name
165+
]
166+
pool[0]['schedule_info'] = filtered[0] if filtered else {}
159167
return pool[0]
160168

161169
def get(self, pool_name: str, attrs: Optional[str] = None, stats: bool = False) -> dict:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { RbdFormModel } from './rbd-form.model';
22

33
export class RbdFormCreateRequestModel extends RbdFormModel {
4+
schedule_interval: string;
45
features: Array<string> = [];
56
}

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,11 @@ export class RbdFormComponent extends CdForm implements OnInit {
659659
this.rbdForm.get('mirroring').setValue(this.mirroring);
660660
this.rbdForm.get('mirroringMode').setValue(response?.mirror_mode);
661661
this.currentImageMirrorMode = response?.mirror_mode;
662-
this.rbdForm.get('schedule').setValue(response?.schedule_interval);
662+
const scheduleInterval = response?.schedule_info?.schedule_interval[0]?.interval;
663+
if (scheduleInterval) {
664+
this.rbdForm.get('schedule').setValue(scheduleInterval);
665+
this.rbdForm.get('schedule').disable();
666+
}
663667
} else {
664668
this.mirroring = false;
665669
this.rbdForm.get('mirroring').setValue(this.mirroring);

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.model.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ export class RbdFormModel {
2121
enable_mirror?: boolean;
2222
mirror_mode?: string;
2323

24-
schedule_interval: string;
24+
schedule_info: ScheduleInfo;
25+
start_time: string;
26+
}
27+
28+
export class ScheduleInfo {
29+
image: string;
30+
schedule_time: string;
31+
schedule_interval: ScheduleInterval[];
32+
}
33+
34+
export class ScheduleInterval {
35+
interval: string;
2536
start_time: string;
2637
}

src/pybind/mgr/dashboard/services/rbd.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .ceph_service import CephService
1717

1818
try:
19-
from typing import List, Optional
19+
from typing import Dict, List, Optional
2020
except ImportError:
2121
pass # For typing only
2222

@@ -318,11 +318,11 @@ def _rbd_image(cls, ioctx, pool_name, namespace, image_name, # pylint: disable=
318318
stat['mirror_mode'] = 'journal'
319319
elif mirror_mode == rbd.RBD_MIRROR_IMAGE_MODE_SNAPSHOT:
320320
stat['mirror_mode'] = 'snapshot'
321-
schedule_status = json.loads(_rbd_support_remote(
322-
'mirror_snapshot_schedule_status')[1])
323-
for scheduled_image in schedule_status['scheduled_images']:
324-
if scheduled_image['image'] == get_image_spec(pool_name, namespace, image_name):
325-
stat['schedule_info'] = scheduled_image
321+
schedule_info = RbdMirroringService.get_snapshot_schedule_info(
322+
get_image_spec(pool_name, namespace, image_name)
323+
)
324+
if schedule_info:
325+
stat['schedule_info'] = schedule_info[0]
326326

327327
stat['name'] = image_name
328328

@@ -758,6 +758,74 @@ def snapshot_schedule_add(cls, image_spec: str, interval: str):
758758
def snapshot_schedule_remove(cls, image_spec: str):
759759
_rbd_support_remote('mirror_snapshot_schedule_remove', image_spec)
760760

761+
@classmethod
762+
def snapshot_schedule_list(cls, image_spec: str = ''):
763+
return _rbd_support_remote('mirror_snapshot_schedule_list', image_spec)
764+
765+
@classmethod
766+
def snapshot_schedule_status(cls, image_spec: str = ''):
767+
return _rbd_support_remote('mirror_snapshot_schedule_status', image_spec)
768+
769+
@classmethod
770+
def get_snapshot_schedule_info(cls, image_spec: str = ''):
771+
"""
772+
Retrieve snapshot schedule information by merging schedule list and status.
773+
774+
Args:
775+
image_spec (str, optional): Specification of an RBD image. If empty,
776+
retrieves all schedule information.
777+
Format: "<pool_name>/<namespace_name>/<image_name>".
778+
779+
Returns:
780+
Optional[List[Dict[str, Any]]]: A list of merged schedule information
781+
dictionaries if found, otherwise None.
782+
"""
783+
schedule_info: List[Dict] = []
784+
785+
# schedule list and status provide the schedule interval
786+
# and schedule timestamp respectively.
787+
schedule_list_raw = cls.snapshot_schedule_list(image_spec)
788+
schedule_status_raw = cls.snapshot_schedule_status(image_spec)
789+
790+
try:
791+
schedule_list = json.loads(
792+
schedule_list_raw[1]) if schedule_list_raw and schedule_list_raw[1] else {}
793+
schedule_status = json.loads(
794+
schedule_status_raw[1]) if schedule_status_raw and schedule_status_raw[1] else {}
795+
except (json.JSONDecodeError, TypeError):
796+
return None
797+
798+
if not schedule_list or not schedule_status:
799+
return None
800+
801+
scheduled_images = schedule_status.get("scheduled_images", [])
802+
803+
for _, schedule in schedule_list.items():
804+
name = schedule.get("name")
805+
if not name:
806+
continue
807+
808+
# find status entry for this schedule
809+
# by matching with the image name
810+
image = next((
811+
sched_image for sched_image in scheduled_images
812+
if sched_image.get("image") == name), None)
813+
if not image:
814+
continue
815+
816+
# eventually we are merging both the list and status entries
817+
# all the needed info are fetched above and here we are just mapping
818+
# it to the dictionary so that in one function we get
819+
# the schedule related information.
820+
merged = {
821+
"name": name,
822+
"schedule_interval": schedule.get("schedule", []),
823+
"schedule_time": image.get("schedule_time")
824+
}
825+
schedule_info.append(merged)
826+
827+
return schedule_info if schedule_info else None
828+
761829

762830
class RbdImageMetadataService(object):
763831
def __init__(self, image):

0 commit comments

Comments
 (0)