JObj({
'in': int,
'up': int,
+ 'state': JList(str)
})),
}),
'pg_info': self.__pg_info_schema,
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:
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')
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'
<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>
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';
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';
columns: CdTableColumn[];
clusterWideActions: CdTableAction[];
icons = Icons;
+ osdSettings = new OsdSettings();
selection = new CdTableSelection();
osds: any[] = [];
];
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 {
}
.card-text-error {
- color: vv.$danger;
+ color: vv.$chart-danger;
display: inline;
}
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';
})
export class HealthComponent implements OnInit, OnDestroy {
healthData: any;
+ osdSettings = new OsdSettings();
interval = new Subscription();
permissions: Permissions;
enabledFeature$: FeatureTogglesMap$;
icons = Icons;
+ color: string;
clientStatsConfig = {
colors: [
constructor(
private healthService: HealthService,
+ private osdService: OsdService,
private authStorageService: AuthStorageService,
private pgCategoryService: PgCategoryService,
private featureToggles: FeatureTogglesService,
this.interval = this.refreshIntervalService.intervalData$.subscribe(() => {
this.getHealth();
});
+
+ this.osdService
+ .getOsdSettings()
+ .pipe(take(1))
+ .subscribe((data: any) => {
+ this.osdSettings = data;
+ });
}
ngOnDestroy() {
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 = [
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([
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([
]);
});
- 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([
{
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'
}
]);
});
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([
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([
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 osdSummary = [
});
}
+ 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;
}
}
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';
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 };
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>
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;
}
import { Component, Input, OnChanges } from '@angular/core';
+import _ from 'lodash';
+
@Component({
selector: 'cd-usage-bar',
templateUrl: './usage-bar.component.html',
@Input()
used: number;
@Input()
+ warningThreshold: number;
+ @Input()
+ errorThreshold: number;
+ @Input()
isBinary = true;
@Input()
decimals = 0;
--- /dev/null
+export class OsdSettings {
+ nearfull_ratio: number;
+ full_ratio: number;
+}
$chart-color-center-text: #151515 !default;
$chart-color-center-text-description: #72767b !default;
$chart-color-tooltip-background: $black !default;
+$chart-danger: #c9190b !default;
// Typography
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: