]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Improve notifications for osd nearfull, full 44024/head
authorAashish Sharma <aashishsharma@localhost.localdomain>
Fri, 19 Nov 2021 09:02:49 +0000 (14:32 +0530)
committerAashish Sharma <aashishsharma@localhost.localdomain>
Wed, 19 Jan 2022 11:05:27 +0000 (16:35 +0530)
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 <aasharma@redhat.com>
16 files changed:
qa/tasks/mgr/dashboard/test_health.py
src/pybind/mgr/dashboard/controllers/health.py
src/pybind/mgr/dashboard/controllers/osd.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/osd-summary.pipe.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.scss
src/pybind/mgr/dashboard/frontend/src/app/shared/components/usage-bar/usage-bar.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/osd-settings.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss
src/pybind/mgr/dashboard/openapi.yaml

index 11ffd790ac20c1ec68706cb98dbaf54ac5bcc891..92de402e20561628e64df16ac79ac3ce577b9a99 100644 (file)
@@ -112,6 +112,7 @@ class HealthTest(DashboardTestCase):
                     JObj({
                         'in': int,
                         'up': int,
+                        'state': JList(str)
                     })),
             }),
             'pg_info': self.__pg_info_schema,
index f0964ba19239d3a8bcc8b7aa394ba6d9ab61ed0b..992b2bc0277b50d8db6b6a6e7aaa6598e4dfe951 100644 (file)
@@ -248,7 +248,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:
index 67db1ad1d9d3da5e2023551b4ca8964401b1ca03..ceebb5acdafbfde4129831fdbe34ee64deddff6d 100644 (file)
@@ -18,6 +18,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')
@@ -93,6 +94,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'
index 9f4f3e2152cf4067322cb6e23ebe89a68a25b2f6..3e6f1475afff8da90f0ea53f2877555d4777c8ac 100644 (file)
@@ -86,7 +86,9 @@
 <ng-template #osdUsageTpl
              let-row="row">
   <cd-usage-bar [total]="row.stats.stat_bytes"
-                [used]="row.stats.stat_bytes_used">
+                [used]="row.stats.stat_bytes_used"
+                [warningThreshold]="osdSettings.nearfull_ratio"
+                [errorThreshold]="osdSettings.full_ratio">
   </cd-usage-bar>
 </ng-template>
 
index 45dc840655ddd727c3bd12f4e85389114e56a2aa..9f418e0e3426c065fc75b7ae0a4f235e3a20d73d 100644 (file)
@@ -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 {
index 8b45a05a2afab5c0a4223ab4b1c19ec4c86650d1..1294f5922db523756d6137ff25f73e4dfade659f 100644 (file)
@@ -27,7 +27,7 @@ cd-info-card {
 }
 
 .card-text-error {
-  color: vv.$danger;
+  color: vv.$chart-danger;
   display: inline;
 }
 
index 00c72395aac9de5d73e953b50b77458f17a7b095..4d1dac769a4b3a2e12ab995e48b28e7c29c2960f 100644 (file)
@@ -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 = [
index f42a55dd4270b243312476e282b5c1224304c0b2..22f5eeff3853b4b00dc6d239b1c2b9875d413e46 100644 (file)
@@ -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([
index 26714ff2ae6bb6957f175321aedd4656b4c66eac..46d2eda6bb79b4b805224f74ca1eef75f34e03c9 100644 (file)
@@ -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;
   }
 }
index e33f2c3fc0fa1df8cac30fcc2dd4e36693485678..c8f881d5e13f540641ed983760fa6d0d3710555f 100644 (file)
@@ -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<OsdSettings> {
+    return this.http.get<OsdSettings>(`${this.path}/settings`, {
+      headers: { Accept: 'application/vnd.ceph.api.v0.1+json' }
+    });
+  }
+
   getDetails(id: number) {
     interface OsdData {
       osd_map: { [key: string]: any };
index 0ecf79f1cdf9a1c87fa452e1f0211a35f3c8936c..655215c45bfef1be2cc9a5a51891466c05b997aa 100644 (file)
@@ -15,6 +15,7 @@
      data-placement="left"
      [ngbTooltip]="usageTooltipTpl">
   <div class="progress-bar bg-info"
+       [ngClass]="{'bg-warning': usedPercentage/100 >= warningThreshold, 'bg-danger': usedPercentage/100 >= errorThreshold}"
        role="progressbar"
        [style.width]="usedPercentage + '%'">
     <span>{{ usedPercentage | number: '1.0-' + decimals }}%</span>
index ef97d15d14da9804b98290df2711517ca586e8b5..e9d6d24984db1af9ece937bddd0d72dd9a7411a0 100644 (file)
@@ -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;
 }
index fb0dbd9a978504fb6b8f469c97863a56cdf5e146..bb11e4e80c80f128f217dde4c70b5aeab6df206d 100644 (file)
@@ -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 (file)
index 0000000..b7bc10f
--- /dev/null
@@ -0,0 +1,4 @@
+export class OsdSettings {
+  nearfull_ratio: number;
+  full_ratio: number;
+}
index 1e203af7c8c4823f9a2a98da0f1c0e818c197d98..6a9dd645f07c58a74cce309812940b1ae7166b8e 100644 (file)
@@ -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
 
index 43532585aa8e68ef70f46162947a47b11ee92444..bc4292876a95f759bfd6419334bf4ef5eeb9ed15 100644 (file)
@@ -6232,6 +6232,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: