From 891a1031a7d121786868328de4a8a737c905cd8f Mon Sep 17 00:00:00 2001 From: Afreen Misbah Date: Wed, 13 Aug 2025 12:19:02 +0530 Subject: [PATCH] mgr/dashboard: Add /health/snapshot api Fixes https://tracker.ceph.com/issues/72609 - The current minimal API relies on fetching data from osdmap and pgmap. - These commands produce large, detailed payloads that become a performance bottleneck and impact scalability, especially in large clusters. - To address this, we propose switching to the ceph snapshot API using ceph status command, which retrieves essential information directly from the cluster map. - ceph status is significantly more lightweight compared to osdmap/pgmap, reducing payload sizes and processing overhead. - This change ensures faster response times, improves system efficiency in large deployments, and minimizes unnecessary data transfer. - update tests Signed-off-by: Afreen Misbah (cherry picked from commit 2609d4f62e9e3906cf3e3fcc042bfdf0bcc633bf) Conflicts: src/pybind/mgr/dashboard/frontend/package-lock.json src/pybind/mgr/dashboard/frontend/package.json src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html --- .../mgr/dashboard/controllers/health.py | 152 +++++++++++++- .../mgr/dashboard/frontend/package-lock.json | 9 +- .../dashboard/dashboard-v3.component.html | 41 ++-- .../dashboard/dashboard-v3.component.spec.ts | 136 ++++++++---- .../dashboard/dashboard-v3.component.ts | 168 +++++++++------ .../ceph/dashboard-v3/pg-summary.pipe.spec.ts | 17 +- .../app/ceph/dashboard-v3/pg-summary.pipe.ts | 14 +- .../src/app/ceph/shared/ceph-shared.module.ts | 4 +- .../health-checks.component.html | 34 +-- .../health-checks.component.spec.ts | 25 --- .../src/app/shared/api/health.service.spec.ts | 24 +++ .../src/app/shared/api/health.service.ts | 18 +- .../src/app/shared/enum/icons.enum.ts | 1 + .../src/app/shared/models/cd-details.ts | 14 ++ .../src/app/shared/models/health.interface.ts | 76 ++++--- .../app/shared/pipes/mds-summary.pipe.spec.ts | 72 ------- .../src/app/shared/pipes/mds-summary.pipe.ts | 51 ----- .../app/shared/pipes/osd-summary.pipe.spec.ts | 43 ---- .../src/app/shared/pipes/osd-summary.pipe.ts | 46 ---- .../src/app/shared/pipes/pipes.module.ts | 8 - src/pybind/mgr/dashboard/openapi.yaml | 196 +++++++++++++++++- 21 files changed, 679 insertions(+), 470 deletions(-) delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts diff --git a/src/pybind/mgr/dashboard/controllers/health.py b/src/pybind/mgr/dashboard/controllers/health.py index de45bebbb46..1457ab494c1 100644 --- a/src/pybind/mgr/dashboard/controllers/health.py +++ b/src/pybind/mgr/dashboard/controllers/health.py @@ -110,6 +110,55 @@ HEALTH_MINIMAL_SCHEMA = ({ 'scrub_status': (str, '') }) +HEALTH_SNAPSHOT_SCHEMA = ({ + 'fsid': (str, 'Cluster filesystem ID'), + 'health': ({ + 'status': (str, 'Overall health status'), + 'checks': ({ + '': ({ + 'severity': (str, 'Health severity level'), + 'summary': ({ + 'message': (str, 'Human-readable summary'), + 'count': (int, 'Occurrence count') + }, 'Summary details'), + 'muted': (bool, 'Whether the check is muted') + }, 'Individual health check object') + }, 'Health checks keyed by name'), + 'mutes': ([str], 'List of muted check names') + }, 'Cluster health overview'), + 'monmap': ({ + 'num_mons': (int, 'Number of monitors') + }, 'Monitor map details'), + 'osdmap': ({ + 'in': (int, 'Number of OSDs in'), + 'up': (int, 'Number of OSDs up'), + 'num_osds': (int, 'Total OSD count') + }, 'OSD map details'), + 'pgmap': ({ + 'pgs_by_state': ([{ + 'state_name': (str, 'Placement group state'), + 'count': (int, 'Count of PGs in this state') + }], 'List of PG counts by state'), + 'num_pools': (int, 'Number of pools'), + 'num_pgs': (int, 'Total PG count'), + 'bytes_used': (int, 'Used capacity in bytes'), + 'bytes_total': (int, 'Total capacity in bytes'), + }, 'Placement group map details'), + 'mgrmap': ({ + 'num_active': (int, 'Number of active managers'), + 'num_standbys': (int, 'Standby manager count') + }, 'Manager map details'), + 'fsmap': ({ + 'num_active': (int, 'Number of active mds'), + 'num_standbys': (int, 'Standby MDS count'), + }, 'Filesystem map details'), + 'num_rgw_gateways': (int, 'Count of RGW gateway daemons running'), + 'num_iscsi_gateways': ({ + 'up': (int, 'Count of iSCSI gateways running'), + 'down': (int, 'Count of iSCSI gateways not running') + }, 'Iscsi gateways status'), +}) + class HealthData(object): """ @@ -281,15 +330,28 @@ class HealthData(object): class Health(BaseController): def __init__(self): super().__init__() - self.health_full = HealthData(self._has_permissions, minimal=False) - self.health_minimal = HealthData(self._has_permissions, minimal=True) + self._health_full = None + self._health_minimal = None + + @property + def health_full(self): + if self._health_full is None: + self._health_full = HealthData(self._has_permissions, minimal=False) + return self._health_full + + @property + def health_minimal(self): + if self._health_minimal is None: + self._health_minimal = HealthData(self._has_permissions, minimal=True) + return self._health_minimal @Endpoint() + @EndpointDoc("Get Cluster's detailed health report") def full(self): return self.health_full.all_health() @Endpoint() - @EndpointDoc("Get Cluster's minimal health report", + @EndpointDoc("Get Cluster's health report with lesser details", responses={200: HEALTH_MINIMAL_SCHEMA}) def minimal(self): return self.health_minimal.all_health() @@ -305,3 +367,87 @@ class Health(BaseController): @Endpoint() def get_telemetry_status(self): return mgr.get_module_option_ex('telemetry', 'enabled', False) + + @Endpoint() + @EndpointDoc( + "Get a quick overview of cluster health at a moment, analogous to " + "the ceph status command in CLI.", + responses={200: HEALTH_SNAPSHOT_SCHEMA}) + def snapshot(self): + data = CephService.send_command('mon', 'status') + + summary = { + 'fsid': data.get('fsid'), + 'health': { + 'status': data.get('health', {}).get('status'), + 'checks': data.get('health', {}).get('checks', {}), + 'mutes': data.get('health', {}).get('mutes', []), + }, + } + + if self._has_permissions(Permission.READ, Scope.MONITOR): + summary['monmap'] = { + 'num_mons': data.get('monmap', {}).get('num_mons'), + } + + if self._has_permissions(Permission.READ, Scope.OSD): + summary['osdmap'] = { + 'in': data.get('osdmap', {}).get('num_in_osds'), + 'up': data.get('osdmap', {}).get('num_up_osds'), + 'num_osds': data.get('osdmap', {}).get('num_osds'), + } + summary['pgmap'] = { + 'pgs_by_state': data.get('pgmap', {}).get('pgs_by_state', []), + 'num_pools': data.get('pgmap', {}).get('num_pools'), + 'num_pgs': data.get('pgmap', {}).get('num_pgs'), + 'bytes_used': data.get('pgmap', {}).get('bytes_used'), + 'bytes_total': data.get('pgmap', {}).get('bytes_total'), + } + + if self._has_permissions(Permission.READ, Scope.MANAGER): + mgrmap = data.get('mgrmap', {}) + available = mgrmap.get('available', False) + num_standbys = mgrmap.get('num_standbys') + num_active = 1 if available else 0 + summary['mgrmap'] = { + 'num_active': num_active, + 'num_standbys': num_standbys, + } + + if self._has_permissions(Permission.READ, Scope.CEPHFS): + fsmap = data.get('fsmap', {}) + by_rank = fsmap.get('by_rank', []) + + active_count = 0 + standby_replay_count = 0 + + for mds in by_rank: + state = mds.get('status', '') + if state == 'up:standby-replay': + standby_replay_count += 1 + elif state.startswith('up:'): + active_count += 1 + + summary['fsmap'] = { + 'num_active': active_count, + 'num_standbys': fsmap.get('up:standby', 0) + standby_replay_count, + } + + if self._has_permissions(Permission.READ, Scope.RGW): + daemons = ( + data.get('servicemap', {}) + .get('services', {}) + .get('rgw', {}) + .get('daemons', {}) + or {} + ) + daemons.pop("summary", None) + summary['num_rgw_gateways'] = len(daemons) + + if self._has_permissions(Permission.READ, Scope.ISCSI): + summary['num_iscsi_gateways'] = self.health_minimal.iscsi_daemons() + + if self._has_permissions(Permission.READ, Scope.HOSTS): + summary['num_hosts'] = len(get_hosts()) + + return summary diff --git a/src/pybind/mgr/dashboard/frontend/package-lock.json b/src/pybind/mgr/dashboard/frontend/package-lock.json index 0535d109d49..ee9f87c2a7a 100644 --- a/src/pybind/mgr/dashboard/frontend/package-lock.json +++ b/src/pybind/mgr/dashboard/frontend/package-lock.json @@ -18,7 +18,7 @@ "@angular/platform-browser": "18.2.11", "@angular/platform-browser-dynamic": "18.2.11", "@angular/router": "18.2.11", - "@carbon/icons": "11.63.0", + "@carbon/icons": "11.41.0", "@carbon/styles": "1.57.0", "@ibm/plex": "6.4.0", "@ng-bootstrap/ng-bootstrap": "17.0.1", @@ -5604,9 +5604,8 @@ "integrity": "sha512-YXed2JUSCGddp3UnY5OffR3W8Pl+dy9a+vfUtYhSLH9TbIEBR6EvYIfvruFMhA8JIVMCUClUqgyMQXM5oMFQ0g==" }, "node_modules/@carbon/icons": { - "version": "11.63.0", - "resolved": "https://registry.npmjs.org/@carbon/icons/-/icons-11.63.0.tgz", - "integrity": "sha512-J5sGbamMMBbQPcdX9ImzEIoa7l2DyNiZYu9ScKtY3dJ6lKeG6GJUFQYH5/vcpuyj02tizVe6rSgWcH210+OOqw==", + "version": "11.41.0", + "integrity": "sha512-9RGaOnihPQx74yBQ0UnEr9JJ+e2aa/J+tmTG/sZ203q2hfoeMF2PqipwOhNS1fqCnyW1zvsYQNydUsNIDzCqaA==", "hasInstallScript": true, "dependencies": { "@ibm/telemetry-js": "^1.5.0" @@ -39308,4 +39307,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html index 90f2bf92e77..81885b1133a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html @@ -73,22 +73,22 @@ [dropdownData]="(isHardwareEnabled$ | async) && (hardwareSummary$ | async)"> - - - - @@ -106,7 +106,7 @@ *ngIf="enabledFeature.rgw"> - @@ -142,27 +142,27 @@
- + Cluster
- +
@@ -225,8 +225,8 @@ [fullHeight]="true" aria-label="Capacity card"> - + @@ -242,9 +242,9 @@
- + @@ -316,13 +316,6 @@ - - - - - - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts index b87888f4f4a..2b28492bc1a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts @@ -23,6 +23,7 @@ import { PgSummaryPipe } from '../pg-summary.pipe'; import { DashboardV3Component } from './dashboard-v3.component'; import { OrchestratorService } from '~/app/shared/api/orchestrator.service'; import { AlertClass } from '~/app/shared/enum/health-icon.enum'; +import { HealthSnapshotMap } from '~/app/shared/models/health.interface'; export class SummaryServiceMock { summaryDataSource = new BehaviorSubject({ @@ -40,26 +41,61 @@ export class SummaryServiceMock { describe('Dashbord Component', () => { let component: DashboardV3Component; let fixture: ComponentFixture; - let healthService: HealthService; let orchestratorService: OrchestratorService; - let getHealthSpy: jasmine.Spy; + let getHealthStatusSpy: jasmine.Spy; let getAlertsSpy: jasmine.Spy; let fakeFeatureTogglesService: jasmine.Spy; - const healthPayload: Record = { - health: { status: 'HEALTH_OK' }, - mon_status: { monmap: { mons: [] }, quorum: [] }, - osd_map: { osds: [] }, - mgr_map: { standbys: [] }, - hosts: 0, - rgw: 0, - fs_map: { filesystems: [], standbys: [] }, - iscsi_daemons: 1, - client_perf: {}, - scrub_status: 'Inactive', - pools: [], - df: { stats: {} }, - pg_info: { object_stats: { num_objects: 1 } } + const healthStatusPayload: HealthSnapshotMap = { + fsid: '7d0cc9da-ca8d-4539-a953-ab062139c26a', + health: { + status: 'HEALTH_WARN', + checks: { + DASHBOARD_DEBUG: { + severity: 'HEALTH_WARN', + summary: { + message: 'Dashboard debug mode is enabled', + count: 0 + }, + muted: false + } + }, + mutes: [] + }, + monmap: { + num_mons: 3 + }, + osdmap: { + in: 3, + up: 3, + num_osds: 3 + }, + pgmap: { + pgs_by_state: [ + { + state_name: 'active+clean', + count: 497 + } + ], + num_pools: 14, + bytes_used: 3236978688, + bytes_total: 325343772672, + num_pgs: 497 + }, + mgrmap: { + num_active: 1, + num_standbys: 0 + }, + fsmap: { + num_standbys: 2, + num_active: 1 + }, + num_rgw_gateways: 3, + num_iscsi_gateways: { + up: 0, + down: 0 + }, + num_hosts: 1 }; const alertsPayload: AlertmanagerAlert[] = [ @@ -131,8 +167,6 @@ describe('Dashbord Component', () => { } ]; - const configValueData: any = 'e90a0d58-658e-4148-8f61-e896c86f0696'; - const orchName: any = 'Cephadm'; configureTestBed({ @@ -159,10 +193,9 @@ describe('Dashbord Component', () => { ); fixture = TestBed.createComponent(DashboardV3Component); component = fixture.componentInstance; - healthService = TestBed.inject(HealthService); orchestratorService = TestBed.inject(OrchestratorService); - getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth'); - getHealthSpy.and.returnValue(of(healthPayload)); + getHealthStatusSpy = spyOn(TestBed.inject(HealthService), 'getHealthSnapshot'); + getHealthStatusSpy.and.returnValue(of(healthStatusPayload)); getAlertsSpy = spyOn(TestBed.inject(PrometheusService), 'getAlerts'); getAlertsSpy.and.returnValue(of(alertsPayload)); component.prometheusAlertService.alerts = alertsPayload; @@ -184,45 +217,50 @@ describe('Dashbord Component', () => { }); it('should get corresponding data into detailsCardData', () => { - spyOn(healthService, 'getClusterFsid').and.returnValue(of(configValueData)); spyOn(orchestratorService, 'getName').and.returnValue(of(orchName)); component.ngOnInit(); - expect(component.detailsCardData.fsid).toBe('e90a0d58-658e-4148-8f61-e896c86f0696'); + expect(component.detailsCardData.fsid).toBe(healthStatusPayload['fsid']); expect(component.detailsCardData.orchestrator).toBe('Cephadm'); expect(component.detailsCardData.cephVersion).toBe('17.0.0-12222-gcd0cd7cb quincy (dev)'); }); it('should check if the respective icon is shown for each status', () => { - const payload = _.cloneDeep(healthPayload); + const payload = _.cloneDeep(healthStatusPayload); // HEALTH_WARN payload.health['status'] = 'HEALTH_WARN'; - payload.health['checks'] = [ - { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } } - ]; - - getHealthSpy.and.returnValue(of(payload)); + payload.health['checks'] = { + FAKE_CHECK: { + severity: 'HEALTH_WARN', + summary: { message: 'fake warning', count: 1 }, + muted: false + } + }; + + getHealthStatusSpy.and.returnValue(of(payload)); fixture.detectChanges(); const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"] i')); expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`); // HEALTH_ERR payload.health['status'] = 'HEALTH_ERR'; - payload.health['checks'] = [ - { severity: 'HEALTH_ERR', type: 'ERR', summary: { message: 'fake error' } } - ]; - - getHealthSpy.and.returnValue(of(payload)); + payload.health['checks'] = { + FAKE_CHECK: { + severity: 'HEALTH_ERR', + summary: { message: 'fake error', count: 1 }, + muted: false + } + }; + + getHealthStatusSpy.and.returnValue(of(payload)); fixture.detectChanges(); expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`); // HEALTH_OK payload.health['status'] = 'HEALTH_OK'; - payload.health['checks'] = [ - { severity: 'HEALTH_OK', type: 'OK', summary: { message: 'fake success' } } - ]; + payload.health['checks'] = {}; - getHealthSpy.and.returnValue(of(payload)); + getHealthStatusSpy.and.returnValue(of(payload)); fixture.detectChanges(); expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`); }); @@ -273,21 +311,29 @@ describe('Dashbord Component', () => { }); it('should render "Status" card text that is not clickable', () => { - fixture.detectChanges(); + const payload = _.cloneDeep(healthStatusPayload); + payload.health['status'] = 'HEALTH_OK'; + payload.health['checks'] = null; + getHealthStatusSpy.and.returnValue(of(payload)); + fixture.detectChanges(); const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]')); const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary')); expect(clickableContent).toBeNull(); }); it('should render "Status" card text that is clickable (popover)', () => { - const payload = _.cloneDeep(healthPayload); + const payload = _.cloneDeep(healthStatusPayload); payload.health['status'] = 'HEALTH_WARN'; - payload.health['checks'] = [ - { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } } - ]; - - getHealthSpy.and.returnValue(of(payload)); + payload.health['checks'] = { + FAKE_CHECK: { + severity: 'HEALTH_WARN', + summary: { message: 'fake warning', count: 1 }, + muted: false + } + }; + + getHealthStatusSpy.and.returnValue(of(payload)); fixture.detectChanges(); const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]')); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts index c4d74c33f3e..ec77518371e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts @@ -11,7 +11,12 @@ import { UtilizationCardQueries } from '~/app/shared/enum/dashboard-promqls.enum'; import { Icons } from '~/app/shared/enum/icons.enum'; -import { DashboardDetails } from '~/app/shared/models/cd-details'; +import { + CapacityCardDetails, + DashboardDetails, + InventoryCommonDetail, + InventoryDetails +} from '~/app/shared/models/cd-details'; import { Permissions } from '~/app/shared/models/permissions'; import { AlertmanagerAlert } from '~/app/shared/models/prometheus-alerts'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; @@ -29,40 +34,35 @@ import { AlertClass } from '~/app/shared/enum/health-icon.enum'; import { HardwareService } from '~/app/shared/api/hardware.service'; import { SettingsService } from '~/app/shared/api/settings.service'; import { + Health, + HealthSnapshotMap, IscsiMap, - MdsMap, - MgrMap, - MonMap, - OsdMap, - PgStatus + PgStateCount } from '~/app/shared/models/health.interface'; -type CapacityCardData = { - osdNearfull: number; - osdFull: number; -}; - @Component({ selector: 'cd-dashboard-v3', templateUrl: './dashboard-v3.component.html', styleUrls: ['./dashboard-v3.component.scss'] }) export class DashboardV3Component extends PrometheusListHelper implements OnInit, OnDestroy { - detailsCardData: DashboardDetails = {}; - capacityCardData: CapacityCardData = { - osdNearfull: null, - osdFull: null - }; - interval = new Subscription(); + telemetryURL = 'https://telemetry-public.ceph.com/'; + origin = window.location.origin; + icons = Icons; + permissions: Permissions; + + hardwareSubject = new BehaviorSubject([]); + private subs = new Subscription(); + private destroy$ = new Subject(); + enabledFeature$: FeatureTogglesMap$; - color: string; - capacityService: any; - capacity: any; - healthData$: Observable; prometheusAlerts$: Observable; + isHardwareEnabled$: Observable; + hardwareSummary$: Observable; + managedByConfig$: Observable; - icons = Icons; + color: string; flexHeight = true; simplebar = { autoHide: true @@ -70,9 +70,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit borderClass: string; alertType: string; alertClass = AlertClass; - healthData: any; - categoryPgAmount: Record = {}; - totalPgs = 0; + queriesResults: { [key: string]: [] } = { USEDCAPACITY: [], IPS: [], @@ -85,27 +83,28 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit READIOPS: [], WRITEIOPS: [] }; + telemetryEnabled: boolean; - telemetryURL = 'https://telemetry-public.ceph.com/'; - origin = window.location.origin; + detailsCardData: DashboardDetails = {}; + capacityCardData: CapacityCardDetails = { + osdNearfull: null, + osdFull: null + }; + healthCardData: Health; + hasHealthChecks: boolean; hardwareHealth: any; hardwareEnabled: boolean = false; hasHardwareError: boolean = false; - isHardwareEnabled$: Observable; - hardwareSummary$: Observable; - hardwareSubject = new BehaviorSubject([]); - managedByConfig$: Observable; - private subs = new Subscription(); - private destroy$ = new Subject(); - + totalCapacity: number = null; + usedCapacity: number = null; hostsCount: number = null; - monMap: MonMap = null; - mgrMap: MgrMap = null; - osdMap: OsdMap = null; - poolStatus: Record[] = null; - pgStatus: PgStatus = null; + monCount: number = null; + poolCount: number = null; rgwCount: number = null; - mdsMap: MdsMap = null; + osdCount: { in: number; out: number; up: number; down: number } & InventoryCommonDetail = null; + pgStatus: { statuses: PgStateCount[] } & InventoryCommonDetail = null; + mgrStatus: InventoryDetails = null; + mdsStatus: InventoryDetails = null; iscsiMap: IscsiMap = null; constructor( @@ -144,14 +143,6 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit } this.loadInventories(); - - // fetch capacity to load the capacity chart - this.refreshIntervalObs(() => this.healthService.getClusterCapacity()).subscribe({ - next: (capacity: any) => { - this.capacity = capacity; - } - }); - this.getPrometheusData(this.prometheusService.lastHourDateObject); this.getDetailsCardData(); this.getTelemetryReport(); @@ -161,11 +152,12 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit getTelemetryText(): string { return this.telemetryEnabled - ? 'Cluster telemetry is active' - : 'Cluster telemetry is inactive. To Activate the Telemetry, \ + ? $localize`Cluster telemetry is active` + : $localize`Cluster telemetry is inactive. To Activate the Telemetry, \ click settings icon on top navigation bar and select \ - Telemetry configration.'; + Telemetry configration.`; } + ngOnDestroy() { this.prometheusService.unsubscribe(); this.subs?.unsubscribe(); @@ -178,9 +170,6 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit } getDetailsCardData() { - this.healthService.getClusterFsid().subscribe((data: string) => { - this.detailsCardData.fsid = data; - }); this.orchestratorService.getName().subscribe((data: string) => { this.detailsCardData.orchestrator = data; }); @@ -251,19 +240,66 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit ); } + private safeSum(a: number, b: number): number | null { + return a != null && b != null ? a + b : null; + } + + private safeDifference(a: number, b: number): number | null { + return a != null && b != null ? a - b : null; + } + loadInventories() { - this.refreshIntervalObs(() => this.healthService.getMinimalHealth()).subscribe({ - next: (result: any) => { - this.hostsCount = result.hosts; - this.monMap = result.mon_status; - this.mgrMap = result.mgr_map; - this.osdMap = result.osd_map; - this.poolStatus = result.pools; - this.pgStatus = result.pg_info; - this.rgwCount = result.rgw; - this.mdsMap = result.fs_map; - this.iscsiMap = result.iscsi_daemons; - this.healthData = result.health; + this.refreshIntervalObs(() => this.healthService.getHealthSnapshot()).subscribe({ + next: (data: HealthSnapshotMap) => { + this.detailsCardData.fsid = data?.fsid; + this.healthCardData = data?.health; + this.hasHealthChecks = !!Object.keys(this.healthCardData?.checks ?? {})?.length; + this.monCount = data?.monmap?.num_mons; + + const osdMap = data?.osdmap; + const osdIn = osdMap?.in; + const osdUp = osdMap?.up; + const osdTotal = osdMap?.num_osds; + + this.osdCount = { + in: osdIn, + up: osdUp, + total: osdTotal, + down: this.safeDifference(osdTotal, osdUp), + out: this.safeDifference(osdTotal, osdIn) + }; + + const pgmap = data?.pgmap; + this.poolCount = pgmap?.num_pools; + this.usedCapacity = pgmap?.bytes_used; + this.totalCapacity = pgmap?.bytes_total; + this.pgStatus = { + statuses: pgmap?.pgs_by_state, + total: pgmap?.num_pgs + }; + + const mgrmap = data?.mgrmap; + const mgrInfo = mgrmap?.num_standbys; + const mgrSuccess = mgrmap?.num_active; + + this.mgrStatus = { + info: mgrInfo, + success: mgrSuccess, + total: this.safeSum(mgrInfo, mgrSuccess) + }; + + const mdsInfo = data?.fsmap?.num_standbys; + const mdsSuccess = data?.fsmap?.num_active; + + this.mdsStatus = { + info: mdsInfo, + success: mdsSuccess, + total: this.safeSum(mdsInfo, mdsSuccess) + }; + + this.rgwCount = data?.num_rgw_gateways; + this.iscsiMap = data?.num_iscsi_gateways; + this.hostsCount = data?.num_hosts; this.enabledFeature$ = this.featureToggles.get(); } }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.spec.ts index b467167fdce..eb813a3fe53 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.spec.ts @@ -4,7 +4,7 @@ import { configureTestBed } from '~/testing/unit-test-helper'; import { PgCategoryService } from '../shared/pg-category.service'; import { PgSummaryPipe } from './pg-summary.pipe'; -describe('OsdSummaryPipe', () => { +describe('PgSummaryPipe', () => { let pipe: PgSummaryPipe; configureTestBed({ @@ -21,16 +21,19 @@ describe('OsdSummaryPipe', () => { it('tranforms value', () => { const value = { - statuses: { - 'active+clean': 241 - }, - pgs_per_osd: 241 + statuses: [ + { + state_name: 'active+clean', + count: 497 + } + ], + total: 497 }; expect(pipe.transform(value)).toEqual({ categoryPgAmount: { - clean: 241 + clean: 497 }, - total: 241 + total: 497 }); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts index c12193accd6..62a4e003099 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts @@ -1,6 +1,6 @@ import { Pipe, PipeTransform } from '@angular/core'; -import _ from 'lodash'; import { PgCategoryService } from '~/app/ceph/shared/pg-category.service'; +import { PgStateCount } from '~/app/shared/models/health.interface'; @Pipe({ name: 'pgSummary' @@ -11,18 +11,16 @@ export class PgSummaryPipe implements PipeTransform { transform(value: any): any { if (!value) return null; const categoryPgAmount: Record = {}; - let total = 0; - _.forEach(value.statuses, (pgAmount, pgStatesText) => { - const categoryType = this.pgCategoryService.getTypeByStates(pgStatesText); - if (_.isUndefined(categoryPgAmount[categoryType])) { + value.statuses.forEach((status: PgStateCount) => { + const categoryType = this.pgCategoryService.getTypeByStates(status?.state_name); + if (!categoryPgAmount?.[categoryType]) { categoryPgAmount[categoryType] = 0; } - categoryPgAmount[categoryType] += pgAmount; - total += pgAmount; + categoryPgAmount[categoryType] += status?.count; }); return { categoryPgAmount, - total + total: value.total }; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts index 5dde6c10034..33a156f048f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts @@ -1,6 +1,8 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; import { PipesModule } from '~/app/shared/pipes/pipes.module'; @@ -11,7 +13,7 @@ import { SmartListComponent } from './smart-list/smart-list.component'; import { HealthChecksComponent } from './health-checks/health-checks.component'; @NgModule({ - imports: [CommonModule, DataTableModule, SharedModule, NgbNavModule, PipesModule], + imports: [CommonModule, DataTableModule, SharedModule, NgbNavModule, PipesModule, RouterModule], exports: [DeviceListComponent, SmartListComponent, HealthChecksComponent], declarations: [DeviceListComponent, SmartListComponent, HealthChecksComponent] }) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.html index 9e9ff96e5f8..cd1105e1284 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.html @@ -1,28 +1,12 @@ - +

+ + See Logs for more details. +

    -
  • - - {{ check.type }}: {{ check.summary.message }}
    -
    - - Failed Daemons: -
    - {{ failedDaemons }} - {{ !last ? ', ' : '' }} -
    -
    -
    -
    - {{ details?.message }} -
    +
  • + + {{ check.key }}: {{ check.value.summary.message }}
- - - - - - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.spec.ts index 43633dc3789..d3ab1dccedb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/health-checks/health-checks.component.spec.ts @@ -2,7 +2,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { HealthChecksComponent } from './health-checks.component'; import { HealthColorPipe } from '~/app/shared/pipes/health-color.pipe'; -import { By } from '@angular/platform-browser'; import { CssHelper } from '~/app/shared/classes/css-helper'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; @@ -25,28 +24,4 @@ describe('HealthChecksComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); - - it('should show the correct health warning for failed daemons', () => { - component.healthData = [ - { - severity: 'HEALTH_WARN', - summary: { - message: '1 failed cephadm daemon(s)', - count: 1 - }, - detail: [ - { - message: 'daemon ceph-exporter.ceph-node-00 on ceph-node-00 is in error state' - } - ], - muted: false, - type: 'CEPHADM_FAILED_DAEMON' - } - ]; - fixture.detectChanges(); - const failedDaemons = fixture.debugElement.query(By.css('.failed-daemons')); - expect(failedDaemons.nativeElement.textContent).toContain( - 'Failed Daemons: ceph-exporter.ceph-node-00 ' - ); - }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.spec.ts index 84eeac0f31a..4084aee9986 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.spec.ts @@ -37,4 +37,28 @@ describe('HealthService', () => { const req = httpTesting.expectOne('api/health/minimal'); expect(req.request.method).toBe('GET'); }); + + it('should call getHealthSnapshot', () => { + service.getHealthSnapshot().subscribe(); + const req = httpTesting.expectOne('api/health/snapshot'); + expect(req.request.method).toBe('GET'); + }); + + it('should call getClusterFsid', () => { + service.getClusterFsid().subscribe(); + const req = httpTesting.expectOne('api/health/get_cluster_fsid'); + expect(req.request.method).toBe('GET'); + }); + + it('should call getOrchestratorName', () => { + service.getOrchestratorName().subscribe(); + const req = httpTesting.expectOne('api/health/get_orchestrator_name'); + expect(req.request.method).toBe('GET'); + }); + + it('should call getTelemetryStatus', () => { + service.getTelemetryStatus().subscribe(); + const req = httpTesting.expectOne('api/health/get_telemetry_status'); + expect(req.request.method).toBe('GET'); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts index b04a27b644d..a7919789c92 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts @@ -1,5 +1,9 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { HealthSnapshotMap } from '../models/health.interface'; + +const BASE_URL = 'api/health'; @Injectable({ providedIn: 'root' @@ -8,26 +12,26 @@ export class HealthService { constructor(private http: HttpClient) {} getFullHealth() { - return this.http.get('api/health/full'); + return this.http.get(`${BASE_URL}/full`); } getMinimalHealth() { - return this.http.get('api/health/minimal'); + return this.http.get(`${BASE_URL}/minimal`); } - getClusterCapacity() { - return this.http.get('api/health/get_cluster_capacity'); + getHealthSnapshot(): Observable { + return this.http.get(`${BASE_URL}/snapshot`); } getClusterFsid() { - return this.http.get('api/health/get_cluster_fsid'); + return this.http.get(`${BASE_URL}/get_cluster_fsid`); } getOrchestratorName() { - return this.http.get('api/health/get_orchestrator_name'); + return this.http.get(`${BASE_URL}/get_orchestrator_name`); } getTelemetryStatus() { - return this.http.get('api/health/get_telemetry_status'); + return this.http.get(`${BASE_URL}/get_telemetry_status`); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts index e6afb14ef80..8d88056d0db 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts @@ -106,6 +106,7 @@ export enum IconSize { } export const ICON_TYPE = { + copy: 'copy', danger: 'warning--filled', infoCircle: 'information--filled', success: 'checkmark--filled', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-details.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-details.ts index d021f19eba7..87e3b2f3601 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-details.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-details.ts @@ -3,3 +3,17 @@ export interface DashboardDetails { orchestrator?: string; cephVersion?: string; } + +export interface CapacityCardDetails { + osdNearfull: number; + osdFull: number; +} + +export interface InventoryCommonDetail { + total: number; +} + +export interface InventoryDetails extends InventoryCommonDetail { + info: number; + success: number; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts index 22963b5de54..c784b06685e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts @@ -1,49 +1,59 @@ -export interface MonMap { - monmap: { - mons: Record[]; - }; - quorum: number[]; -} - -export interface MgrMap { - active_name: string; - standbys: string[]; +export interface IscsiMap { + up: number; + down: number; } -export interface OsdMap { - osds: Osd[]; +export interface HealthCheck { + severity: string; + summary: { + message: string; + count: number; + }; + muted: boolean; } -export interface PgStatus { - object_stats: ObjectStats; - statuses: Status; - pgs_per_osd: number; +export interface Health { + status: string; + checks: Record; + mutes: string[]; } -export interface MdsMap { - filesystems: any[]; - standbys: any[]; +export interface MonMap { + num_mons: number; } -export interface IscsiMap { +export interface OsdMap { + in: number; up: number; - down: number; + num_osds: number; } -interface ObjectStats { - num_objects: number; - num_object_copies: number; - num_objects_degraded: number; - num_objects_misplaced: number; - num_objects_unfound: number; +export interface PgStateCount { + state_name: string; + count: number; +} +export interface PgMap { + pgs_by_state: PgStateCount[]; + num_pools: number; + bytes_used: number; + bytes_total: number; + num_pgs: number; } -interface Status { - 'active+clean': number; +export interface HealthMapCommon { + num_standbys: number; + num_active: number; } -interface Osd { - in: number; - up: number; - state: string[]; +export interface HealthSnapshotMap { + fsid: string; + health: Health; + monmap: MonMap; + osdmap: OsdMap; + pgmap: PgMap; + mgrmap: HealthMapCommon; + fsmap: HealthMapCommon; + num_rgw_gateways: number; + num_iscsi_gateways: { up: number; down: number }; + num_hosts: number; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts deleted file mode 100644 index 4081aa26ec0..00000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { configureTestBed } from '~/testing/unit-test-helper'; -import { MdsSummaryPipe } from './mds-summary.pipe'; - -describe('MdsSummaryPipe', () => { - let pipe: MdsSummaryPipe; - - configureTestBed({ - providers: [MdsSummaryPipe] - }); - - beforeEach(() => { - pipe = TestBed.inject(MdsSummaryPipe); - }); - - it('create an instance', () => { - expect(pipe).toBeTruthy(); - }); - - it('transforms with 0 active and 2 standy', () => { - const payload = { - standbys: [{ name: 'a' }], - filesystems: [{ mdsmap: { info: [{ state: 'up:standby-replay' }] } }] - }; - - expect(pipe.transform(payload)).toEqual({ - success: 0, - info: 2, - total: 2 - }); - }); - - it('transforms with 1 active and 1 standy', () => { - const payload = { - standbys: [{ name: 'b' }], - filesystems: [{ mdsmap: { info: [{ state: 'up:active', name: 'a' }] } }] - }; - expect(pipe.transform(payload)).toEqual({ - success: 1, - info: 1, - total: 2 - }); - }); - - it('transforms with 0 filesystems', () => { - const payload: Record = { - standbys: [0], - filesystems: [] - }; - - expect(pipe.transform(payload)).toEqual({ - success: 0, - info: 0, - total: 0 - }); - }); - - it('transforms without filesystem', () => { - const payload = { standbys: [{ name: 'a' }] }; - - expect(pipe.transform(payload)).toEqual({ - success: 0, - info: 1, - total: 1 - }); - }); - - it('transforms without value', () => { - expect(pipe.transform(undefined)).toEqual(null); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts deleted file mode 100644 index 43004fa0c04..00000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -import _ from 'lodash'; - -@Pipe({ - name: 'mdsSummary' -}) -export class MdsSummaryPipe implements PipeTransform { - transform(value: any): any { - if (!value) { - return null; - } - - let activeCount = 0; - let standbyCount = 0; - let standbys = 0; - let active = 0; - let standbyReplay = 0; - _.each(value.standbys, () => { - standbys += 1; - }); - - if (value.standbys && !value.filesystems) { - standbyCount = standbys; - activeCount = 0; - } else if (value.filesystems.length === 0) { - activeCount = 0; - } else { - _.each(value.filesystems, (fs) => { - _.each(fs.mdsmap.info, (mds) => { - if (mds.state === 'up:standby-replay') { - standbyReplay += 1; - } else { - active += 1; - } - }); - }); - - activeCount = active; - standbyCount = standbys + standbyReplay; - } - const totalCount = activeCount + standbyCount; - const mdsSummary = { - success: activeCount, - info: standbyCount, - total: totalCount - }; - - return mdsSummary; - } -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts deleted file mode 100644 index 88e457d858b..00000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { configureTestBed } from '~/testing/unit-test-helper'; -import { OsdSummaryPipe } from './osd-summary.pipe'; - -describe('OsdSummaryPipe', () => { - let pipe: OsdSummaryPipe; - - configureTestBed({ - providers: [OsdSummaryPipe] - }); - - beforeEach(() => { - pipe = TestBed.inject(OsdSummaryPipe); - }); - - it('create an instance', () => { - expect(pipe).toBeTruthy(); - }); - - it('transforms without value', () => { - expect(pipe.transform(undefined)).toBe(null); - }); - - it('transforms having 3 osd with 3 up, 3 in, 0 down, 0 out', () => { - const value = { - osds: [ - { up: 1, in: 1, state: ['up', 'exists'] }, - { up: 1, in: 1, state: ['up', 'exists'] }, - { up: 1, in: 1, state: ['up', 'exists'] } - ] - }; - expect(pipe.transform(value)).toEqual({ - total: 3, - down: 0, - out: 0, - up: 3, - in: 3, - nearfull: 0, - full: 0 - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts deleted file mode 100644 index cf4ea649dd1..00000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -import _ from 'lodash'; - -@Pipe({ - name: 'osdSummary' -}) -export class OsdSummaryPipe implements PipeTransform { - transform(value: any): any { - if (!value) { - return null; - } - - let inCount = 0; - let upCount = 0; - let nearFullCount = 0; - let fullCount = 0; - _.each(value.osds, (osd) => { - if (osd.in) { - inCount++; - } - if (osd.up) { - upCount++; - } - if (osd.state.includes('nearfull')) { - nearFullCount++; - } - if (osd.state.includes('full')) { - fullCount++; - } - }); - - const downCount = value.osds.length - upCount; - const outCount = value.osds.length - inCount; - const osdSummary = { - total: value.osds.length, - down: downCount, - out: outCount, - up: upCount, - in: inCount, - nearfull: nearFullCount, - full: fullCount - }; - return osdSummary; - } -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts index bab30d05423..11e83887298 100755 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/pipes.module.ts @@ -22,12 +22,10 @@ import { IscsiBackstorePipe } from './iscsi-backstore.pipe'; import { JoinPipe } from './join.pipe'; import { LogPriorityPipe } from './log-priority.pipe'; import { MapPipe } from './map.pipe'; -import { MdsSummaryPipe } from './mds-summary.pipe'; import { MgrSummaryPipe } from './mgr-summary.pipe'; import { MillisecondsPipe } from './milliseconds.pipe'; import { NotAvailablePipe } from './not-available.pipe'; import { OrdinalPipe } from './ordinal.pipe'; -import { OsdSummaryPipe } from './osd-summary.pipe'; import { RbdConfigurationSourcePipe } from './rbd-configuration-source.pipe'; import { RelativeDatePipe } from './relative-date.pipe'; import { RoundPipe } from './round.pipe'; @@ -78,8 +76,6 @@ import { DimlessBinaryPerMinutePipe } from './dimless-binary-per-minute.pipe'; SearchHighlightPipe, HealthIconPipe, MgrSummaryPipe, - MdsSummaryPipe, - OsdSummaryPipe, OctalToHumanReadablePipe, PathPipe, PluralizePipe, @@ -121,8 +117,6 @@ import { DimlessBinaryPerMinutePipe } from './dimless-binary-per-minute.pipe'; SearchHighlightPipe, HealthIconPipe, MgrSummaryPipe, - MdsSummaryPipe, - OsdSummaryPipe, OctalToHumanReadablePipe, PathPipe, PluralizePipe, @@ -159,8 +153,6 @@ import { DimlessBinaryPerMinutePipe } from './dimless-binary-per-minute.pipe'; SanitizeHtmlPipe, HealthIconPipe, MgrSummaryPipe, - MdsSummaryPipe, - OsdSummaryPipe, OctalToHumanReadablePipe, MbpersecondPipe, DimlessBinaryPerMinutePipe diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 16d1e6ab51a..defb265b5e3 100755 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -4976,6 +4976,7 @@ paths: trace. security: - jwt: [] + summary: Get Cluster's detailed health report tags: - Health /api/health/get_cluster_capacity: @@ -5427,7 +5428,200 @@ paths: trace. security: - jwt: [] - summary: Get Cluster's minimal health report + summary: Get Cluster's health report with lesser details + tags: + - Health + /api/health/snapshot: + get: + parameters: [] + responses: + '200': + content: + application/vnd.ceph.api.v1.0+json: + schema: + properties: + fsid: + description: Cluster filesystem ID + type: string + fsmap: + description: Filesystem map details + properties: + num_active: + description: Number of active mds + type: integer + num_standbys: + description: Standby MDS count + type: integer + required: + - num_active + - num_standbys + type: object + health: + description: Cluster health overview + properties: + checks: + description: Health checks keyed by name + properties: + : + description: Individual health check object + properties: + muted: + description: Whether the check is muted + type: boolean + severity: + description: Health severity level + type: string + summary: + description: Summary details + properties: + count: + description: Occurrence count + type: integer + message: + description: Human-readable summary + type: string + required: + - message + - count + type: object + required: + - severity + - summary + - muted + type: object + required: + - + type: object + mutes: + description: List of muted check names + items: + type: string + type: array + status: + description: Overall health status + type: string + required: + - status + - checks + - mutes + type: object + mgrmap: + description: Manager map details + properties: + num_active: + description: Number of active managers + type: integer + num_standbys: + description: Standby manager count + type: integer + required: + - num_active + - num_standbys + type: object + monmap: + description: Monitor map details + properties: + num_mons: + description: Number of monitors + type: integer + required: + - num_mons + type: object + num_iscsi_gateways: + description: Iscsi gateways status + properties: + down: + description: Count of iSCSI gateways not running + type: integer + up: + description: Count of iSCSI gateways running + type: integer + required: + - up + - down + type: object + num_rgw_gateways: + description: Count of RGW gateway daemons running + type: integer + osdmap: + description: OSD map details + properties: + in: + description: Number of OSDs in + type: integer + num_osds: + description: Total OSD count + type: integer + up: + description: Number of OSDs up + type: integer + required: + - in + - up + - num_osds + type: object + pgmap: + description: Placement group map details + properties: + bytes_total: + description: Total capacity in bytes + type: integer + bytes_used: + description: Used capacity in bytes + type: integer + num_pgs: + description: Total PG count + type: integer + num_pools: + description: Number of pools + type: integer + pgs_by_state: + description: List of PG counts by state + items: + properties: + count: + description: Count of PGs in this state + type: integer + state_name: + description: Placement group state + type: string + required: + - state_name + - count + type: object + type: array + required: + - pgs_by_state + - num_pools + - num_pgs + - bytes_used + - bytes_total + type: object + required: + - fsid + - health + - monmap + - osdmap + - pgmap + - mgrmap + - fsmap + - num_rgw_gateways + - num_iscsi_gateways + 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 a quick overview of cluster health at a moment, analogous to the + ceph status command in CLI. tags: - Health /api/host: -- 2.39.5