@@ -37,6 +37,26 @@ class MirrorHealth(IntEnum):
3737 MIRROR_HEALTH_DISABLED = 4
3838 MIRROR_HEALTH_INFO = 5
3939
40+
41+ MIRROR_IMAGE_STATUS_MAP = {
42+ rbd .MIRROR_IMAGE_STATUS_STATE_UNKNOWN : "Unknown" ,
43+ rbd .MIRROR_IMAGE_STATUS_STATE_ERROR : "Error" ,
44+ rbd .MIRROR_IMAGE_STATUS_STATE_SYNCING : "Syncing" ,
45+ rbd .MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY : "Starting Replay" ,
46+ rbd .MIRROR_IMAGE_STATUS_STATE_REPLAYING : "Replaying" ,
47+ rbd .MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY : "Stopping Replay" ,
48+ rbd .MIRROR_IMAGE_STATUS_STATE_STOPPED : "Stopped" ,
49+ }
50+
51+
52+ def get_mirror_status_label (code : int ) -> str :
53+ default_code = rbd .MIRROR_IMAGE_STATUS_STATE_UNKNOWN
54+ return MIRROR_IMAGE_STATUS_MAP .get (
55+ code ,
56+ MIRROR_IMAGE_STATUS_MAP [default_code ]
57+ )
58+
59+
4060# pylint: disable=not-callable
4161
4262
@@ -220,6 +240,31 @@ def _get_pool_stats(pool_names):
220240 return pool_stats
221241
222242
243+ def _get_mirroring_status (pool_name , image_name ):
244+ ioctx = mgr .rados .open_ioctx (pool_name )
245+ mode = rbd .RBD ().mirror_mode_get (ioctx )
246+ status = rbd .Image (ioctx , image_name ).mirror_image_get_status ()
247+ for remote in status .get ("remote_statuses" , []):
248+ remote ["state" ] = get_mirror_status_label (remote ["state" ])
249+ desc = remote .get ("description" )
250+
251+ if not desc .startswith ("replaying, " ):
252+ continue
253+
254+ try :
255+ metrics = json .loads (desc .split (", " , 1 )[1 ])
256+ remote ["description" ] = metrics
257+ except (IndexError , json .JSONDecodeError ):
258+ continue
259+
260+ if mode == rbd .RBD_MIRROR_MODE_POOL :
261+ primary_tid = metrics ["primary_position" ]["entry_tid" ]
262+ non_primary_tid = metrics ["non_primary_position" ]["entry_tid" ]
263+ percent_done = (non_primary_tid / primary_tid ) * 100 if primary_tid else 0
264+ remote ["syncing_percent" ] = round (percent_done , 2 )
265+ return status
266+
267+
223268@ViewCache ()
224269def get_daemons_and_pools (): # pylint: disable=R0915
225270 daemons = get_daemons ()
@@ -387,8 +432,10 @@ def _get_content_data(): # pylint: disable=R0914
387432 }
388433
389434 if mirror_image ['health' ] == 'ok' :
435+ status = _get_mirroring_status (pool_name , mirror_image ['name' ])
390436 image .update ({
391- 'description' : mirror_image ['description' ]
437+ 'description' : mirror_image ['description' ],
438+ 'remote_status' : status .get ("remote_statuses" , [])
392439 })
393440 image_ready .append (image )
394441 elif mirror_image ['health' ] == 'syncing' :
@@ -487,6 +534,17 @@ def __call__(self):
487534 'content_data' : content_data }
488535
489536
537+ @APIRouter ('/block/mirroring/{pool_name}/{image_name}/summary' , Scope .RBD_MIRRORING )
538+ @APIDoc ("RBD Mirroring Summary Management API" , "RbdMirroringSummary" )
539+ class RbdImageMirroringSummary (BaseController ):
540+
541+ @Endpoint ()
542+ @handle_rbd_mirror_error ()
543+ @ReadPermission
544+ def __call__ (self , pool_name , image_name ):
545+ return _get_mirroring_status (pool_name , image_name )
546+
547+
490548@APIRouter ('/block/mirroring/pool' , Scope .RBD_MIRRORING )
491549@APIDoc ("RBD Mirroring Pool Mode Management API" , "RbdMirroringPoolMode" )
492550class RbdMirroringPoolMode (RESTController ):
0 commit comments