}], 'List of filesystems on daemon'),
}]
+MIRROR_SYNC_PHASE_SCHEMA = {
+ 'state': (str, 'Phase state, e.g. completed, in-progress, or waiting'),
+ 'duration': (str, 'Phase duration'),
+}
+
+MIRROR_SYNC_BYTES_PROGRESS_SCHEMA = {
+ 'sync_bytes': (str, 'Bytes synced so far'),
+ 'total_bytes': (str, 'Total bytes to sync'),
+ 'sync_percent': (str, 'Sync completion percentage'),
+}
+
+MIRROR_SYNC_FILES_PROGRESS_SCHEMA = {
+ 'sync_files': (str, 'Files synced so far'),
+ 'total_files': (str, 'Total files to sync'),
+ 'sync_percent': (str, 'Sync completion percentage'),
+}
+
+MIRROR_LAST_SYNCED_SNAP_SCHEMA = {
+ 'id': (int, 'Snapshot ID'),
+ 'name': (str, 'Snapshot name'),
+ 'crawl_duration': (str, 'Time taken to scan directory'),
+ 'datasync_queue_wait_duration': (str, 'Time in data sync queue'),
+ 'sync_duration': (str, 'Snapshot sync duration'),
+ 'sync_time_stamp': (str, 'Time of the last sync'),
+ 'sync_bytes': (str, 'Bytes synced for the snapshot'),
+ 'sync_files': (int, 'Number of files synced for the snapshot'),
+}
+
+MIRROR_CURRENT_SYNCING_SNAP_SCHEMA = {
+ 'id': (int, 'Snapshot ID'),
+ 'name': (str, 'Snapshot name'),
+ 'sync-mode': (str, 'Snapshot sync mode: full or delta'),
+ 'avg_read_throughput_bytes': (str, 'Average read throughput'),
+ 'avg_write_throughput_bytes': (str, 'Average write throughput'),
+ 'crawl': (MIRROR_SYNC_PHASE_SCHEMA, 'Directory crawl progress'),
+ 'datasync_queue_wait': (MIRROR_SYNC_PHASE_SCHEMA, 'Data sync queue wait progress'),
+ 'bytes': (MIRROR_SYNC_BYTES_PROGRESS_SCHEMA, 'Byte sync progress'),
+ 'files': (MIRROR_SYNC_FILES_PROGRESS_SCHEMA, 'File sync progress'),
+ 'eta': (str, 'Estimated time remaining for the current sync'),
+}
+
+MIRROR_PEER_SYNC_STAT_SCHEMA = {
+ 'state': (str, 'Mirror sync state: idle, syncing, stale, or failed'),
+ 'failure_reason': (str, 'Last sync failure reason when state is failed'),
+ 'current_syncing_snap': (MIRROR_CURRENT_SYNCING_SNAP_SCHEMA,
+ 'Snapshot currently being synchronized'),
+ 'last_synced_snap': (MIRROR_LAST_SYNCED_SNAP_SCHEMA,
+ 'Last successfully synchronized snapshot'),
+ 'snaps_synced': (int, 'Total number of snapshots synchronized'),
+ 'snaps_deleted': (int, 'Total number of snapshots deleted on the peer'),
+ 'snaps_renamed': (int, 'Total number of snapshots renamed on the peer'),
+ 'metrics_updated_at': (float, 'Wall-clock time when metrics were last updated'),
+}
+
+MIRROR_DIR_METRICS_SCHEMA = {
+ 'peer': ({
+ 'peer_uuid': (MIRROR_PEER_SYNC_STAT_SCHEMA,
+ 'Sync statistics keyed by peer UUID'),
+ }, 'Peer sync statistics for the mirrored directory'),
+}
+
+MIRROR_STATUS_SCHEMA = {
+ 'metrics': ({
+ '/dir_path': (MIRROR_DIR_METRICS_SCHEMA,
+ 'Mirror directory metrics keyed by absolute path'),
+ }, 'Snapshot mirror sync metrics grouped by mirrored directory path'),
+}
+
# pylint: disable=R0904
@APIRouter('/cephfs', Scope.CEPHFS)
)
return json.loads(out)
- @Endpoint('GET', path='/snapshot-mirror-status')
+ @EndpointDoc("Get snapshot mirror sync metrics",
+ parameters={
+ 'fs_name': (str, 'File system name'),
+ 'path': (str, 'Mirrored directory path'),
+ 'peer_id': (str, 'Peer UUID'),
+ },
+ responses={200: MIRROR_STATUS_SCHEMA})
+ @Endpoint('GET', path='/{fs_name}/status')
@ReadPermission
- def snapshot_mirror_status(self, fs_name: str, mirrored_dir_path=None, peer_uuid=None):
- error_code, out, err = mgr.remote('mirroring', 'snapshot_mirror_status',
- fs_name, mirrored_dir_path, peer_uuid)
+ def mirror_fs_status(self, fs_name: str, path: str = '', peer_id: str = ''):
+ error_code, out, err = mgr.remote(
+ 'mirroring', 'snapshot_mirror_status', fs_name,
+ path or None, peer_id or None)
if error_code != 0:
raise DashboardException(
- msg=f'Failed to get Cephfs snapshot mirror status: {err}',
+ msg=f'Failed to get Cephfs mirror status: {err}',
code=error_code,
component='cephfs.mirror'
)
import { ActivatedRoute, convertToParamMap } from '@angular/router';
import { of, throwError } from 'rxjs';
-import { CephfsService, SnapshotMirrorStatusResponse } from '~/app/shared/api/cephfs.service';
+import { CephfsService } from '~/app/shared/api/cephfs.service';
+import { MirrorStatusResponse } from '~/app/shared/models/cephfs.model';
import { FormatterService } from '~/app/shared/services/formatter.service';
import { CephfsMirroringFsMirrorPathsComponent } from './cephfs-mirroring-fs-mirror-paths.component';
let cephfsService: any;
let formatterService: any;
- const mockSnapshotMirrorStatusResponse: SnapshotMirrorStatusResponse = {
+ const mockMirrorStatusResponse: MirrorStatusResponse = {
metrics: {
'/path1': {
peer: {
beforeEach(async () => {
const cephfsServiceMock = {
- getSnapshotMirrorStatus: jest.fn()
+ getMirrorStatus: jest.fn()
};
const formatterServiceMock = {
});
it('should initialize columns and fetch fsName on init', () => {
- cephfsService.getSnapshotMirrorStatus.mockReturnValue(of(mockSnapshotMirrorStatusResponse));
+ cephfsService.getMirrorStatus.mockReturnValue(of(mockMirrorStatusResponse));
component.ngOnInit();
// Verify fsName is fetched and data is loaded
expect(component.fsName).toBe('test-fs');
- expect(cephfsService.getSnapshotMirrorStatus).toHaveBeenCalledWith('test-fs');
+ expect(cephfsService.getMirrorStatus).toHaveBeenCalledWith('test-fs');
});
describe('parseMirrorStatus', () => {
});
it('should parse mirror status with current_syncing_snap structure', () => {
- const result = component.parseMirrorStatus(mockSnapshotMirrorStatusResponse);
+ const result = component.parseMirrorStatus(mockMirrorStatusResponse);
expect(result.length).toBe(2);
});
it('should skip paths without peer data', () => {
- const dataWithoutPeer: SnapshotMirrorStatusResponse = {
+ const dataWithoutPeer: MirrorStatusResponse = {
metrics: {
'/path1': {} as any
}
});
it('should use default values when optional fields are missing', () => {
- const minimalData: SnapshotMirrorStatusResponse = {
+ const minimalData: MirrorStatusResponse = {
metrics: {
'/path1': {
peer: {
describe('loadMirrorPaths', () => {
it('should load mirror paths successfully', () => {
- cephfsService.getSnapshotMirrorStatus.mockReturnValue(of(mockSnapshotMirrorStatusResponse));
+ cephfsService.getMirrorStatus.mockReturnValue(of(mockMirrorStatusResponse));
component.fsName = 'test-fs';
component.loadMirrorPaths();
- expect(cephfsService.getSnapshotMirrorStatus).toHaveBeenCalledWith('test-fs');
+ expect(cephfsService.getMirrorStatus).toHaveBeenCalledWith('test-fs');
expect(component.mirrorPaths.length).toBe(2);
});
it('should set empty array on error', () => {
- cephfsService.getSnapshotMirrorStatus.mockReturnValue(
- throwError(() => new Error('API Error'))
- );
+ cephfsService.getMirrorStatus.mockReturnValue(throwError(() => new Error('API Error')));
component.fsName = 'test-fs';
component.loadMirrorPaths();
component.loadMirrorPaths();
- expect(cephfsService.getSnapshotMirrorStatus).not.toHaveBeenCalled();
+ expect(cephfsService.getMirrorStatus).not.toHaveBeenCalled();
});
});
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
-import { CephfsService, SnapshotMirrorStatusResponse } from '~/app/shared/api/cephfs.service';
+import { CephfsService } from '~/app/shared/api/cephfs.service';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
import { ICON_TYPE } from '~/app/shared/enum/icons.enum';
import { FormatterService } from '~/app/shared/services/formatter.service';
+import { MirrorStatusResponse } from '~/app/shared/models/cephfs.model';
interface MirrorPath {
path: string;
return;
}
- this.cephfsService.getSnapshotMirrorStatus(this.fsName).subscribe(
- (data: SnapshotMirrorStatusResponse) => {
+ this.cephfsService.getMirrorStatus(this.fsName).subscribe(
+ (data: MirrorStatusResponse) => {
this.mirrorPaths = this.parseMirrorStatus(data);
if (this.selectedPath) {
this.selectedPath =
);
}
- parseMirrorStatus(data: SnapshotMirrorStatusResponse): MirrorPath[] {
+ parseMirrorStatus(data: MirrorStatusResponse): MirrorPath[] {
if (!data?.metrics) {
return [];
}
-<main class="cephfs-mirroring-fs-schedules">
- <cds-tile>
- <p class="cds--type-body-01"
- i18n>Overview content will be available here.</p>
- </cds-tile>
-</main>
+@let fsData = (fsData$ | async);
+<cds-tile class="cds-mb-5">
+ <div cdsGrid
+ [fullWidth]="true"
+ [narrow]="true">
+ <div cdsRow>
+ <div cdsCol
+ cdsStack="vertical"
+ [gap]="3"
+ class="border-subtle-right"
+ [columnNumbers]="{lg: 3, md: 3, sm: 4}">
+ <span class="cds--type-label-01"
+ i18n>Mirror paths</span>
+ <span class="cds--type-heading-06">{{ fsData?.stats?.mirrorPaths }}</span>
+ </div>
+ <div cdsCol
+ class="cds-pl-6 border-subtle-right cds-center-inline-content"
+ [columnNumbers]="{lg: 3, md: 3, sm: 4}">
+ <div cdsStack="vertical"
+ [gap]="3"
+ class="cds-text-start">
+ <span class="cds--type-label-01"
+ i18n>Syncing paths</span>
+ <span class="cds--type-heading-06">{{ fsData?.stats?.syncingPaths }}</span>
+ </div>
+ </div>
+ <div cdsCol
+ class="cds-pl-7 border-subtle-right"
+ [columnNumbers]="{lg: 3, md: 3, sm: 4}">
+ <div cdsStack="vertical"
+ [gap]="3"
+ class="cds-text-start">
+ <span class="cds--type-label-01"
+ i18n>Last successful sync</span>
+ <span class="cds--type-heading-03">
+ @if (fsData?.sync?.syncedAt) {
+ {{ fsData.sync.path }} {{ fsData.sync.snapName }}: {{ fsData.sync.syncedAt | relativeDate }}
+ } @else if (fsData?.sync?.path || fsData?.sync?.snapName) {
+ {{ fsData.sync.path }} {{ fsData.sync.snapName }}
+ } @else {
+ -
+ }
+ </span>
+ </div>
+ </div>
+ <div cdsCol
+ class="cds-pl-6 cds-center-inline-content"
+ [columnNumbers]="{lg: 3, md: 3, sm: 4}">
+ <div cdsStack="vertical"
+ [gap]="3"
+ class="cds-text-start">
+ <span class="cds--type-label-01"
+ i18n>Failures</span>
+ <span cdsStack="horizontal"
+ [gap]="2"
+ class="vertical-align">
+ @if ((fsData?.stats?.failures ?? 0) > 0) {
+ <cd-icon type="error"
+ [size]="iconSize.size20"></cd-icon>
+ }
+ <span class="cds--type-heading-06">{{ fsData?.stats?.failures }}</span>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+</cds-tile>
+
+<cds-tile class="cds-mb-5">
+ <div cdsGrid
+ [fullWidth]="true"
+ [narrow]="true">
+ <div cdsRow
+ class="cds-mb-5">
+ <div cdsCol
+ [columnNumbers]="{lg: 16, md: 8, sm: 4}">
+ <h4 class="cds--type-heading-03 cds-m-0"
+ i18n>Destination cluster</h4>
+ </div>
+ </div>
+ <div cdsRow
+ class="cds-mb-5">
+ <div cdsCol
+ cdsStack="vertical"
+ [gap]="1"
+ [columnNumbers]="{lg: 4, md: 4, sm: 4}">
+ <span class="cds--type-label-01"
+ i18n>Cluster name</span>
+ <span class="cds--type-body-compact-01">{{ fsData?.destination?.clusterName }}</span>
+ </div>
+ <div cdsCol
+ cdsStack="vertical"
+ [gap]="1"
+ [columnNumbers]="{lg: 4, md: 4, sm: 4}">
+ <span class="cds--type-label-01"
+ i18n>Filesystem name</span>
+ <span class="cds--type-body-compact-01">{{ fsData?.destination?.destinationFsName }}</span>
+ </div>
+ </div>
+ <div cdsRow
+ class="cds-mb-5">
+ <div cdsCol
+ cdsStack="vertical"
+ [gap]="1"
+ [columnNumbers]="{lg: 4, md: 4, sm: 4}">
+ <span class="cds--type-label-01"
+ i18n>FSID</span>
+ <span class="cds--type-body-compact-01">{{ fsData?.destination?.fsid }}</span>
+ </div>
+ <div cdsCol
+ cdsStack="vertical"
+ [gap]="1"
+ [columnNumbers]="{lg: 4, md: 4, sm: 4}">
+ <span class="cds--type-label-01"
+ i18n>Monitor endpoint</span>
+ <span class="cds--type-body-compact-01">{{ fsData?.destination?.monitorEndpoint }}</span>
+ </div>
+ </div>
+ <div cdsRow>
+ <div cdsCol
+ cdsStack="vertical"
+ [gap]="1"
+ [columnNumbers]="{lg: 4, md: 4, sm: 4}">
+ <span class="cds--type-label-01"
+ i18n>Bytes synced</span>
+ <span class="cds--type-body-compact-01">{{ fsData?.sync?.bytesSynced }}</span>
+ </div>
+ <div cdsCol
+ cdsStack="vertical"
+ [gap]="1"
+ [columnNumbers]="{lg: 4, md: 4, sm: 4}">
+ <span class="cds--type-label-01"
+ i18n>Last synced</span>
+ <span class="cds--type-body-compact-01">
+ @if (fsData?.sync?.syncedAt) {
+ {{ fsData.sync.syncedAt | relativeDate }}
+ } @else {
+ -
+ }
+ </span>
+ </div>
+ </div>
+ </div>
+</cds-tile>
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, convertToParamMap } from '@angular/router';
-import { of } from 'rxjs';
+import { BehaviorSubject, of } from 'rxjs';
+import { take } from 'rxjs/operators';
+
+import { CephfsService } from '~/app/shared/api/cephfs.service';
+import {
+ Daemon,
+ DaemonOverviewInfo,
+ MirroringFsOverviewData,
+ MirroringFsSyncInfo
+} from '~/app/shared/models/cephfs.model';
+import { RelativeDatePipe } from '~/app/shared/pipes/relative-date.pipe';
+import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
import { CephfsMirroringFsOverviewComponent } from './cephfs-mirroring-fs-overview.component';
describe('CephfsMirroringFsOverviewComponent', () => {
let component: CephfsMirroringFsOverviewComponent;
let fixture: ComponentFixture<CephfsMirroringFsOverviewComponent>;
+ let cephfsServiceMock: {
+ listDaemonStatus: jest.Mock;
+ listMirrorPeers: jest.Mock;
+ getMirrorStatus: jest.Mock;
+ };
+ let refreshInterval$: BehaviorSubject<null>;
+
+ const mockDaemonStatus: Daemon[] = [
+ {
+ daemon_id: 1,
+ filesystems: [
+ {
+ filesystem_id: 1,
+ id: '1',
+ name: 'myfs',
+ directory_count: 5,
+ peers: [
+ {
+ uuid: 'peer-uuid',
+ remote: {
+ client_name: 'client.mirror',
+ cluster_name: 'remote-cluster',
+ fs_name: 'remote_fs',
+ fsid: 'abc-123',
+ mon_host: '10.0.0.1:6789'
+ },
+ stats: {
+ failure_count: 2,
+ recovery_count: 1
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ];
+
+ const mockMirrorStatus = {
+ metrics: {
+ '/dir1': {
+ peer: {
+ 'peer-uuid': {
+ state: 'idle',
+ last_synced_snap: {
+ name: 'snap1',
+ sync_bytes: '1.00 KiB',
+ sync_time_stamp: '9000.000000s'
+ },
+ metrics_updated_at: 1_700_000_000
+ }
+ }
+ },
+ '/dir2': {
+ peer: {
+ 'peer-uuid': {
+ state: 'syncing'
+ }
+ }
+ }
+ }
+ };
beforeEach(async () => {
+ refreshInterval$ = new BehaviorSubject<null>(null);
+ cephfsServiceMock = {
+ listDaemonStatus: jest.fn().mockReturnValue(of(mockDaemonStatus)),
+ listMirrorPeers: jest.fn().mockReturnValue(
+ of({
+ 'peer-uuid': {
+ client_name: 'client.mirror',
+ site_name: 'remote-site',
+ fs_name: 'remote_fs'
+ }
+ })
+ ),
+ getMirrorStatus: jest.fn().mockReturnValue(of(mockMirrorStatus))
+ };
+
await TestBed.configureTestingModule({
- declarations: [CephfsMirroringFsOverviewComponent],
+ declarations: [CephfsMirroringFsOverviewComponent, RelativeDatePipe],
providers: [
{
provide: ActivatedRoute,
paramMap: of(convertToParamMap({ fsName: 'myfs' }))
}
}
+ },
+ { provide: CephfsService, useValue: cephfsServiceMock },
+ {
+ provide: RefreshIntervalService,
+ useValue: { intervalData$: refreshInterval$.asObservable() }
}
],
schemas: [NO_ERRORS_SCHEMA]
component = fixture.componentInstance;
fixture.detectChanges();
+ return fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
- it('should read fsName from parent route params', () => {
- expect(component.fsName).toBe('myfs');
+ it('should read fsName from parent route params', async () => {
+ const fsData = await component.fsData$.pipe(take(1)).toPromise();
+ expect(fsData?.fsName).toBe('myfs');
+ });
+
+ it('should populate fsData from daemon and mirror status', async () => {
+ const fsData = await component.fsData$.pipe(take(1)).toPromise();
+
+ expect(cephfsServiceMock.listDaemonStatus).toHaveBeenCalled();
+ expect(cephfsServiceMock.listMirrorPeers).toHaveBeenCalledWith('myfs');
+ expect(cephfsServiceMock.getMirrorStatus).toHaveBeenCalledWith('myfs', undefined, 'peer-uuid');
+ expect(fsData?.stats.mirrorPaths).toBe(5);
+ expect(fsData?.stats.failures).toBe(2);
+ expect(fsData?.destination.clusterName).toBe('remote-cluster');
+ expect(fsData?.destination.destinationFsName).toBe('remote_fs');
+ expect(fsData?.destination.fsid).toBe('abc-123');
+ expect(fsData?.destination.monitorEndpoint).toBe('10.0.0.1:6789');
+ expect(fsData?.destination.siteName).toBe('remote-site');
+ expect(fsData?.stats.syncingPaths).toBe(1);
+ expect(fsData?.sync.bytesSynced).toBe('1.00 KiB');
+ expect(fsData?.sync.path).toBe('/dir1');
+ expect(fsData?.sync.snapName).toBe('snap1');
+ expect(fsData?.sync.syncedAt).toBe(1_700_000_000);
+ });
+
+ it('should refresh overview data on interval tick', async () => {
+ await component.fsData$.pipe(take(1)).toPromise();
+ expect(cephfsServiceMock.listDaemonStatus).toHaveBeenCalledTimes(1);
+
+ refreshInterval$.next(null);
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(cephfsServiceMock.listDaemonStatus).toHaveBeenCalledTimes(2);
+ expect(cephfsServiceMock.listMirrorPeers).toHaveBeenCalledTimes(2);
+ expect(cephfsServiceMock.getMirrorStatus).toHaveBeenCalledTimes(2);
+ });
+});
+
+describe('CephfsMirroringFsOverviewComponent helpers', () => {
+ let component: CephfsMirroringFsOverviewComponent;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [CephfsMirroringFsOverviewComponent],
+ providers: [
+ {
+ provide: ActivatedRoute,
+ useValue: { parent: { paramMap: of(convertToParamMap({ fsName: 'myfs' })) } }
+ },
+ {
+ provide: CephfsService,
+ useValue: {
+ listDaemonStatus: jest.fn(),
+ listMirrorPeers: jest.fn(),
+ getMirrorStatus: jest.fn()
+ }
+ },
+ { provide: RefreshIntervalService, useValue: { intervalData$: of(null) } }
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ });
+ component = TestBed.createComponent(CephfsMirroringFsOverviewComponent).componentInstance;
+ });
+
+ const call = <T>(method: string, ...args: unknown[]): T =>
+ ((component as unknown) as Record<string, (...a: unknown[]) => T>)[method](...args);
+
+ it('getDaemonOverviewInfo returns defaults when filesystem is missing', () => {
+ const info = call<DaemonOverviewInfo>('getDaemonOverviewInfo', [], 'missing');
+ expect(info.mirrorPaths).toBe(0);
+ expect(info.peerUuid).toBeUndefined();
+ });
+
+ it('getDaemonOverviewInfo maps daemon peer data', () => {
+ const info = call<DaemonOverviewInfo>(
+ 'getDaemonOverviewInfo',
+ [
+ {
+ filesystems: [
+ {
+ name: 'myfs',
+ directory_count: 3,
+ peers: [
+ {
+ uuid: 'peer-1',
+ remote: { cluster_name: 'remote', fs_name: 'dest', fsid: 'id', mon_host: 'mon' },
+ stats: { failure_count: 1 }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ 'myfs'
+ );
+
+ expect(info.mirrorPaths).toBe(3);
+ expect(info.failures).toBe(1);
+ expect(info.peerUuid).toBe('peer-1');
+ expect(info.clusterName).toBe('remote');
+ });
+
+ it('buildMirroringFsOverviewData uses empty sync when status is null', () => {
+ const data = call<MirroringFsOverviewData>(
+ 'buildMirroringFsOverviewData',
+ 'myfs',
+ {
+ mirrorPaths: 2,
+ failures: 0,
+ clusterName: 'c',
+ destinationFsName: 'd',
+ fsid: 'f',
+ monitorEndpoint: 'm',
+ peerUuid: 'peer-1'
+ },
+ { 'peer-1': { site_name: 'site-a' } },
+ null
+ );
+
+ expect(data.stats.syncingPaths).toBe(0);
+ expect(data.destination.siteName).toBe('site-a');
+ expect(data.sync.bytesSynced).toBe('-');
+ });
+
+ it('extractLatestSync counts syncing paths and picks latest snapshot', () => {
+ const sync = call<{ syncingPaths: number; info: MirroringFsSyncInfo }>('extractLatestSync', {
+ metrics: {
+ '/old': {
+ peer: {
+ p1: {
+ state: 'idle',
+ last_synced_snap: { name: 'old', sync_bytes: '1 B', sync_time_stamp: '1s' },
+ metrics_updated_at: 100
+ }
+ }
+ },
+ '/new': {
+ peer: {
+ p2: {
+ state: 'syncing',
+ last_synced_snap: { name: 'new', sync_bytes: '2 B', sync_time_stamp: '2s' },
+ metrics_updated_at: 200
+ }
+ }
+ }
+ }
+ });
+
+ expect(sync.syncingPaths).toBe(1);
+ expect(sync.info.snapName).toBe('new');
+ expect(sync.info.path).toBe('/new');
+ expect(sync.info.syncedAt).toBe(200);
+ });
+
+ it('isNewerMirrorSync prefers metrics_updated_at over sync timestamp', () => {
+ expect(call<boolean>('isNewerMirrorSync', '1s', 300, '9s', 200)).toBe(true);
+ expect(call<boolean>('isNewerMirrorSync', '9s', 100, '1s', 200)).toBe(false);
+ });
+
+ it('mirrorMetricsUpdatedAtToEpoch parses valid values and rejects invalid ones', () => {
+ expect(call<number | null>('mirrorMetricsUpdatedAtToEpoch', 1_700_000_000.9)).toBe(
+ 1_700_000_000
+ );
+ expect(call<number | null>('mirrorMetricsUpdatedAtToEpoch', '1234.5')).toBe(1234);
+ expect(call<number | null>('mirrorMetricsUpdatedAtToEpoch', '')).toBeNull();
+ expect(call<number | null>('mirrorMetricsUpdatedAtToEpoch', 0)).toBeNull();
});
});
-import { Component, OnInit, ViewEncapsulation } from '@angular/core';
-import { ActivatedRoute, ParamMap } from '@angular/router';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ DestroyRef,
+ inject,
+ ViewEncapsulation
+} from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { ActivatedRoute, convertToParamMap, ParamMap } from '@angular/router';
+import { EMPTY, forkJoin, Observable, of } from 'rxjs';
+import { catchError, exhaustMap, map, shareReplay, switchMap } from 'rxjs/operators';
+
+import { CephfsService } from '~/app/shared/api/cephfs.service';
+import {
+ Daemon,
+ DaemonOverviewInfo,
+ MirroringFsOverviewData,
+ MirroringFsSyncInfo,
+ MirrorPeerList,
+ MirrorStatusResponse
+} from '~/app/shared/models/cephfs.model';
+import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
+import { IconSize } from '~/app/shared/enum/icons.enum';
@Component({
selector: 'cd-cephfs-mirroring-fs-overview',
templateUrl: './cephfs-mirroring-fs-overview.component.html',
styleUrls: ['./cephfs-mirroring-fs-overview.component.scss'],
standalone: false,
+ changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
-export class CephfsMirroringFsOverviewComponent implements OnInit {
- fsName = '';
+export class CephfsMirroringFsOverviewComponent {
+ private readonly route = inject(ActivatedRoute);
+ private readonly cephfsService = inject(CephfsService);
+ private readonly refreshIntervalService = inject(RefreshIntervalService);
+ private readonly destroyRef = inject(DestroyRef);
+
+ readonly iconSize = IconSize;
+
+ private readonly fsName$ = (this.route.parent?.paramMap ?? of(convertToParamMap({}))).pipe(
+ map((paramMap: ParamMap) => paramMap.get('fsName') ?? '-')
+ );
+
+ readonly fsData$ = this.fsName$.pipe(
+ switchMap((fsName) => this.refreshIntervalObs(() => this.fetchFsData(fsName))),
+ shareReplay({ bufferSize: 1, refCount: true })
+ );
+
+ private refreshIntervalObs<T>(fn: () => Observable<T>): Observable<T> {
+ return this.refreshIntervalService.intervalData$.pipe(
+ exhaustMap(() => fn().pipe(catchError(() => EMPTY))),
+ takeUntilDestroyed(this.destroyRef)
+ );
+ }
+
+ private fetchFsData(fsName: string): Observable<MirroringFsOverviewData> {
+ return forkJoin({
+ daemons: this.cephfsService.listDaemonStatus().pipe(catchError(() => of([] as Daemon[]))),
+ peers: this.cephfsService
+ .listMirrorPeers(fsName)
+ .pipe(catchError(() => of({} as MirrorPeerList)))
+ }).pipe(
+ switchMap(({ daemons, peers }) => {
+ const daemonInfo = this.getDaemonOverviewInfo(daemons, fsName);
+ if (!daemonInfo.peerUuid) {
+ return of(this.buildMirroringFsOverviewData(fsName, daemonInfo, peers, null));
+ }
+
+ return this.cephfsService.getMirrorStatus(fsName, undefined, daemonInfo.peerUuid).pipe(
+ catchError(() => of({} as MirrorStatusResponse)),
+ map((status) => this.buildMirroringFsOverviewData(fsName, daemonInfo, peers, status))
+ );
+ })
+ );
+ }
+
+ private getDaemonOverviewInfo(daemons: Daemon[], fsName: string): DaemonOverviewInfo {
+ const empty: DaemonOverviewInfo = {
+ mirrorPaths: 0,
+ failures: 0,
+ clusterName: '-',
+ destinationFsName: '-',
+ fsid: '-',
+ monitorEndpoint: '-'
+ };
+
+ for (const daemon of daemons) {
+ const fs = daemon.filesystems?.find((filesystem) => filesystem.name === fsName);
+ if (!fs) {
+ continue;
+ }
+
+ const peer = fs.peers?.[0];
+ return {
+ mirrorPaths: fs.directory_count ?? 0,
+ failures: (fs.peers ?? []).reduce((sum, item) => sum + (item.stats?.failure_count ?? 0), 0),
+ clusterName: peer?.remote?.cluster_name ?? '-',
+ destinationFsName: peer?.remote?.fs_name ?? '-',
+ fsid: peer?.remote?.fsid ?? '-',
+ monitorEndpoint: peer?.remote?.mon_host ?? '-',
+ peerUuid: peer?.uuid
+ };
+ }
+
+ return empty;
+ }
+
+ private buildMirroringFsOverviewData(
+ fsName: string,
+ daemonInfo: DaemonOverviewInfo,
+ peers: MirrorPeerList,
+ status: MirrorStatusResponse | null
+ ): MirroringFsOverviewData {
+ const sync = status ? this.extractLatestSync(status) : this.emptySyncInfo();
+
+ return {
+ fsName,
+ stats: {
+ mirrorPaths: daemonInfo.mirrorPaths,
+ failures: daemonInfo.failures,
+ syncingPaths: sync.syncingPaths
+ },
+ destination: {
+ clusterName: daemonInfo.clusterName,
+ siteName: daemonInfo.peerUuid ? peers[daemonInfo.peerUuid]?.site_name ?? '-' : '-',
+ destinationFsName: daemonInfo.destinationFsName,
+ fsid: daemonInfo.fsid,
+ monitorEndpoint: daemonInfo.monitorEndpoint
+ },
+ sync: sync.info
+ };
+ }
+
+ private emptySyncInfo(): { syncingPaths: number; info: MirroringFsSyncInfo } {
+ return {
+ syncingPaths: 0,
+ info: {
+ bytesSynced: '-',
+ path: '',
+ snapName: '',
+ syncedAt: null
+ }
+ };
+ }
+
+ private extractLatestSync(
+ status: MirrorStatusResponse
+ ): {
+ syncingPaths: number;
+ info: MirroringFsSyncInfo;
+ } {
+ let syncingPaths = 0;
+ let latestSyncTime = '';
+ let latestMetricsUpdatedAt: number | string | undefined;
+ let latestSnapName = '';
+ let latestBytes = '';
+ let latestSyncPath = '';
- constructor(private route: ActivatedRoute) {}
+ for (const [dirPath, dirMetrics] of Object.entries(status.metrics ?? {})) {
+ for (const dir of Object.values(dirMetrics.peer ?? {})) {
+ if (dir.state === 'syncing') {
+ syncingPaths++;
+ }
+
+ const snap = dir.last_synced_snap;
+ if (!snap) {
+ continue;
+ }
+
+ const syncTime = String(snap.sync_time_stamp ?? '');
+ const snapName = snap.name ?? '';
+ const metricsUpdatedAt = dir.metrics_updated_at;
+ if (
+ this.isNewerMirrorSync(syncTime, metricsUpdatedAt, latestSyncTime, latestMetricsUpdatedAt)
+ ) {
+ latestSyncTime = syncTime;
+ latestMetricsUpdatedAt = metricsUpdatedAt;
+ latestSnapName = snapName;
+ latestBytes = String(snap.sync_bytes ?? '');
+ latestSyncPath = dirPath;
+ } else if (!latestSnapName && snapName) {
+ latestSnapName = snapName;
+ latestBytes = latestBytes || String(snap.sync_bytes ?? '');
+ latestSyncPath = dirPath;
+ latestMetricsUpdatedAt = latestMetricsUpdatedAt ?? metricsUpdatedAt;
+ }
+ }
+ }
+
+ return {
+ syncingPaths,
+ info: {
+ bytesSynced: latestBytes || '-',
+ path: latestSyncPath,
+ snapName: latestSnapName,
+ syncedAt: this.mirrorMetricsUpdatedAtToEpoch(latestMetricsUpdatedAt)
+ }
+ };
+ }
+
+ private isNewerMirrorSync(
+ syncTime: string,
+ metricsUpdatedAt: number | string | undefined,
+ latestSyncTime: string,
+ latestMetricsUpdatedAt: number | string | undefined
+ ): boolean {
+ const newEpoch = this.mirrorMetricsUpdatedAtToEpoch(metricsUpdatedAt);
+ const latestEpoch = this.mirrorMetricsUpdatedAtToEpoch(latestMetricsUpdatedAt);
+ if (newEpoch !== null && latestEpoch !== null) {
+ return newEpoch >= latestEpoch;
+ }
+ return Boolean(syncTime && syncTime >= latestSyncTime);
+ }
- ngOnInit(): void {
- this.route.parent?.paramMap.subscribe((paramMap: ParamMap) => {
- this.fsName = paramMap.get('fsName') ?? '';
- });
+ private mirrorMetricsUpdatedAtToEpoch(
+ metricsUpdatedAt: number | string | undefined
+ ): number | null {
+ if (metricsUpdatedAt === undefined || metricsUpdatedAt === null || metricsUpdatedAt === '') {
+ return null;
+ }
+ const epoch =
+ typeof metricsUpdatedAt === 'number'
+ ? metricsUpdatedAt
+ : parseFloat(String(metricsUpdatedAt));
+ if (!Number.isFinite(epoch) || epoch <= 0) {
+ return null;
+ }
+ return Math.floor(epoch);
}
}
import { cdEncode, cdEncodeNot } from '../decorators/cd-encode';
import { CephfsDir, CephfsDirStatfs, CephfsQuotas } from '../models/cephfs-directory-models';
import { shareReplay } from 'rxjs/operators';
-import { Daemon } from '../models/cephfs.model';
+import { Daemon, MirrorPeerList, MirrorStatusResponse } from '../models/cephfs.model';
export interface SnapshotMirrorStatusResponse {
metrics: {
});
}
- getSnapshotMirrorStatus(
+ listMirrorPeers(fsName: string): Observable<MirrorPeerList> {
+ return this.http.get<MirrorPeerList>(`${this.baseURL}/mirror/${fsName}`);
+ }
+
+ getMirrorStatus(
fsName: string,
- mirroredDirPath?: string,
- peerUuid?: string
- ): Observable<SnapshotMirrorStatusResponse> {
+ path?: string,
+ peerId?: string
+ ): Observable<MirrorStatusResponse> {
let params = new HttpParams();
- if (mirroredDirPath) {
- params = params.append('mirrored_dir_path', mirroredDirPath);
+ if (path) {
+ params = params.set('path', path);
}
- if (peerUuid) {
- params = params.append('peer_uuid', peerUuid);
+ if (peerId) {
+ params = params.set('peer_id', peerId);
}
- return this.http.get<SnapshotMirrorStatusResponse>(
- `${this.baseURL}/mirror/snapshot-mirror-status/${fsName}`,
- { params }
- );
+ return this.http.get<MirrorStatusResponse>(`${this.baseURL}/mirror/${fsName}/status`, {
+ params
+ });
}
addMirrorDirectory(@cdEncodeNot fsName: string, @cdEncodeNot path: string): Observable<any> {
client_name: string;
cluster_name: string;
fs_name: string;
+ fsid?: string;
+ mon_host?: string;
}
export interface PeerStats {
export type DaemonResponse = Daemon[];
+export interface MirrorPeerListEntry {
+ client_name: string;
+ site_name: string;
+ fs_name: string;
+}
+
+export type MirrorPeerList = Record<string, MirrorPeerListEntry>;
+
+export interface MirrorSyncedSnap {
+ id?: number;
+ name?: string;
+ sync_bytes?: number | string;
+ sync_duration?: number | string;
+ sync_time_stamp?: number | string;
+}
+
+export interface MirrorDirStatus {
+ state?: string;
+ last_synced_snap?: MirrorSyncedSnap;
+ current_syncing_snap?: MirrorSyncedSnap;
+ snaps_synced?: number;
+ metrics_updated_at?: number | string;
+}
+
+export interface MirrorDirMetrics {
+ peer?: Record<string, MirrorDirStatus>;
+}
+
+export type MirrorStatusMetrics = Record<string, MirrorDirMetrics>;
+
+export interface MirrorStatusResponse {
+ metrics?: MirrorStatusMetrics;
+}
+
+export interface DaemonOverviewInfo {
+ mirrorPaths: number;
+ failures: number;
+ clusterName: string;
+ destinationFsName: string;
+ fsid: string;
+ monitorEndpoint: string;
+ peerUuid?: string;
+}
+
+export interface MirroringFsOverviewStats {
+ mirrorPaths: number;
+ syncingPaths: number;
+ failures: number;
+}
+
+export interface MirroringFsDestinationCluster {
+ clusterName: string;
+ siteName: string;
+ destinationFsName: string;
+ fsid: string;
+ monitorEndpoint: string;
+}
+
+export interface MirroringFsSyncInfo {
+ bytesSynced: string;
+ path: string;
+ snapName: string;
+ syncedAt: number | null;
+}
+
+export interface MirroringFsOverviewData {
+ fsName: string;
+ stats: MirroringFsOverviewStats;
+ destination: MirroringFsDestinationCluster;
+ sync: MirroringFsSyncInfo;
+}
+
export type MirroringEntityRow = {
entity: string;
mdsCaps: string;
.cds-pt-6 {
padding-top: layout.$spacing-06;
}
+
+.cds-text-start {
+ text-align: start;
+}
+
+.cds-center-inline-content {
+ display: flex;
+ justify-content: center;
+
+ > .cds--stack-vertical {
+ width: fit-content;
+ text-align: start;
+ }
+}
summary: Enable mirroring for a filesystem
tags:
- CephfsMirror
- /api/cephfs/mirror/snapshot-mirror-status/{fs_name}:
- get:
- parameters:
- - in: path
- name: fs_name
- required: true
- schema:
- type: string
- - allowEmptyValue: true
- in: query
- name: mirrored_dir_path
- schema:
- type: string
- - allowEmptyValue: true
- in: query
- name: peer_uuid
- schema:
- type: string
- responses:
- '200':
- content:
- application/json:
- schema:
- type: object
- application/vnd.ceph.api.v1.0+json:
- schema:
- type: object
- 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: []
- tags:
- - CephfsMirror
/api/cephfs/mirror/token:
post:
parameters: []
summary: Get peers
tags:
- CephfsMirror
+ /api/cephfs/mirror/{fs_name}/status:
+ get:
+ parameters:
+ - description: File system name
+ in: path
+ name: fs_name
+ required: true
+ schema:
+ type: string
+ - default: ''
+ description: Mirrored directory path
+ in: query
+ name: path
+ schema:
+ type: string
+ - default: ''
+ description: Peer UUID
+ in: query
+ name: peer_id
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ properties:
+ metrics:
+ description: Snapshot mirror sync metrics grouped by mirrored
+ directory path
+ properties:
+ /dir_path:
+ description: Mirror directory metrics keyed by absolute path
+ properties:
+ peer:
+ description: Peer sync statistics for the mirrored directory
+ properties:
+ peer_uuid:
+ description: Sync statistics keyed by peer UUID
+ properties:
+ current_syncing_snap:
+ description: Snapshot currently being synchronized
+ properties:
+ avg_read_throughput_bytes:
+ description: Average read throughput
+ type: string
+ avg_write_throughput_bytes:
+ description: Average write throughput
+ type: string
+ bytes:
+ description: Byte sync progress
+ properties:
+ sync_bytes:
+ description: Bytes synced so far
+ type: string
+ sync_percent:
+ description: Sync completion percentage
+ type: string
+ total_bytes:
+ description: Total bytes to sync
+ type: string
+ required: &id024
+ - sync_bytes
+ - total_bytes
+ - sync_percent
+ type: object
+ crawl:
+ description: Directory crawl progress
+ properties:
+ duration:
+ description: Phase duration
+ type: string
+ state:
+ description: Phase state, e.g. completed,
+ in-progress, or waiting
+ type: string
+ required: &id025
+ - state
+ - duration
+ type: object
+ datasync_queue_wait:
+ description: Data sync queue wait progress
+ properties:
+ duration:
+ description: Phase duration
+ type: string
+ state:
+ description: Phase state, e.g. completed,
+ in-progress, or waiting
+ type: string
+ required: &id026
+ - state
+ - duration
+ type: object
+ eta:
+ description: Estimated time remaining for
+ the current sync
+ type: string
+ files:
+ description: File sync progress
+ properties:
+ sync_files:
+ description: Files synced so far
+ type: string
+ sync_percent:
+ description: Sync completion percentage
+ type: string
+ total_files:
+ description: Total files to sync
+ type: string
+ required: &id027
+ - sync_files
+ - total_files
+ - sync_percent
+ type: object
+ id:
+ description: Snapshot ID
+ type: integer
+ name:
+ description: Snapshot name
+ type: string
+ sync-mode:
+ description: 'Snapshot sync mode: full or
+ delta'
+ type: string
+ required: &id028
+ - id
+ - name
+ - sync-mode
+ - avg_read_throughput_bytes
+ - avg_write_throughput_bytes
+ - crawl
+ - datasync_queue_wait
+ - bytes
+ - files
+ - eta
+ type: object
+ failure_reason:
+ description: Last sync failure reason when state
+ is failed
+ type: string
+ last_synced_snap:
+ description: Last successfully synchronized snapshot
+ properties:
+ crawl_duration:
+ description: Time taken to scan directory
+ type: string
+ datasync_queue_wait_duration:
+ description: Time in data sync queue
+ type: string
+ id:
+ description: Snapshot ID
+ type: integer
+ name:
+ description: Snapshot name
+ type: string
+ sync_bytes:
+ description: Bytes synced for the snapshot
+ type: string
+ sync_duration:
+ description: Snapshot sync duration
+ type: string
+ sync_files:
+ description: Number of files synced for the
+ snapshot
+ type: integer
+ sync_time_stamp:
+ description: Time of the last sync
+ type: string
+ required: &id029
+ - id
+ - name
+ - crawl_duration
+ - datasync_queue_wait_duration
+ - sync_duration
+ - sync_time_stamp
+ - sync_bytes
+ - sync_files
+ type: object
+ metrics_updated_at:
+ description: Wall-clock time when metrics were
+ last updated
+ type: number
+ snaps_deleted:
+ description: Total number of snapshots deleted
+ on the peer
+ type: integer
+ snaps_renamed:
+ description: Total number of snapshots renamed
+ on the peer
+ type: integer
+ snaps_synced:
+ description: Total number of snapshots synchronized
+ type: integer
+ state:
+ description: 'Mirror sync state: idle, syncing,
+ stale, or failed'
+ type: string
+ required: &id030
+ - state
+ - failure_reason
+ - current_syncing_snap
+ - last_synced_snap
+ - snaps_synced
+ - snaps_deleted
+ - snaps_renamed
+ - metrics_updated_at
+ type: object
+ required: &id031
+ - peer_uuid
+ type: object
+ required: &id032
+ - peer
+ type: object
+ required: &id033
+ - /dir_path
+ type: object
+ required: &id034
+ - metrics
+ type: object
+ application/vnd.ceph.api.v1.0+json:
+ schema:
+ properties:
+ metrics:
+ description: Snapshot mirror sync metrics grouped by mirrored
+ directory path
+ properties:
+ /dir_path:
+ description: Mirror directory metrics keyed by absolute path
+ properties:
+ peer:
+ description: Peer sync statistics for the mirrored directory
+ properties:
+ peer_uuid:
+ description: Sync statistics keyed by peer UUID
+ properties:
+ current_syncing_snap:
+ description: Snapshot currently being synchronized
+ properties:
+ avg_read_throughput_bytes:
+ description: Average read throughput
+ type: string
+ avg_write_throughput_bytes:
+ description: Average write throughput
+ type: string
+ bytes:
+ description: Byte sync progress
+ properties:
+ sync_bytes:
+ description: Bytes synced so far
+ type: string
+ sync_percent:
+ description: Sync completion percentage
+ type: string
+ total_bytes:
+ description: Total bytes to sync
+ type: string
+ required: *id024
+ type: object
+ crawl:
+ description: Directory crawl progress
+ properties:
+ duration:
+ description: Phase duration
+ type: string
+ state:
+ description: Phase state, e.g. completed,
+ in-progress, or waiting
+ type: string
+ required: *id025
+ type: object
+ datasync_queue_wait:
+ description: Data sync queue wait progress
+ properties:
+ duration:
+ description: Phase duration
+ type: string
+ state:
+ description: Phase state, e.g. completed,
+ in-progress, or waiting
+ type: string
+ required: *id026
+ type: object
+ eta:
+ description: Estimated time remaining for
+ the current sync
+ type: string
+ files:
+ description: File sync progress
+ properties:
+ sync_files:
+ description: Files synced so far
+ type: string
+ sync_percent:
+ description: Sync completion percentage
+ type: string
+ total_files:
+ description: Total files to sync
+ type: string
+ required: *id027
+ type: object
+ id:
+ description: Snapshot ID
+ type: integer
+ name:
+ description: Snapshot name
+ type: string
+ sync-mode:
+ description: 'Snapshot sync mode: full or
+ delta'
+ type: string
+ required: *id028
+ type: object
+ failure_reason:
+ description: Last sync failure reason when state
+ is failed
+ type: string
+ last_synced_snap:
+ description: Last successfully synchronized snapshot
+ properties:
+ crawl_duration:
+ description: Time taken to scan directory
+ type: string
+ datasync_queue_wait_duration:
+ description: Time in data sync queue
+ type: string
+ id:
+ description: Snapshot ID
+ type: integer
+ name:
+ description: Snapshot name
+ type: string
+ sync_bytes:
+ description: Bytes synced for the snapshot
+ type: string
+ sync_duration:
+ description: Snapshot sync duration
+ type: string
+ sync_files:
+ description: Number of files synced for the
+ snapshot
+ type: integer
+ sync_time_stamp:
+ description: Time of the last sync
+ type: string
+ required: *id029
+ type: object
+ metrics_updated_at:
+ description: Wall-clock time when metrics were
+ last updated
+ type: number
+ snaps_deleted:
+ description: Total number of snapshots deleted
+ on the peer
+ type: integer
+ snaps_renamed:
+ description: Total number of snapshots renamed
+ on the peer
+ type: integer
+ snaps_synced:
+ description: Total number of snapshots synchronized
+ type: integer
+ state:
+ description: 'Mirror sync state: idle, syncing,
+ stale, or failed'
+ type: string
+ required: *id030
+ type: object
+ required: *id031
+ type: object
+ required: *id032
+ type: object
+ required: *id033
+ type: object
+ required: *id034
+ type: object
+ 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 snapshot mirror sync metrics
+ tags:
+ - CephfsMirror
/api/cephfs/mirror/{fs_name}/{peer_uuid}:
delete:
parameters:
max_files:
description: ''
type: integer
- required: &id024
+ required: &id035
- max_bytes
- max_files
type: object
max_files:
description: ''
type: integer
- required: *id024
+ required: *id035
type: object
description: OK
'400':
subdirs:
description: ''
type: integer
- required: &id025
+ required: &id036
- bytes
- files
- subdirs
subdirs:
description: ''
type: integer
- required: *id025
+ required: *id036
type: object
description: OK
'400':
description: Config option type
type: string
type: object
- required: &id026
+ required: &id037
- name
- type
- level
description: Config option type
type: string
type: object
- required: *id026
+ required: *id037
type: array
description: OK
'400':
type:
description: Type of Rule
type: integer
- required: &id027
+ required: &id038
- rule_id
- rule_name
- ruleset
type:
description: Type of Rule
type: integer
- required: *id027
+ required: *id038
type: object
description: OK
'400':
description: ''
type: string
type: object
- required: &id028
+ required: &id039
- crush-failure-domain
- k
- m
description: ''
type: string
type: object
- required: *id028
+ required: *id039
type: array
description: OK
'400':
rgw:
description: ''
type: boolean
- required: &id029
+ required: &id040
- rbd
- mirroring
- iscsi
rgw:
description: ''
type: boolean
- required: *id029
+ required: *id040
type: object
description: OK
'400':
instance:
description: grafana instance
type: string
- required: &id030
+ required: &id041
- instance
type: object
application/vnd.ceph.api.v1.0+json:
instance:
description: grafana instance
type: string
- required: *id030
+ required: *id041
type: object
description: OK
'400':
write_op_per_sec:
description: ''
type: integer
- required: &id031
+ required: &id042
- read_bytes_sec
- read_op_per_sec
- recovering_bytes_per_sec
total_used_raw_bytes:
description: ''
type: integer
- required: &id032
+ required: &id043
- total_avail_bytes
- total_bytes
- total_used_raw_bytes
type: object
- required: &id033
+ required: &id044
- stats
type: object
fs_map:
ro_compat:
description: ''
type: string
- required: &id034
+ required: &id045
- compat
- ro_compat
- incompat
up:
description: ''
type: string
- required: &id035
+ required: &id046
- session_autoclose
- balancer
- up
standbys:
description: ''
type: string
- required: &id036
+ required: &id047
- mdsmap
- standbys
type: object
type: array
- required: &id037
+ required: &id048
- filesystems
type: object
health:
status:
description: ''
type: string
- required: &id038
+ required: &id049
- checks
- mutes
- status
up:
description: ''
type: integer
- required: &id039
+ required: &id050
- up
- down
type: object
standbys:
description: ''
type: string
- required: &id040
+ required: &id051
- active_name
- standbys
type: object
mons:
description: ''
type: string
- required: &id041
+ required: &id052
- mons
type: object
quorum:
items:
type: integer
type: array
- required: &id042
+ required: &id053
- monmap
- quorum
type: object
up:
description: ''
type: integer
- required: &id043
+ required: &id054
- in
- up
type: object
type: array
- required: &id044
+ required: &id055
- osds
type: object
pg_info:
num_objects_unfound:
description: ''
type: integer
- required: &id045
+ required: &id056
- num_objects
- num_object_copies
- num_objects_degraded
statuses:
description: ''
type: string
- required: &id046
+ required: &id057
- object_stats
- pgs_per_osd
- statuses
scrub_status:
description: ''
type: string
- required: &id047
+ required: &id058
- client_perf
- df
- fs_map
write_op_per_sec:
description: ''
type: integer
- required: *id031
+ required: *id042
type: object
df:
description: ''
total_used_raw_bytes:
description: ''
type: integer
- required: *id032
+ required: *id043
type: object
- required: *id033
+ required: *id044
type: object
fs_map:
description: ''
ro_compat:
description: ''
type: string
- required: *id034
+ required: *id045
type: object
created:
description: ''
up:
description: ''
type: string
- required: *id035
+ required: *id046
type: object
standbys:
description: ''
type: string
- required: *id036
+ required: *id047
type: object
type: array
- required: *id037
+ required: *id048
type: object
health:
description: ''
status:
description: ''
type: string
- required: *id038
+ required: *id049
type: object
hosts:
description: ''
up:
description: ''
type: integer
- required: *id039
+ required: *id050
type: object
mgr_map:
description: ''
standbys:
description: ''
type: string
- required: *id040
+ required: *id051
type: object
mon_status:
description: ''
mons:
description: ''
type: string
- required: *id041
+ required: *id052
type: object
quorum:
description: ''
items:
type: integer
type: array
- required: *id042
+ required: *id053
type: object
osd_map:
description: ''
up:
description: ''
type: integer
- required: *id043
+ required: *id054
type: object
type: array
- required: *id044
+ required: *id055
type: object
pg_info:
description: ''
num_objects_unfound:
description: ''
type: integer
- required: *id045
+ required: *id056
type: object
pgs_per_osd:
description: ''
statuses:
description: ''
type: string
- required: *id046
+ required: *id057
type: object
pools:
description: ''
scrub_status:
description: ''
type: string
- required: *id047
+ required: *id058
type: object
description: OK
'400':
num_standbys:
description: Standby MDS count
type: integer
- required: &id048
+ required: &id059
- num_active
- num_standbys
type: object
message:
description: Human-readable summary
type: string
- required: &id049
+ required: &id060
- message
- count
type: object
- required: &id050
+ required: &id061
- severity
- summary
- muted
type: object
- required: &id051
+ required: &id062
- <check_name>
type: object
mutes:
status:
description: Overall health status
type: string
- required: &id052
+ required: &id063
- status
- checks
- mutes
num_standbys:
description: Standby manager count
type: integer
- required: &id053
+ required: &id064
- num_active
- num_standbys
type: object
items:
type: integer
type: array
- required: &id054
+ required: &id065
- num_mons
- quorum
type: object
up:
description: Count of iSCSI gateways running
type: integer
- required: &id055
+ required: &id066
- up
- down
type: object
up:
description: Number of OSDs up
type: integer
- required: &id056
+ required: &id067
- in
- up
- num_osds
state_name:
description: Placement group state
type: string
- required: &id057
+ required: &id068
- state_name
- count
type: object
recovering_bytes_per_sec:
description: Total recovery in bytes
type: integer
- required: &id058
+ required: &id069
- pgs_by_state
- num_pools
- num_pgs
- bytes_total
- recovering_bytes_per_sec
type: object
- required: &id059
+ required: &id070
- fsid
- health
- monmap
num_standbys:
description: Standby MDS count
type: integer
- required: *id048
+ required: *id059
type: object
health:
description: Cluster health overview
message:
description: Human-readable summary
type: string
- required: *id049
+ required: *id060
type: object
- required: *id050
+ required: *id061
type: object
- required: *id051
+ required: *id062
type: object
mutes:
description: List of muted check names
status:
description: Overall health status
type: string
- required: *id052
+ required: *id063
type: object
mgrmap:
description: Manager map details
num_standbys:
description: Standby manager count
type: integer
- required: *id053
+ required: *id064
type: object
monmap:
description: Monitor map details
items:
type: integer
type: array
- required: *id054
+ required: *id065
type: object
num_hosts:
description: Count of hosts
up:
description: Count of iSCSI gateways running
type: integer
- required: *id055
+ required: *id066
type: object
num_rgw_gateways:
description: Count of RGW gateway daemons running
up:
description: Number of OSDs up
type: integer
- required: *id056
+ required: *id067
type: object
pgmap:
description: Placement group map details
state_name:
description: Placement group state
type: string
- required: *id057
+ required: *id068
type: object
type: array
recovering_bytes_per_sec:
description: Total recovery in bytes
type: integer
- required: *id058
+ required: *id069
type: object
- required: *id059
+ required: *id070
type: object
description: OK
'400':
type:
description: type of service
type: string
- required: &id060
+ required: &id071
- type
- count
type: object
type:
description: type of service
type: string
- required: &id061
+ required: &id072
- type
- id
type: object
orchestrator:
description: ''
type: boolean
- required: &id062
+ required: &id073
- ceph
- orchestrator
type: object
status:
description: ''
type: string
- required: &id063
+ required: &id074
- hostname
- services
- service_instances
type:
description: type of service
type: string
- required: *id060
+ required: *id071
type: object
type: array
service_type:
type:
description: type of service
type: string
- required: *id061
+ required: *id072
type: object
type: array
sources:
orchestrator:
description: ''
type: boolean
- required: *id062
+ required: *id073
type: object
status:
description: ''
type: string
- required: *id063
+ required: *id074
type: object
description: OK
'400':
IDENTsupport:
description: ''
type: string
- required: &id064
+ required: &id075
- IDENTsupport
- IDENTstatus
- FAILsupport
transport:
description: ''
type: string
- required: &id065
+ required: &id076
- serialNum
- transport
- mediaType
type:
description: ''
type: string
- required: &id066
+ required: &id077
- name
- osd_id
- cluster_name
start:
description: ''
type: string
- required: &id067
+ required: &id078
- start
- sectors
- sectorsize
- human_readable_size
- holders
type: object
- required: &id068
+ required: &id079
- partition_name
type: object
path:
vendor:
description: ''
type: string
- required: &id069
+ required: &id080
- removable
- ro
- vendor
- path
- locked
type: object
- required: &id070
+ required: &id081
- rejected_reasons
- available
- path
name:
description: Hostname
type: string
- required: &id071
+ required: &id082
- name
- addr
- devices
IDENTsupport:
description: ''
type: string
- required: *id064
+ required: *id075
type: object
linkSpeed:
description: ''
transport:
description: ''
type: string
- required: *id065
+ required: *id076
type: object
lvs:
description: ''
type:
description: ''
type: string
- required: *id066
+ required: *id077
type: object
type: array
osd_ids:
start:
description: ''
type: string
- required: *id067
+ required: *id078
type: object
- required: *id068
+ required: *id079
type: object
path:
description: ''
vendor:
description: ''
type: string
- required: *id069
+ required: *id080
type: object
- required: *id070
+ required: *id081
type: object
type: array
labels:
name:
description: Hostname
type: string
- required: *id071
+ required: *id082
type: object
description: OK
'400':
description: username
type: string
type: object
- required: &id072
+ required: &id083
- user
- password
- mutual_user
description: username
type: string
type: object
- required: *id072
+ required: *id083
type: array
description: OK
'400':
type:
description: ''
type: string
- required: &id073
+ required: &id084
- type
- addr
- nonce
type: object
type: array
- required: &id074
+ required: &id085
- addrvec
type: object
channel:
stamp:
description: ''
type: string
- required: &id075
+ required: &id086
- name
- rank
- addrs
items:
type: string
type: array
- required: &id076
+ required: &id087
- clog
- audit_log
type: object
type:
description: ''
type: string
- required: *id073
+ required: *id084
type: object
type: array
- required: *id074
+ required: *id085
type: object
channel:
description: ''
stamp:
description: ''
type: string
- required: *id075
+ required: *id086
type: object
type: array
clog:
items:
type: string
type: array
- required: *id076
+ required: *id087
type: object
description: OK
'400':
type:
description: Type of the option
type: string
- required: &id077
+ required: &id088
- name
- type
- level
- tags
- see_also
type: object
- required: &id078
+ required: &id089
- Option_name
type: object
type: object
- required: &id079
+ required: &id090
- name
- enabled
- always_on
type:
description: Type of the option
type: string
- required: *id077
+ required: *id088
type: object
- required: *id078
+ required: *id089
type: object
type: object
- required: *id079
+ required: *id090
type: array
description: OK
'400':
type:
description: ''
type: string
- required: &id080
+ required: &id091
- type
- addr
- nonce
type: object
type: array
- required: &id081
+ required: &id092
- addrvec
type: object
rank:
items:
type: integer
type: array
- required: &id082
+ required: &id093
- num_sessions
type: object
weight:
description: ''
type: integer
- required: &id083
+ required: &id094
- rank
- name
- public_addrs
release:
description: ''
type: string
- required: &id084
+ required: &id095
- features
- release
- num
release:
description: ''
type: string
- required: &id085
+ required: &id096
- features
- release
- num
release:
description: ''
type: string
- required: &id086
+ required: &id097
- features
- release
- num
release:
description: ''
type: string
- required: &id087
+ required: &id098
- features
- release
- num
type: object
type: array
- required: &id088
+ required: &id099
- mon
- mds
- client
items:
type: integer
type: array
- required: &id089
+ required: &id100
- required_con
- required_mon
- quorum_con
items:
type: string
type: array
- required: &id090
+ required: &id101
- persistent
- optional
type: object
type:
description: ''
type: string
- required: &id091
+ required: &id102
- type
- addr
- nonce
type: object
type: array
- required: &id092
+ required: &id103
- addrvec
type: object
rank:
items:
type: integer
type: array
- required: &id093
+ required: &id104
- num_sessions
type: object
weight:
description: ''
type: integer
- required: &id094
+ required: &id105
- rank
- name
- public_addrs
- stats
type: object
type: array
- required: &id095
+ required: &id106
- epoch
- fsid
- modified
items:
type: string
type: array
- required: &id096
+ required: &id107
- name
- rank
- state
items:
type: integer
type: array
- required: &id097
+ required: &id108
- mon_status
- in_quorum
- out_quorum
type:
description: ''
type: string
- required: *id080
+ required: *id091
type: object
type: array
- required: *id081
+ required: *id092
type: object
rank:
description: ''
items:
type: integer
type: array
- required: *id082
+ required: *id093
type: object
weight:
description: ''
type: integer
- required: *id083
+ required: *id094
type: object
type: array
mon_status:
release:
description: ''
type: string
- required: *id084
+ required: *id095
type: object
type: array
mds:
release:
description: ''
type: string
- required: *id085
+ required: *id096
type: object
type: array
mgr:
release:
description: ''
type: string
- required: *id086
+ required: *id097
type: object
type: array
mon:
release:
description: ''
type: string
- required: *id087
+ required: *id098
type: object
type: array
- required: *id088
+ required: *id099
type: object
features:
description: ''
items:
type: integer
type: array
- required: *id089
+ required: *id100
type: object
monmap:
description: ''
items:
type: string
type: array
- required: *id090
+ required: *id101
type: object
fsid:
description: ''
type:
description: ''
type: string
- required: *id091
+ required: *id102
type: object
type: array
- required: *id092
+ required: *id103
type: object
rank:
description: ''
items:
type: integer
type: array
- required: *id093
+ required: *id104
type: object
weight:
description: ''
type: integer
- required: *id094
+ required: *id105
type: object
type: array
- required: *id095
+ required: *id106
type: object
name:
description: ''
items:
type: string
type: array
- required: *id096
+ required: *id107
type: object
out_quorum:
description: ''
items:
type: integer
type: array
- required: *id097
+ required: *id108
type: object
description: OK
'400':
squash:
description: Client squash policy
type: string
- required: &id098
+ required: &id109
- addresses
- access_type
- squash
user_id:
description: User id
type: string
- required: &id099
+ required: &id110
- name
type: object
path:
type: string
type: array
type: object
- required: &id100
+ required: &id111
- export_id
- path
- cluster_id
squash:
description: Client squash policy
type: string
- required: *id098
+ required: *id109
type: object
type: array
cluster_id:
user_id:
description: User id
type: string
- required: *id099
+ required: *id110
type: object
path:
description: Export path
type: string
type: array
type: object
- required: *id100
+ required: *id111
type: array
description: OK
'400':
squash:
description: Client squash policy
type: string
- required: &id101
+ required: &id112
- addresses
- access_type
- squash
user_id:
description: User id
type: string
- required: &id102
+ required: &id113
- name
type: object
path:
items:
type: string
type: array
- required: &id103
+ required: &id114
- export_id
- path
- cluster_id
squash:
description: Client squash policy
type: string
- required: *id101
+ required: *id112
type: object
type: array
cluster_id:
user_id:
description: User id
type: string
- required: *id102
+ required: *id113
type: object
path:
description: Export path
items:
type: string
type: array
- required: *id103
+ required: *id114
type: object
description: Resource created.
'202':
squash:
description: Client squash policy
type: string
- required: &id104
+ required: &id115
- addresses
- access_type
- squash
user_id:
description: User id
type: string
- required: &id105
+ required: &id116
- name
type: object
path:
items:
type: string
type: array
- required: &id106
+ required: &id117
- export_id
- path
- cluster_id
squash:
description: Client squash policy
type: string
- required: *id104
+ required: *id115
type: object
type: array
cluster_id:
user_id:
description: User id
type: string
- required: *id105
+ required: *id116
type: object
path:
description: Export path
items:
type: string
type: array
- required: *id106
+ required: *id117
type: object
description: OK
'400':
squash:
description: Client squash policy
type: string
- required: &id107
+ required: &id118
- addresses
- access_type
- squash
user_id:
description: User id
type: string
- required: &id108
+ required: &id119
- name
type: object
path:
items:
type: string
type: array
- required: &id109
+ required: &id120
- export_id
- path
- cluster_id
squash:
description: Client squash policy
type: string
- required: *id107
+ required: *id118
type: object
type: array
cluster_id:
user_id:
description: User id
type: string
- required: *id108
+ required: *id119
type: object
path:
description: Export path
items:
type: string
type: array
- required: *id109
+ required: *id120
type: object
description: Resource updated.
'202':
items:
type: string
type: array
- required: &id110
+ required: &id121
- list_of_flags
type: object
application/vnd.ceph.api.v1.0+json:
items:
type: string
type: array
- required: *id110
+ required: *id121
type: object
description: OK
'400':
items:
type: string
type: array
- required: &id111
+ required: &id122
- list_of_flags
type: object
application/vnd.ceph.api.v1.0+json:
items:
type: string
type: array
- required: *id111
+ required: *id122
type: object
description: Resource updated.
'202':
osd:
description: OSD ID
type: integer
- required: &id112
+ required: &id123
- osd
- flags
type: object
osd:
description: OSD ID
type: integer
- required: *id112
+ required: *id123
type: object
description: OK
'400':
items:
type: string
type: array
- required: &id113
+ required: &id124
- added
- removed
- ids
items:
type: string
type: array
- required: *id113
+ required: *id124
type: object
description: Resource updated.
'202':
items:
type: string
type: array
- required: &id114
+ required: &id125
- safe_to_destroy
- active
- missing_stats
items:
type: string
type: array
- required: *id114
+ required: *id125
type: object
description: OK
'400':
value:
description: ''
type: integer
- required: &id115
+ required: &id126
- description
- nick
- type
- units
- value
type: object
- required: &id116
+ required: &id127
- .cache_bytes
type: object
- required: &id117
+ required: &id128
- mon.a
type: object
application/vnd.ceph.api.v1.0+json:
value:
description: ''
type: integer
- required: *id115
+ required: *id126
type: object
- required: *id116
+ required: *id127
type: object
- required: *id117
+ required: *id128
type: object
description: OK
'400':
type:
description: ''
type: string
- required: &id118
+ required: &id129
- type
type: object
hit_set_period:
target_version:
description: ''
type: string
- required: &id119
+ required: &id130
- ready_epoch
- last_epoch_started
- last_epoch_clean
pg_num_min:
description: ''
type: integer
- required: &id120
+ required: &id131
- pg_num_min
- pg_num_max
type: object
description: ''
type: integer
type: object
- required: &id121
+ required: &id132
- pool
- pool_name
- flags
type:
description: ''
type: string
- required: *id118
+ required: *id129
type: object
hit_set_period:
description: ''
target_version:
description: ''
type: string
- required: *id119
+ required: *id130
type: object
min_read_recency_for_promote:
description: ''
pg_num_min:
description: ''
type: integer
- required: *id120
+ required: *id131
type: object
pg_autoscale_mode:
description: ''
description: ''
type: integer
type: object
- required: *id121
+ required: *id132
type: array
description: OK
'400':
description: Zone Group
type: string
type: object
- required: &id122
+ required: &id133
- id
- version
- server_hostname
description: Zone Group
type: string
type: object
- required: *id122
+ required: *id133
type: array
description: OK
'400':
items:
type: string
type: array
- required: &id123
+ required: &id134
- list_of_users
type: object
application/vnd.ceph.api.v1.0+json:
items:
type: string
type: array
- required: *id123
+ required: *id134
type: object
description: OK
'400':
items:
type: string
type: array
- required: &id124
+ required: &id135
- cephfs
type: object
system:
description: ''
type: boolean
type: object
- required: &id125
+ required: &id136
- name
- description
- scopes_permissions
items:
type: string
type: array
- required: *id124
+ required: *id135
type: object
system:
description: ''
type: boolean
type: object
- required: *id125
+ required: *id136
type: array
description: OK
'400':
description: Certificate target (service name or hostname)
type: string
type: object
- required: &id126
+ required: &id137
- cert_name
- scope
- signed_by
description: Certificate target (service name or hostname)
type: string
type: object
- required: *id126
+ required: *id137
type: array
description: OK
'400':
certificate:
description: Root CA certificate in PEM format
type: string
- required: &id127
+ required: &id138
- certificate
type: object
application/vnd.ceph.api.v1.0+json:
certificate:
description: Root CA certificate in PEM format
type: string
- required: *id127
+ required: *id138
type: object
description: OK
'400':
description: Settings Value
type: boolean
type: object
- required: &id128
+ required: &id139
- name
- default
- type
description: Settings Value
type: boolean
type: object
- required: *id128
+ required: *id139
type: array
description: OK
'400':
source_type:
description: resource
type: string
- required: &id129
+ required: &id140
- source_type
- ref
type: object
realm:
description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
type: string
- required: &id130
+ required: &id141
- realm
- join_sources
type: object
count:
description: Number of instances to place
type: integer
- required: &id131
+ required: &id142
- count
type: object
public_addrs:
description: Defines where the system will assign the
managed IPs.
type: string
- required: &id132
+ required: &id143
- address
- destination
type: object
source_type:
description: resource
type: string
- required: &id133
+ required: &id144
- source_type
- ref
type: object
type: array
type: object
- required: &id134
+ required: &id145
- resource_type
- cluster_id
- auth_mode
source_type:
description: resource
type: string
- required: *id129
+ required: *id140
type: object
type: array
realm:
description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
type: string
- required: *id130
+ required: *id141
type: object
intent:
description: Desired state of the resource, e.g., 'present'
count:
description: Number of instances to place
type: integer
- required: *id131
+ required: *id142
type: object
public_addrs:
description: Public Address
description: Defines where the system will assign the
managed IPs.
type: string
- required: *id132
+ required: *id143
type: object
type: array
resource_type:
source_type:
description: resource
type: string
- required: *id133
+ required: *id144
type: object
type: array
type: object
- required: *id134
+ required: *id145
type: array
description: OK
'400':
source_type:
description: resource
type: string
- required: &id135
+ required: &id146
- source_type
- ref
type: object
realm:
description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
type: string
- required: &id136
+ required: &id147
- realm
- join_sources
type: object
count:
description: Number of instances to place
type: integer
- required: &id137
+ required: &id148
- count
type: object
public_addrs:
description: Defines where the system will assign
the managed IPs.
type: string
- required: &id138
+ required: &id149
- address
- destination
type: object
source_type:
description: resource
type: string
- required: &id139
+ required: &id150
- source_type
- ref
type: object
type: array
- required: &id140
+ required: &id151
- resource_type
- cluster_id
- auth_mode
success:
description: Indicates if the operation was successful
type: boolean
- required: &id141
+ required: &id152
- resource
- state
- success
success:
description: Indicates if the overall operation was successful
type: boolean
- required: &id142
+ required: &id153
- results
- success
type: object
source_type:
description: resource
type: string
- required: *id135
+ required: *id146
type: object
type: array
realm:
description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
type: string
- required: *id136
+ required: *id147
type: object
intent:
description: Desired state of the resource, e.g., 'present'
count:
description: Number of instances to place
type: integer
- required: *id137
+ required: *id148
type: object
public_addrs:
description: Public Address
description: Defines where the system will assign
the managed IPs.
type: string
- required: *id138
+ required: *id149
type: object
type: array
resource_type:
source_type:
description: resource
type: string
- required: *id139
+ required: *id150
type: object
type: array
- required: *id140
+ required: *id151
type: object
state:
description: The current state of the resource, e.g.,
success:
description: Indicates if the operation was successful
type: boolean
- required: *id141
+ required: *id152
type: object
type: array
success:
description: Indicates if the overall operation was successful
type: boolean
- required: *id142
+ required: *id153
type: object
description: Resource created.
'202':
source_type:
description: resource
type: string
- required: &id143
+ required: &id154
- source_type
- ref
type: object
realm:
description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
type: string
- required: &id144
+ required: &id155
- realm
- join_sources
type: object
count:
description: Number of instances to place
type: integer
- required: &id145
+ required: &id156
- count
type: object
public_addrs:
description: Defines where the system will assign the managed
IPs.
type: string
- required: &id146
+ required: &id157
- address
- destination
type: object
source_type:
description: resource
type: string
- required: &id147
+ required: &id158
- source_type
- ref
type: object
type: array
- required: &id148
+ required: &id159
- resource_type
- cluster_id
- auth_mode
source_type:
description: resource
type: string
- required: *id143
+ required: *id154
type: object
type: array
realm:
description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
type: string
- required: *id144
+ required: *id155
type: object
intent:
description: Desired state of the resource, e.g., 'present' or
count:
description: Number of instances to place
type: integer
- required: *id145
+ required: *id156
type: object
public_addrs:
description: Public Address
description: Defines where the system will assign the managed
IPs.
type: string
- required: *id146
+ required: *id157
type: object
type: array
resource_type:
source_type:
description: resource
type: string
- required: *id147
+ required: *id158
type: object
type: array
- required: *id148
+ required: *id159
type: object
description: OK
'400':
username:
description: Username for authentication
type: string
- required: &id149
+ required: &id160
- username
- password
type: object
description: ceph.smb.join.auth
type: string
type: object
- required: &id150
+ required: &id161
- resource_type
- auth_id
- intent
username:
description: Username for authentication
type: string
- required: *id149
+ required: *id160
type: object
auth_id:
description: Unique identifier for the join auth resource
description: ceph.smb.join.auth
type: string
type: object
- required: *id150
+ required: *id161
type: array
description: OK
'400':
username:
description: Username for authentication
type: string
- required: &id151
+ required: &id162
- username
- password
type: object
resource_type:
description: ceph.smb.join.auth
type: string
- required: &id152
+ required: &id163
- resource_type
- auth_id
- intent
success:
description: Indicates if the operation was successful
type: boolean
- required: &id153
+ required: &id164
- resource
- state
- success
success:
description: Indicates if the overall operation was successful
type: boolean
- required: &id154
+ required: &id165
- results
- success
type: object
username:
description: Username for authentication
type: string
- required: *id151
+ required: *id162
type: object
auth_id:
description: Unique identifier for the join auth resource
resource_type:
description: ceph.smb.join.auth
type: string
- required: *id152
+ required: *id163
type: object
state:
description: The current state of the resource, e.g.,
success:
description: Indicates if the operation was successful
type: boolean
- required: *id153
+ required: *id164
type: object
type: array
success:
description: Indicates if the overall operation was successful
type: boolean
- required: *id154
+ required: *id165
type: object
description: Resource created.
'202':
username:
description: Username for authentication
type: string
- required: &id155
+ required: &id166
- username
- password
type: object
resource_type:
description: ceph.smb.join.auth
type: string
- required: &id156
+ required: &id167
- resource_type
- auth_id
- intent
username:
description: Username for authentication
type: string
- required: *id155
+ required: *id166
type: object
auth_id:
description: Unique identifier for the join auth resource
resource_type:
description: ceph.smb.join.auth
type: string
- required: *id156
+ required: *id167
type: object
description: OK
'400':
volume:
description: Name of the CephFS file system
type: string
- required: &id157
+ required: &id168
- volume
- path
- provider
write_iops_limit:
description: 'QoS: max write IOPS (0=disabled)'
type: integer
- required: &id158
+ required: &id169
- resource_type
- cluster_id
- share_id
volume:
description: Name of the CephFS file system
type: string
- required: *id157
+ required: *id168
type: object
cluster_id:
description: Unique identifier for the cluster
write_iops_limit:
description: 'QoS: max write IOPS (0=disabled)'
type: integer
- required: *id158
+ required: *id169
type: object
description: OK
'400':
volume:
description: Name of the CephFS file system
type: string
- required: &id159
+ required: &id170
- volume
- path
- provider
write_iops_limit:
description: 'QoS: max write IOPS (0=disabled)'
type: integer
- required: &id160
+ required: &id171
- resource_type
- cluster_id
- share_id
success:
description: Indicates if the operation was successful
type: boolean
- required: &id161
+ required: &id172
- resource
- state
- success
success:
description: Indicates if the overall operation was successful
type: boolean
- required: &id162
+ required: &id173
- results
- success
type: object
volume:
description: Name of the CephFS file system
type: string
- required: *id159
+ required: *id170
type: object
cluster_id:
description: Unique identifier for the cluster
write_iops_limit:
description: 'QoS: max write IOPS (0=disabled)'
type: integer
- required: *id160
+ required: *id171
type: object
state:
description: The current state of the resource, e.g.,
success:
description: Indicates if the operation was successful
type: boolean
- required: *id161
+ required: *id172
type: object
type: array
success:
description: Indicates if the overall operation was successful
type: boolean
- required: *id162
+ required: *id173
type: object
description: Resource created.
'202':
volume:
description: Name of the CephFS file system
type: string
- required: &id163
+ required: &id174
- volume
- path
- provider
write_iops_limit:
description: 'QoS: max write IOPS (0=disabled)'
type: integer
- required: &id164
+ required: &id175
- resource_type
- cluster_id
- share_id
volume:
description: Name of the CephFS file system
type: string
- required: *id163
+ required: *id174
type: object
cluster_id:
description: Unique identifier for the cluster
write_iops_limit:
description: 'QoS: max write IOPS (0=disabled)'
type: integer
- required: *id164
+ required: *id175
type: object
description: OK
'400':
name:
description: The name of the group
type: string
- required: &id165
+ required: &id176
- name
type: object
type: array
password:
description: The password for the user
type: string
- required: &id166
+ required: &id177
- name
- password
type: object
type: array
- required: &id167
+ required: &id178
- users
- groups
type: object
type: object
- required: &id168
+ required: &id179
- resource_type
- users_groups_id
- intent
name:
description: The name of the group
type: string
- required: *id165
+ required: *id176
type: object
type: array
users:
password:
description: The password for the user
type: string
- required: *id166
+ required: *id177
type: object
type: array
- required: *id167
+ required: *id178
type: object
type: object
- required: *id168
+ required: *id179
type: array
description: OK
'400':
username:
description: Username for authentication
type: string
- required: &id169
+ required: &id180
- username
- password
type: object
resource_type:
description: ceph.smb.join.auth
type: string
- required: &id170
+ required: &id181
- resource_type
- auth_id
- intent
success:
description: Indicates if the operation was successful
type: boolean
- required: &id171
+ required: &id182
- resource
- state
- success
description: Indicates if the overall operation was
successful
type: boolean
- required: &id172
+ required: &id183
- results
- success
type: object
success:
description: Indicates if the operation was successful
type: boolean
- required: &id173
+ required: &id184
- resource
- state
- success
success:
description: Indicates if the overall operation was successful
type: boolean
- required: &id174
+ required: &id185
- results
- success
type: object
username:
description: Username for authentication
type: string
- required: *id169
+ required: *id180
type: object
auth_id:
description: Unique identifier for the join
resource_type:
description: ceph.smb.join.auth
type: string
- required: *id170
+ required: *id181
type: object
state:
description: The current state of the resource, e.g.,
success:
description: Indicates if the operation was successful
type: boolean
- required: *id171
+ required: *id182
type: object
type: array
success:
description: Indicates if the overall operation was
successful
type: boolean
- required: *id172
+ required: *id183
type: object
state:
description: The current state of the resource, e.g.,
success:
description: Indicates if the operation was successful
type: boolean
- required: *id173
+ required: *id184
type: object
type: array
success:
description: Indicates if the overall operation was successful
type: boolean
- required: *id174
+ required: *id185
type: object
description: Resource created.
'202':
name:
description: The name of the group
type: string
- required: &id175
+ required: &id186
- name
type: object
type: array
password:
description: The password for the user
type: string
- required: &id176
+ required: &id187
- name
- password
type: object
type: array
- required: &id177
+ required: &id188
- users
- groups
type: object
- required: &id178
+ required: &id189
- resource_type
- users_groups_id
- intent
name:
description: The name of the group
type: string
- required: *id175
+ required: *id186
type: object
type: array
users:
password:
description: The password for the user
type: string
- required: *id176
+ required: *id187
type: object
type: array
- required: *id177
+ required: *id188
type: object
- required: *id178
+ required: *id189
type: object
description: OK
'400':
pool:
description: ''
type: integer
- required: &id179
+ required: &id190
- pool
type: object
name:
success:
description: ''
type: boolean
- required: &id180
+ required: &id191
- name
- metadata
- begin_time
warnings:
description: ''
type: integer
- required: &id181
+ required: &id192
- warnings
- errors
type: object
version:
description: ''
type: string
- required: &id182
+ required: &id193
- health_status
- mgr_id
- mgr_host
pool:
description: ''
type: integer
- required: *id179
+ required: *id190
type: object
name:
description: ''
success:
description: ''
type: boolean
- required: *id180
+ required: *id191
type: object
type: array
have_mon_connection:
warnings:
description: ''
type: integer
- required: *id181
+ required: *id192
type: object
version:
description: ''
type: string
- required: *id182
+ required: *id193
type: object
description: OK
'400':
pool:
description: ''
type: integer
- required: &id183
+ required: &id194
- pool
type: object
name:
success:
description: ''
type: boolean
- required: &id184
+ required: &id195
- name
- metadata
- begin_time
- exception
type: object
type: array
- required: &id185
+ required: &id196
- executing_tasks
- finished_tasks
type: object
pool:
description: ''
type: integer
- required: *id183
+ required: *id194
type: object
name:
description: finished tasks name
success:
description: ''
type: boolean
- required: *id184
+ required: *id195
type: object
type: array
- required: *id185
+ required: *id196
type: object
description: OK
'400':
mode:
description: ''
type: string
- required: &id186
+ required: &id197
- active
- mode
type: object
items:
type: string
type: array
- required: &id187
+ required: &id198
- cluster_changed
- active_changed
type: object
straw2:
description: ''
type: integer
- required: &id188
+ required: &id199
- straw2
type: object
bucket_sizes:
'3':
description: ''
type: integer
- required: &id189
+ required: &id200
- '1'
- '3'
type: object
'11':
description: ''
type: integer
- required: &id190
+ required: &id201
- '1'
- '11'
type: object
straw_calc_version:
description: ''
type: integer
- required: &id191
+ required: &id202
- choose_local_tries
- choose_local_fallback_tries
- choose_total_tries
- require_feature_tunables5
- has_v5_rules
type: object
- required: &id192
+ required: &id203
- num_devices
- num_types
- num_buckets
ever_enabled_multiple:
description: ''
type: boolean
- required: &id193
+ required: &id204
- enable_multiple
- ever_enabled_multiple
type: object
total_num_mds:
description: ''
type: integer
- required: &id194
+ required: &id205
- count
- feature_flags
- num_standby_mds
num_with_osd:
description: ''
type: integer
- required: &id195
+ required: &id206
- num
- num_with_mon
- num_with_mds
x86_64:
description: ''
type: integer
- required: &id196
+ required: &id207
- x86_64
type: object
ceph_version:
ceph version 16.0.0-3151-gf202994fcf:
description: ''
type: integer
- required: &id197
+ required: &id208
- ceph version 16.0.0-3151-gf202994fcf
type: object
cpu:
Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz:
description: ''
type: integer
- required: &id198
+ required: &id209
- Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz
type: object
distro:
centos:
description: ''
type: integer
- required: &id199
+ required: &id210
- centos
type: object
distro_description:
CentOS Linux 8 (Core):
description: ''
type: integer
- required: &id200
+ required: &id211
- CentOS Linux 8 (Core)
type: object
kernel_description:
'#1 SMP Wed Jul 1 19:53:01 UTC 2020':
description: ''
type: integer
- required: &id201
+ required: &id212
- '#1 SMP Wed Jul 1 19:53:01 UTC 2020'
type: object
kernel_version:
5.7.7-200.fc32.x86_64:
description: ''
type: integer
- required: &id202
+ required: &id213
- 5.7.7-200.fc32.x86_64
type: object
os:
Linux:
description: ''
type: integer
- required: &id203
+ required: &id214
- Linux
type: object
- required: &id204
+ required: &id215
- arch
- ceph_version
- os
x86_64:
description: ''
type: integer
- required: &id205
+ required: &id216
- x86_64
type: object
ceph_version:
ceph version 16.0.0-3151-gf202994fcf:
description: ''
type: integer
- required: &id206
+ required: &id217
- ceph version 16.0.0-3151-gf202994fcf
type: object
cpu:
Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz:
description: ''
type: integer
- required: &id207
+ required: &id218
- Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz
type: object
distro:
centos:
description: ''
type: integer
- required: &id208
+ required: &id219
- centos
type: object
distro_description:
CentOS Linux 8 (Core):
description: ''
type: integer
- required: &id209
+ required: &id220
- CentOS Linux 8 (Core)
type: object
kernel_description:
'#1 SMP Wed Jul 1 19:53:01 UTC 2020':
description: ''
type: integer
- required: &id210
+ required: &id221
- '#1 SMP Wed Jul 1 19:53:01 UTC 2020'
type: object
kernel_version:
5.7.7-200.fc32.x86_64:
description: ''
type: integer
- required: &id211
+ required: &id222
- 5.7.7-200.fc32.x86_64
type: object
os:
Linux:
description: ''
type: integer
- required: &id212
+ required: &id223
- Linux
type: object
osd_objectstore:
bluestore:
description: ''
type: integer
- required: &id213
+ required: &id224
- bluestore
type: object
rotational:
'1':
description: ''
type: integer
- required: &id214
+ required: &id225
- '1'
type: object
- required: &id215
+ required: &id226
- osd_objectstore
- rotational
- arch
- distro_description
- distro
type: object
- required: &id216
+ required: &id227
- osd
- mon
type: object
items:
type: string
type: array
- required: &id217
+ required: &id228
- persistent
- optional
type: object
v2_addr_mons:
description: ''
type: integer
- required: &id218
+ required: &id229
- count
- features
- min_mon_release
require_osd_release:
description: ''
type: string
- required: &id219
+ required: &id230
- count
- require_osd_release
- require_min_compat_client
type:
description: ''
type: string
- required: &id220
+ required: &id231
- pool
- type
- pg_num
num_pools:
description: ''
type: integer
- required: &id221
+ required: &id232
- num_pools
- num_images_by_pool
- mirroring_by_pool
zones:
description: ''
type: integer
- required: &id222
+ required: &id233
- count
- zones
- zonegroups
rgw:
description: ''
type: integer
- required: &id223
+ required: &id234
- rgw
type: object
usage:
total_used_bytes:
description: ''
type: integer
- required: &id224
+ required: &id235
- pools
- pg_num
- total_used_bytes
- total_bytes
- total_avail_bytes
type: object
- required: &id225
+ required: &id236
- leaderboard
- report_version
- report_timestamp
- balancer
- crashes
type: object
- required: &id226
+ required: &id237
- report
- device_report
type: object
mode:
description: ''
type: string
- required: *id186
+ required: *id197
type: object
channels:
description: ''
items:
type: string
type: array
- required: *id187
+ required: *id198
type: object
crashes:
description: ''
straw2:
description: ''
type: integer
- required: *id188
+ required: *id199
type: object
bucket_sizes:
description: ''
'3':
description: ''
type: integer
- required: *id189
+ required: *id200
type: object
bucket_types:
description: ''
'11':
description: ''
type: integer
- required: *id190
+ required: *id201
type: object
compat_weight_set:
description: ''
straw_calc_version:
description: ''
type: integer
- required: *id191
+ required: *id202
type: object
- required: *id192
+ required: *id203
type: object
fs:
description: ''
ever_enabled_multiple:
description: ''
type: boolean
- required: *id193
+ required: *id204
type: object
filesystems:
description: ''
total_num_mds:
description: ''
type: integer
- required: *id194
+ required: *id205
type: object
hosts:
description: ''
num_with_osd:
description: ''
type: integer
- required: *id195
+ required: *id206
type: object
leaderboard:
description: ''
x86_64:
description: ''
type: integer
- required: *id196
+ required: *id207
type: object
ceph_version:
description: ''
ceph version 16.0.0-3151-gf202994fcf:
description: ''
type: integer
- required: *id197
+ required: *id208
type: object
cpu:
description: ''
Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz:
description: ''
type: integer
- required: *id198
+ required: *id209
type: object
distro:
description: ''
centos:
description: ''
type: integer
- required: *id199
+ required: *id210
type: object
distro_description:
description: ''
CentOS Linux 8 (Core):
description: ''
type: integer
- required: *id200
+ required: *id211
type: object
kernel_description:
description: ''
'#1 SMP Wed Jul 1 19:53:01 UTC 2020':
description: ''
type: integer
- required: *id201
+ required: *id212
type: object
kernel_version:
description: ''
5.7.7-200.fc32.x86_64:
description: ''
type: integer
- required: *id202
+ required: *id213
type: object
os:
description: ''
Linux:
description: ''
type: integer
- required: *id203
+ required: *id214
type: object
- required: *id204
+ required: *id215
type: object
osd:
description: ''
x86_64:
description: ''
type: integer
- required: *id205
+ required: *id216
type: object
ceph_version:
description: ''
ceph version 16.0.0-3151-gf202994fcf:
description: ''
type: integer
- required: *id206
+ required: *id217
type: object
cpu:
description: ''
Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz:
description: ''
type: integer
- required: *id207
+ required: *id218
type: object
distro:
description: ''
centos:
description: ''
type: integer
- required: *id208
+ required: *id219
type: object
distro_description:
description: ''
CentOS Linux 8 (Core):
description: ''
type: integer
- required: *id209
+ required: *id220
type: object
kernel_description:
description: ''
'#1 SMP Wed Jul 1 19:53:01 UTC 2020':
description: ''
type: integer
- required: *id210
+ required: *id221
type: object
kernel_version:
description: ''
5.7.7-200.fc32.x86_64:
description: ''
type: integer
- required: *id211
+ required: *id222
type: object
os:
description: ''
Linux:
description: ''
type: integer
- required: *id212
+ required: *id223
type: object
osd_objectstore:
description: ''
bluestore:
description: ''
type: integer
- required: *id213
+ required: *id224
type: object
rotational:
description: ''
'1':
description: ''
type: integer
- required: *id214
+ required: *id225
type: object
- required: *id215
+ required: *id226
type: object
- required: *id216
+ required: *id227
type: object
mon:
description: ''
items:
type: string
type: array
- required: *id217
+ required: *id228
type: object
ipv4_addr_mons:
description: ''
v2_addr_mons:
description: ''
type: integer
- required: *id218
+ required: *id229
type: object
osd:
description: ''
require_osd_release:
description: ''
type: string
- required: *id219
+ required: *id230
type: object
pools:
description: ''
type:
description: ''
type: string
- required: *id220
+ required: *id231
type: object
type: array
rbd:
num_pools:
description: ''
type: integer
- required: *id221
+ required: *id232
type: object
report_id:
description: ''
zones:
description: ''
type: integer
- required: *id222
+ required: *id233
type: object
services:
description: ''
rgw:
description: ''
type: integer
- required: *id223
+ required: *id234
type: object
usage:
description: ''
total_used_bytes:
description: ''
type: integer
- required: *id224
+ required: *id235
type: object
- required: *id225
+ required: *id236
type: object
- required: *id226
+ required: *id237
type: object
description: OK
'400':
username:
description: Username of the user
type: string
- required: &id227
+ required: &id238
- username
- roles
- name
username:
description: Username of the user
type: string
- required: *id227
+ required: *id238
type: object
description: OK
'400':
self.assertIn(error_message, response.get('detail', ''))
mgr.remote.assert_called_once_with('mirroring', 'snapshot_mirror_ls', fs_name)
+ def test_mirror_status_success(self):
+ fs_name = 'test_fs'
+ peer_uuid = 'peer-uuid-123'
+ expected_status = {
+ 'metrics': {
+ '/dir1': {
+ 'peer': {
+ peer_uuid: {
+ 'state': 'idle',
+ 'last_synced_snap': {
+ 'name': 'snap1',
+ 'sync_bytes': '1.00 KiB',
+ 'sync_time_stamp': '1704189600.000000s'
+ }
+ }
+ }
+ }
+ }
+ }
+ mock_output = json.dumps(expected_status)
+ mgr.remote = Mock(return_value=(0, mock_output, ''))
+
+ self._get(f'/api/cephfs/mirror/{fs_name}/status?peer_id={peer_uuid}')
+ self.assertStatus(200)
+ self.assertJsonBody(expected_status)
+ mgr.remote.assert_called_once_with(
+ 'mirroring', 'snapshot_mirror_status', fs_name, None, peer_uuid)
+
+ def test_mirror_status_error(self):
+ fs_name = 'test_fs'
+ error_message = 'no cephfs-mirror daemon available'
+ mgr.remote = Mock(return_value=(1, '', error_message))
+
+ self._get(f'/api/cephfs/mirror/{fs_name}/status')
+ self.assertStatus(400)
+ response = self.json_body()
+ self.assertIn('Failed to get Cephfs mirror status', response.get('detail', ''))
+ self.assertIn(error_message, response.get('detail', ''))
+ mgr.remote.assert_called_once_with(
+ 'mirroring', 'snapshot_mirror_status', fs_name, None, None)
+
class CephFSMirrorStatusTest(ControllerTestCase):