Skip to content

Commit 16126b1

Browse files
authored
Merge pull request ceph#65079 from rhcs-dashboard/img-summary-mirroring
mgr/dashboard: expose image summary API
2 parents 3468465 + f4b09da commit 16126b1

File tree

3 files changed

+99
-2
lines changed

3 files changed

+99
-2
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import inspect
23
import json
34
import logging
@@ -279,9 +280,15 @@ def inner(*args, **kwargs):
279280
if version else 'application/xml')
280281
return ret.encode('utf8')
281282
if json_response:
283+
# convert datetime obj so json can serialize properly
284+
def json_default(obj):
285+
if isinstance(obj, datetime.datetime):
286+
return obj.isoformat().replace("+00:00", "Z")
287+
return str(obj)
288+
282289
cherrypy.response.headers['Content-Type'] = (version.to_mime_type(subtype='json')
283290
if version else 'application/json')
284-
ret = json.dumps(ret).encode('utf8')
291+
ret = json.dumps(ret, default=json_default).encode('utf8')
285292
return ret
286293
return inner
287294

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

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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()
224269
def 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")
492550
class RbdMirroringPoolMode(RESTController):

src/pybind/mgr/dashboard/openapi.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,38 @@ paths:
15581558
summary: Display Rbd Mirroring Summary
15591559
tags:
15601560
- RbdMirroringSummary
1561+
/api/block/mirroring/{pool_name}/{image_name}/summary:
1562+
get:
1563+
parameters:
1564+
- in: path
1565+
name: pool_name
1566+
required: true
1567+
schema:
1568+
type: string
1569+
- in: path
1570+
name: image_name
1571+
required: true
1572+
schema:
1573+
type: string
1574+
responses:
1575+
'200':
1576+
content:
1577+
application/vnd.ceph.api.v1.0+json:
1578+
type: object
1579+
description: OK
1580+
'400':
1581+
description: Operation exception. Please check the response body for details.
1582+
'401':
1583+
description: Unauthenticated access. Please login first.
1584+
'403':
1585+
description: Unauthorized access. Please check your permissions.
1586+
'500':
1587+
description: Unexpected error. Please check the response body for the stack
1588+
trace.
1589+
security:
1590+
- jwt: []
1591+
tags:
1592+
- RbdMirroringSummary
15611593
/api/block/pool/{pool_name}/namespace:
15621594
get:
15631595
parameters:

0 commit comments

Comments
 (0)