]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: add GET endpoint for CephFS mirror peers list and daemon status
authorPedro Gonzalez Gomez <pegonzal@ibm.com>
Thu, 20 Nov 2025 14:09:03 +0000 (15:09 +0100)
committerPedro Gonzalez Gomez <pegonzal@ibm.com>
Wed, 26 Nov 2025 17:21:30 +0000 (18:21 +0100)
Fixes: https://tracker.ceph.com/issues/74002
Signed-off-by: Pedro Gonzalez Gomez <pegonzal@ibm.com>
src/pybind/mgr/dashboard/controllers/cephfs.py
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/security.py
src/pybind/mgr/dashboard/tests/test_cephfs.py

index 29292329aafda14c04fa81bab7397c8e14ca36e3..11bbea4d7bcfccd09cb79a9b080f9e4b939a6787 100644 (file)
@@ -30,6 +30,35 @@ GET_STATFS_SCHEMA = {
     'subdirs': (int, '')
 }
 
+LIST_PEERS_SCHEMA = [{
+    'uuid': ({
+        'client_name': (str, 'Ceph client name'),
+        'site_name': (str, 'Remote site name'),
+        'fs_name': (str, 'File system name'),
+    }, 'Peer ID'),
+}]
+
+DAEMON_STATUS_SCHEMA = [{
+    'daemon_id': (int, 'Daemon ID'),
+    'filesystems': ([{
+        'filesystem_id': (int, 'Filesystem ID'),
+        'name': (str, 'Filesystem name'),
+        'directory_count': (int, 'Directory count'),
+        'peers': ([{
+            'uuid': (str, 'Peer UUID'),
+            'remote': ({
+                'client_name': (str, 'Ceph client name'),
+                'cluster_name': (str, 'Remote cluster name'),
+                'fs_name': (str, 'Remote filesystem name'),
+            }, 'Remote peer information'),
+            'stats': ({
+                'failure_count': (int, 'Number of sync failures'),
+                'recovery_count': (int, 'Number of peer recoveries'),
+            }, 'Peer statistics'),
+        }], 'List of peer objects'),
+    }], 'List of filesystems on daemon'),
+}]
+
 
 # pylint: disable=R0904
 @APIRouter('/cephfs', Scope.CEPHFS)
@@ -1206,3 +1235,37 @@ class CephFSSnapshotSchedule(RESTController):
             )
 
         return f'Snapshot schedule for path {path} activated successfully'
+
+
+@APIRouter('/cephfs/mirror', Scope.CEPHFS_MIRROR)
+@APIDoc("Cephfs Mirror Management API", "CephfsMirror")
+class CephFSMirror(RESTController):
+
+    @EndpointDoc("Get peers",
+                 parameters={
+                     'fs_name': (str, 'File system name'),
+                 },
+                 responses={200: LIST_PEERS_SCHEMA})
+    def list(self, fs_name: str):
+        error_code, out, err = mgr.remote('mirroring', 'snapshot_mirror_peer_list', fs_name)
+        if error_code != 0:
+            raise DashboardException(
+                msg=f'Failed to get Cephfs mirror peers: {err}',
+                code=error_code,
+                component='cephfs.mirror'
+            )
+        return json.loads(out)
+
+    @EndpointDoc("Get mirror daemon and peers information",
+                 responses={200: DAEMON_STATUS_SCHEMA})
+    @Endpoint('GET', path='/daemon-status')
+    @ReadPermission
+    def daemon_status(self):
+        error_code, out, err = mgr.remote('mirroring', 'snapshot_mirror_daemon_status')
+        if error_code != 0:
+            raise DashboardException(
+                msg=f'Failed to get Cephfs mirror daemon status: {err}',
+                code=error_code,
+                component='cephfs.mirror'
+            )
+        return json.loads(out)
index ca0163d98f69e5a2751f81b3caa148cfb7b37fe5..20e76934bbd89f2b69b9e8e4664cdb0b1d9cc2db 100755 (executable)
@@ -1898,6 +1898,154 @@ paths:
         given path
       tags:
       - Cephfs
+  /api/cephfs/mirror:
+    get:
+      parameters:
+      - description: File system name
+        in: query
+        name: fs_name
+        required: true
+        schema:
+          type: string
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                items:
+                  properties:
+                    uuid:
+                      description: Peer ID
+                      properties:
+                        client_name:
+                          description: Ceph client name
+                          type: string
+                        fs_name:
+                          description: File system name
+                          type: string
+                        site_name:
+                          description: Remote site name
+                          type: string
+                      required:
+                      - client_name
+                      - site_name
+                      - fs_name
+                      type: object
+                  type: object
+                required:
+                - uuid
+                type: array
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: Get peers
+      tags:
+      - CephfsMirror
+  /api/cephfs/mirror/daemon-status:
+    get:
+      parameters: []
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                items:
+                  properties:
+                    daemon_id:
+                      description: Daemon ID
+                      type: integer
+                    filesystems:
+                      description: List of filesystems on daemon
+                      items:
+                        properties:
+                          directory_count:
+                            description: Directory count
+                            type: integer
+                          filesystem_id:
+                            description: Filesystem ID
+                            type: integer
+                          name:
+                            description: Filesystem name
+                            type: string
+                          peers:
+                            description: List of peer objects
+                            items:
+                              properties:
+                                remote:
+                                  description: Remote peer information
+                                  properties:
+                                    client_name:
+                                      description: Ceph client name
+                                      type: string
+                                    cluster_name:
+                                      description: Remote cluster name
+                                      type: string
+                                    fs_name:
+                                      description: Remote filesystem name
+                                      type: string
+                                  required:
+                                  - client_name
+                                  - cluster_name
+                                  - fs_name
+                                  type: object
+                                stats:
+                                  description: Peer statistics
+                                  properties:
+                                    failure_count:
+                                      description: Number of sync failures
+                                      type: integer
+                                    recovery_count:
+                                      description: Number of peer recoveries
+                                      type: integer
+                                  required:
+                                  - failure_count
+                                  - recovery_count
+                                  type: object
+                                uuid:
+                                  description: Peer UUID
+                                  type: string
+                              required:
+                              - uuid
+                              - remote
+                              - stats
+                              type: object
+                            type: array
+                        required:
+                        - filesystem_id
+                        - name
+                        - directory_count
+                        - peers
+                        type: object
+                      type: array
+                  type: object
+                required:
+                - daemon_id
+                - filesystems
+                type: array
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: Get mirror daemon and peers information
+      tags:
+      - CephfsMirror
   /api/cephfs/remove/{name}:
     delete:
       parameters:
@@ -19313,6 +19461,8 @@ tags:
   name: CephFSSubvolume
 - description: Cephfs Management API
   name: Cephfs
+- description: Cephfs Mirror Management API
+  name: CephfsMirror
 - description: Cephfs Snapshot Clone Management API
   name: CephfsSnapshotClone
 - description: Cephfs Subvolume Group Management API
index c329d24e1b34b11d6404d74eaf86650ff526ed4e..8a7b3dced61c29eebfa9f3a04e782909e59aa845 100644 (file)
@@ -19,6 +19,7 @@ class Scope(object):
     RBD_MIRRORING = "rbd-mirroring"
     RGW = "rgw"
     CEPHFS = "cephfs"
+    CEPHFS_MIRROR = "cephfs-mirror"
     MANAGER = "manager"
     LOG = "log"
     GRAFANA = "grafana"
index ae4253543841d122d0b69ea97f2eed5d2e5804a9..e8c29239fbe8c689c5693d71e5aeec3dc191b62f 100644 (file)
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import json
 from collections import defaultdict
 
 try:
@@ -6,7 +7,8 @@ try:
 except ImportError:
     from unittest.mock import patch, Mock
 
-from ..controllers.cephfs import CephFS
+from .. import mgr
+from ..controllers.cephfs import CephFS, CephFSMirror
 from ..tests import ControllerTestCase
 
 
@@ -40,3 +42,87 @@ class CephFsTest(ControllerTestCase):
         self.cephFs._append_mds_metadata(mds_versions, 'foo')
         self.assertEqual(len(mds_versions), 1)
         self.assertEqual(mds_versions['bar'], ['foo'])
+
+
+class CephFSMirrorTest(ControllerTestCase):
+
+    @classmethod
+    def setup_server(cls):
+        cls.setup_controllers([CephFSMirror])
+
+    def test_list_success(self):
+        fs_name = 'test_fs'
+        expected_peers = [
+            {
+                'uuid': {
+                    'client_name': 'client.mirror',
+                    'site_name': 'remote-site',
+                    'fs_name': 'test_fs'
+                }
+            }
+        ]
+        mock_output = json.dumps(expected_peers)
+        mgr.remote = Mock(return_value=(0, mock_output, ''))
+
+        self._get(f'/api/cephfs/mirror?fs_name={fs_name}')
+        self.assertStatus(200)
+        self.assertJsonBody(expected_peers)
+        mgr.remote.assert_called_once_with('mirroring', 'snapshot_mirror_peer_list', fs_name)
+
+    def test_list_error(self):
+        fs_name = 'test_fs'
+        error_message = 'Failed to connect to remote'
+        mgr.remote = Mock(return_value=(1, '', error_message))
+
+        self._get(f'/api/cephfs/mirror?fs_name={fs_name}')
+        self.assertStatus(400)
+        response = self.json_body()
+        self.assertIn('Failed to get Cephfs mirror peers', response.get('detail', ''))
+        self.assertIn(error_message, response.get('detail', ''))
+        mgr.remote.assert_called_once_with('mirroring', 'snapshot_mirror_peer_list', fs_name)
+
+    def test_daemon_status_success(self):
+        expected_status = [
+            {
+                'daemon_id': 1,
+                'filesystems': [
+                    {
+                        'filesystem_id': 1,
+                        'name': 'test_fs',
+                        'directory_count': 5,
+                        'peers': [
+                            {
+                                'uuid': 'peer-uuid-123',
+                                'remote': {
+                                    'client_name': 'client.mirror',
+                                    'cluster_name': 'remote-cluster',
+                                    'fs_name': 'remote_fs'
+                                },
+                                'stats': {
+                                    'failure_count': 0,
+                                    'recovery_count': 1
+                                }
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+        mock_output = json.dumps(expected_status)
+        mgr.remote = Mock(return_value=(0, mock_output, ''))
+
+        self._get('/api/cephfs/mirror/daemon-status')
+        self.assertStatus(200)
+        self.assertJsonBody(expected_status)
+        mgr.remote.assert_called_once_with('mirroring', 'snapshot_mirror_daemon_status')
+
+    def test_daemon_status_error(self):
+        error_message = 'Daemon not available'
+        mgr.remote = Mock(return_value=(1, '', error_message))
+
+        self._get('/api/cephfs/mirror/daemon-status')
+        self.assertStatus(400)
+        response = self.json_body()
+        self.assertIn('Failed to get Cephfs mirror daemon status', response.get('detail', ''))
+        self.assertIn(error_message, response.get('detail', ''))
+        mgr.remote.assert_called_once_with('mirroring', 'snapshot_mirror_daemon_status')