From 4a02aa5541e7f6b1b64e3be19f61833b32ca99df Mon Sep 17 00:00:00 2001 From: Aashish Sharma Date: Fri, 19 Nov 2021 14:32:49 +0530 Subject: [PATCH] mgr/dashboard: Improve notifications for osd nearfull, full This PR adds some visual hints for osds that are near full or full Fixes: https://tracker.ceph.com/issues/53334 Signed-off-by: Aashish Sharma (cherry picked from commit f771cd492cd06da13f26e5f7ffe41b2d3c43f950) --- qa/tasks/mgr/dashboard/test_health.py | 1 + .../mgr/dashboard/controllers/health.py | 2 +- src/pybind/mgr/dashboard/controllers/osd.py | 10 ++++ .../osd/osd-list/osd-list.component.html | 4 +- .../osd/osd-list/osd-list.component.ts | 10 ++++ .../dashboard/health/health.component.scss | 2 +- .../ceph/dashboard/health/health.component.ts | 24 +++++++++ .../ceph/dashboard/osd-summary.pipe.spec.ts | 50 ++++++++++++------- .../app/ceph/dashboard/osd-summary.pipe.ts | 32 ++++++++++++ .../src/app/shared/api/osd.service.ts | 8 +++ .../usage-bar/usage-bar.component.html | 1 + .../usage-bar/usage-bar.component.scss | 8 +++ .../usage-bar/usage-bar.component.ts | 6 +++ .../src/app/shared/models/osd-settings.ts | 4 ++ .../styles/defaults/_bootstrap-defaults.scss | 1 + src/pybind/mgr/dashboard/openapi.yaml | 22 ++++++++ 16 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/osd-settings.ts diff --git a/qa/tasks/mgr/dashboard/test_health.py b/qa/tasks/mgr/dashboard/test_health.py index 7efd7b80db40d..693b7b65cdf02 100644 --- a/qa/tasks/mgr/dashboard/test_health.py +++ b/qa/tasks/mgr/dashboard/test_health.py @@ -106,6 +106,7 @@ class HealthTest(DashboardTestCase): JObj({ 'in': int, 'up': int, + 'state': JList(str) })), }), 'pg_info': self.__pg_info_schema, diff --git a/src/pybind/mgr/dashboard/controllers/health.py b/src/pybind/mgr/dashboard/controllers/health.py index ed7e575de63c2..304402d35a7de 100644 --- a/src/pybind/mgr/dashboard/controllers/health.py +++ b/src/pybind/mgr/dashboard/controllers/health.py @@ -249,7 +249,7 @@ class HealthData(object): if self._minimal: osd_map = partial_dict(osd_map, ['osds']) osd_map['osds'] = [ - partial_dict(item, ['in', 'up']) + partial_dict(item, ['in', 'up', 'state']) for item in osd_map['osds'] ] else: diff --git a/src/pybind/mgr/dashboard/controllers/osd.py b/src/pybind/mgr/dashboard/controllers/osd.py index 95b6e7a04dc75..ca4bf58c68310 100644 --- a/src/pybind/mgr/dashboard/controllers/osd.py +++ b/src/pybind/mgr/dashboard/controllers/osd.py @@ -19,6 +19,7 @@ from ..tools import str_to_bool from . import APIDoc, APIRouter, CreatePermission, DeletePermission, Endpoint, \ EndpointDoc, ReadPermission, RESTController, Task, UpdatePermission, \ allow_empty_body +from ._version import APIVersion from .orchestrator import raise_if_no_orchestrator logger = logging.getLogger('controllers.osd') @@ -94,6 +95,15 @@ class Osd(RESTController): osd['operational_status'] = self._get_operational_status(osd_id, removing_osd_ids) return list(osds.values()) + @RESTController.Collection('GET', version=APIVersion.EXPERIMENTAL) + @ReadPermission + def settings(self): + result = CephService.send_command('mon', 'osd dump') + return { + 'nearfull_ratio': result['nearfull_ratio'], + 'full_ratio': result['full_ratio'] + } + def _get_operational_status(self, osd_id: int, removing_osd_ids: Optional[List[int]]): if removing_osd_ids is None: return 'unmanaged' diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html index 9f4f3e2152cf4..3e6f1475afff8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html @@ -86,7 +86,9 @@ + [used]="row.stats.stat_bytes_used" + [warningThreshold]="osdSettings.nearfull_ratio" + [errorThreshold]="osdSettings.full_ratio"> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts index 45dc840655ddd..9f418e0e3426c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts @@ -5,6 +5,7 @@ import { Router } from '@angular/router'; import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import _ from 'lodash'; import { forkJoin as observableForkJoin, Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; import { OrchestratorService } from '~/app/shared/api/orchestrator.service'; import { OsdService } from '~/app/shared/api/osd.service'; @@ -23,6 +24,7 @@ import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; import { FinishedTask } from '~/app/shared/models/finished-task'; import { OrchestratorFeature } from '~/app/shared/models/orchestrator.enum'; import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface'; +import { OsdSettings } from '~/app/shared/models/osd-settings'; import { Permissions } from '~/app/shared/models/permissions'; import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; @@ -67,6 +69,7 @@ export class OsdListComponent extends ListWithDetails implements OnInit { columns: CdTableColumn[]; clusterWideActions: CdTableAction[]; icons = Icons; + osdSettings = new OsdSettings(); selection = new CdTableSelection(); osds: any[] = []; @@ -344,6 +347,13 @@ export class OsdListComponent extends ListWithDetails implements OnInit { ]; this.orchService.status().subscribe((status: OrchestratorStatus) => (this.orchStatus = status)); + + this.osdService + .getOsdSettings() + .pipe(take(1)) + .subscribe((data: any) => { + this.osdSettings = data; + }); } getDisable(action: 'create' | 'delete', selection: CdTableSelection): boolean | string { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.scss index 8b45a05a2afab..1294f5922db52 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.scss @@ -27,7 +27,7 @@ cd-info-card { } .card-text-error { - color: vv.$danger; + color: vv.$chart-danger; display: inline; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts index 00c72395aac9d..4d1dac769a4b3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts @@ -2,11 +2,14 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import _ from 'lodash'; import { Subscription } from 'rxjs'; +import { take } from 'rxjs/operators'; import { PgCategoryService } from '~/app/ceph/shared/pg-category.service'; import { HealthService } from '~/app/shared/api/health.service'; +import { OsdService } from '~/app/shared/api/osd.service'; import { CssHelper } from '~/app/shared/classes/css-helper'; import { Icons } from '~/app/shared/enum/icons.enum'; +import { OsdSettings } from '~/app/shared/models/osd-settings'; import { Permissions } from '~/app/shared/models/permissions'; import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe'; import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe'; @@ -24,10 +27,12 @@ import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.s }) export class HealthComponent implements OnInit, OnDestroy { healthData: any; + osdSettings = new OsdSettings(); interval = new Subscription(); permissions: Permissions; enabledFeature$: FeatureTogglesMap$; icons = Icons; + color: string; clientStatsConfig = { colors: [ @@ -59,6 +64,7 @@ export class HealthComponent implements OnInit, OnDestroy { constructor( private healthService: HealthService, + private osdService: OsdService, private authStorageService: AuthStorageService, private pgCategoryService: PgCategoryService, private featureToggles: FeatureTogglesService, @@ -75,6 +81,13 @@ export class HealthComponent implements OnInit, OnDestroy { this.interval = this.refreshIntervalService.intervalData$.subscribe(() => { this.getHealth(); }); + + this.osdService + .getOsdSettings() + .pipe(take(1)) + .subscribe((data: any) => { + this.osdSettings = data; + }); } ngOnDestroy() { @@ -149,6 +162,17 @@ export class HealthComponent implements OnInit, OnDestroy { data.df.stats.total_bytes ); + if (percentUsed / 100 >= this.osdSettings.nearfull_ratio) { + this.color = 'chart-color-red'; + } else if (percentUsed / 100 >= this.osdSettings.full_ratio) { + this.color = 'chart-color-yellow'; + } else { + this.color = 'chart-color-blue'; + } + this.rawCapacityChartConfig.colors[0].backgroundColor[0] = this.cssHelper.propertyValue( + this.color + ); + chart.dataset[0].data = [percentUsed, percentAvailable]; chart.labels = [ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts index f42a55dd4270b..22f5eeff3853b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts @@ -25,9 +25,9 @@ describe('OsdSummaryPipe', () => { it('transforms having 3 osd with 3 up, 3 in, 0 down, 0 out', () => { const value = { osds: [ - { up: 1, in: 1 }, - { up: 1, in: 1 }, - { up: 1, in: 1 } + { 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([ @@ -49,9 +49,9 @@ describe('OsdSummaryPipe', () => { it('transforms having 3 osd with 2 up, 1 in, 1 down, 2 out', () => { const value = { osds: [ - { up: 1, in: 1 }, - { up: 1, in: 0 }, - { up: 0, in: 0 } + { up: 1, in: 1, state: ['up', 'exists'] }, + { up: 1, in: 0, state: ['up', 'exists'] }, + { up: 0, in: 0, state: ['exists'] } ] }; expect(pipe.transform(value)).toEqual([ @@ -78,12 +78,12 @@ describe('OsdSummaryPipe', () => { ]); }); - it('transforms having 3 osd with 2 up, 3 in, 1 down, 0 out', () => { + it('transforms having 3 osd with 2 up, 3 in, 1 full, 1 nearfull, 1 down, 0 out', () => { const value = { osds: [ - { up: 1, in: 1 }, - { up: 1, in: 1 }, - { up: 0, in: 1 } + { up: 1, in: 1, state: ['up', 'nearfull'] }, + { up: 1, in: 1, state: ['up', 'exists'] }, + { up: 0, in: 1, state: ['full'] } ] }; expect(pipe.transform(value)).toEqual([ @@ -106,6 +106,22 @@ describe('OsdSummaryPipe', () => { { content: '1 down', class: 'card-text-error' + }, + { + content: '', + class: 'card-text-line-break' + }, + { + content: '1 near full', + class: 'card-text-error' + }, + { + content: '', + class: 'card-text-line-break' + }, + { + content: '1 full', + class: 'card-text-error' } ]); }); @@ -113,9 +129,9 @@ describe('OsdSummaryPipe', () => { it('transforms having 3 osd with 3 up, 2 in, 0 down, 1 out', () => { const value = { osds: [ - { up: 1, in: 1 }, - { up: 1, in: 1 }, - { up: 1, in: 0 } + { up: 1, in: 1, state: ['up', 'exists'] }, + { up: 1, in: 1, state: ['up', 'exists'] }, + { up: 1, in: 0, state: ['up', 'exists'] } ] }; expect(pipe.transform(value)).toEqual([ @@ -145,10 +161,10 @@ describe('OsdSummaryPipe', () => { it('transforms having 4 osd with 3 up, 2 in, 1 down, another 2 out', () => { const value = { osds: [ - { up: 1, in: 1 }, - { up: 1, in: 0 }, - { up: 1, in: 0 }, - { up: 0, in: 1 } + { up: 1, in: 1, state: ['up', 'exists'] }, + { up: 1, in: 0, state: ['up', 'exists'] }, + { up: 1, in: 0, state: ['up', 'exists'] }, + { up: 0, in: 1, state: ['exists'] } ] }; expect(pipe.transform(value)).toEqual([ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts index 26714ff2ae6bb..46d2eda6bb79b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts @@ -13,6 +13,8 @@ export class OsdSummaryPipe implements PipeTransform { let inCount = 0; let upCount = 0; + let nearFullCount = 0; + let fullCount = 0; _.each(value.osds, (osd) => { if (osd.in) { inCount++; @@ -20,6 +22,12 @@ export class OsdSummaryPipe implements PipeTransform { if (osd.up) { upCount++; } + if (osd.state.includes('nearfull')) { + nearFullCount++; + } + if (osd.state.includes('full')) { + fullCount++; + } }); const osdSummary = [ @@ -54,6 +62,30 @@ export class OsdSummaryPipe implements PipeTransform { }); } + if (nearFullCount > 0) { + osdSummary.push( + { + content: '', + class: 'card-text-line-break' + }, + { + content: `${nearFullCount} ${$localize`near full`}`, + class: 'card-text-error' + }, + { + content: '', + class: 'card-text-line-break' + } + ); + } + + if (fullCount > 0) { + osdSummary.push({ + content: `${fullCount} ${$localize`full`}`, + class: 'card-text-error' + }); + } + return osdSummary; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts index e33f2c3fc0fa1..c8f881d5e13f5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts @@ -2,10 +2,12 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import _ from 'lodash'; +import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { CdDevice } from '../models/devices'; import { InventoryDeviceType } from '../models/inventory-device-type.model'; +import { OsdSettings } from '../models/osd-settings'; import { SmartDataResponseV1 } from '../models/smart'; import { DeviceService } from '../services/device.service'; @@ -76,6 +78,12 @@ export class OsdService { return this.http.get(`${this.path}`); } + getOsdSettings(): Observable { + return this.http.get(`${this.path}/settings`, { + headers: { Accept: 'application/vnd.ceph.api.v0.1+json' } + }); + } + getDetails(id: number) { interface OsdData { osd_map: { [key: string]: any }; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.html index 0ecf79f1cdf9a..655215c45bfef 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.html @@ -15,6 +15,7 @@ data-placement="left" [ngbTooltip]="usageTooltipTpl">
{{ usedPercentage | number: '1.0-' + decimals }}% diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.scss index ef97d15d14da9..e9d6d24984db1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.scss @@ -4,6 +4,14 @@ background-color: vv.$primary !important; } +.bg-warning { + background-color: vv.$warning !important; +} + +.bg-danger { + background-color: vv.$danger !important; +} + .bg-freespace { background-color: vv.$gray-400 !important; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.ts index fb0dbd9a97850..bb11e4e80c80f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.ts @@ -1,5 +1,7 @@ import { Component, Input, OnChanges } from '@angular/core'; +import _ from 'lodash'; + @Component({ selector: 'cd-usage-bar', templateUrl: './usage-bar.component.html', @@ -11,6 +13,10 @@ export class UsageBarComponent implements OnChanges { @Input() used: number; @Input() + warningThreshold: number; + @Input() + errorThreshold: number; + @Input() isBinary = true; @Input() decimals = 0; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/osd-settings.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/osd-settings.ts new file mode 100644 index 0000000000000..b7bc10fc02e05 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/osd-settings.ts @@ -0,0 +1,4 @@ +export class OsdSettings { + nearfull_ratio: number; + full_ratio: number; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss b/src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss index 1e203af7c8c48..6a9dd645f07c5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss @@ -68,6 +68,7 @@ $chart-color-purple: #3c3d99 !default; $chart-color-center-text: #151515 !default; $chart-color-center-text-description: #72767b !default; $chart-color-tooltip-background: $black !default; +$chart-danger: #c9190b !default; // Typography diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 53c93bb10474d..5848fb8d544e0 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -6070,6 +6070,28 @@ paths: summary: Check If OSD is Safe to Destroy tags: - OSD + /api/osd/settings: + get: + parameters: [] + responses: + '200': + content: + application/vnd.ceph.api.v0.1+json: + 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: + - OSD /api/osd/{svc_id}: delete: parameters: -- 2.39.5