'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)
)
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)
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:
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
RBD_MIRRORING = "rbd-mirroring"
RGW = "rgw"
CEPHFS = "cephfs"
+ CEPHFS_MIRROR = "cephfs-mirror"
MANAGER = "manager"
LOG = "log"
GRAFANA = "grafana"
# -*- coding: utf-8 -*-
+import json
from collections import defaultdict
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
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')