import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
+import _ = require('lodash');
import { BsModalService } from 'ngx-bootstrap/modal';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { EMPTY, of } from 'rxjs';
let component: OsdListComponent;
let fixture: ComponentFixture<OsdListComponent>;
let modalServiceShowSpy: jasmine.Spy;
+ let osdService: OsdService;
const fakeAuthStorageService = {
getPermissions: () => {
fixture = TestBed.createComponent(OsdListComponent);
fixture.detectChanges();
component = fixture.componentInstance;
+ osdService = TestBed.get(OsdService);
modalServiceShowSpy = spyOn(TestBed.get(BsModalService), 'show').and.stub();
});
expect(component).toBeTruthy();
});
+ it('should have columns that are sortable', () => {
+ expect(component.columns.every((column) => Boolean(column.prop))).toBeTruthy();
+ });
+
+ describe('getOsdList', () => {
+ let osds;
+
+ const createOsd = (n: number) => ({
+ in: 'in',
+ up: 'up',
+ stats_history: {
+ op_out_bytes: [[n, n], [n * 2, n * 2]],
+ op_in_bytes: [[n * 3, n * 3], [n * 4, n * 4]]
+ },
+ stats: {
+ stat_bytes_used: n * n,
+ stat_bytes: n * n * n
+ }
+ });
+
+ const expectAttributeOnEveryOsd = (attr: string) =>
+ expect(component.osds.every((osd) => Boolean(_.get(osd, attr)))).toBeTruthy();
+
+ beforeEach(() => {
+ spyOn(osdService, 'getList').and.callFake(() => of(osds));
+ osds = [createOsd(1), createOsd(2), createOsd(3)];
+ component.getOsdList();
+ });
+
+ it('should replace "this.osds" with new data', () => {
+ expect(component.osds.length).toBe(3);
+ expect(osdService.getList).toHaveBeenCalledTimes(1);
+
+ osds = [createOsd(4)];
+ component.getOsdList();
+ expect(component.osds.length).toBe(1);
+ expect(osdService.getList).toHaveBeenCalledTimes(2);
+ });
+
+ it('should have custom attribute "collectedStates"', () => {
+ expectAttributeOnEveryOsd('collectedStates');
+ expect(component.osds[0].collectedStates).toEqual(['in', 'up']);
+ });
+
+ it('should have custom attribute "stats_history.out_bytes"', () => {
+ expectAttributeOnEveryOsd('stats_history.out_bytes');
+ expect(component.osds[0].stats_history.out_bytes).toEqual([1, 2]);
+ });
+
+ it('should have custom attribute "stats_history.in_bytes"', () => {
+ expectAttributeOnEveryOsd('stats_history.in_bytes');
+ expect(component.osds[0].stats_history.in_bytes).toEqual([3, 4]);
+ });
+
+ it('should have custom attribute "stats.usage"', () => {
+ expectAttributeOnEveryOsd('stats.usage');
+ expect(component.osds[0].stats.usage).toBe(1);
+ expect(component.osds[1].stats.usage).toBe(0.5);
+ expect(component.osds[2].stats.usage).toBe(3 / 9);
+ });
+
+ it('should have custom attribute "cdIsBinary" to be true', () => {
+ expectAttributeOnEveryOsd('cdIsBinary');
+ expect(component.osds[0].cdIsBinary).toBe(true);
+ });
+ });
+
describe('show table actions as defined', () => {
let tableActions: TableActionsComponent;
let scenario: { fn; empty; single };
describe('tests if the correct methods are called on confirmation', () => {
const expectOsdServiceMethodCalled = (
actionName: string,
- osdServiceMethodName: string
+ osdServiceMethodName: 'markOut' | 'markIn' | 'markDown' | 'markLost' | 'purge' | 'destroy'
): void => {
- const osdServiceSpy = spyOn(TestBed.get(OsdService), osdServiceMethodName).and.callFake(
- () => EMPTY
- );
+ const osdServiceSpy = spyOn(osdService, osdServiceMethodName).and.callFake(() => EMPTY);
openActionModal(actionName);
const initialState = modalServiceShowSpy.calls.first().args[1].initialState;
const submit = initialState.onSubmit || initialState.submitAction;
{ prop: 'collectedStates', name: this.i18n('Status'), cellTemplate: this.statusColor },
{ prop: 'stats.numpg', name: this.i18n('PGs') },
{ prop: 'stats.stat_bytes', name: this.i18n('Size'), pipe: this.dimlessBinaryPipe },
- { name: this.i18n('Usage'), cellTemplate: this.osdUsageTpl },
+ { prop: 'stats.usage', name: this.i18n('Usage'), cellTemplate: this.osdUsageTpl },
{
prop: 'stats_history.out_bytes',
name: this.i18n('Read bytes'),
getOsdList() {
this.osdService.getList().subscribe((data: any[]) => {
- this.osds = data;
- data.map((osd) => {
+ this.osds = data.map((osd) => {
osd.collectedStates = OsdListComponent.collectStates(osd);
osd.stats_history.out_bytes = osd.stats_history.op_out_bytes.map((i) => i[1]);
osd.stats_history.in_bytes = osd.stats_history.op_in_bytes.map((i) => i[1]);
+ osd.stats.usage = osd.stats.stat_bytes_used / osd.stats.stat_bytes;
osd.cdIsBinary = true;
return osd;
});
expect(component.counters).toEqual([]);
});
+ it('should have columns that are sortable', () => {
+ expect(component.columns.every((column) => Boolean(column.prop))).toBeTruthy();
+ });
+
describe('Error handling', () => {
const context = new CdTableFetchDataContext(() => {});
},
{
name: this.i18n('Value'),
+ prop: 'value',
cellTemplate: this.valueTpl,
flexGrow: 1
}
expect(component).toBeTruthy();
});
+ it('should have columns that are sortable', () => {
+ expect(component.columns.every((column) => Boolean(column.prop))).toBeTruthy();
+ });
+
describe('pool deletion', () => {
let taskWrapper: TaskWrapperService;
it('transforms pools data correctly', () => {
const pools = [
{
- stats: { rd_bytes: { latest: 6, rate: 4, series: [[0, 2], [1, 6]] } },
+ stats: {
+ bytes_used: { latest: 5, rate: 0, series: [] },
+ max_avail: { latest: 15, rate: 0, series: [] },
+ rd_bytes: { latest: 6, rate: 4, series: [[0, 2], [1, 6]] }
+ },
pg_status: { 'active+clean': 8, down: 2 }
}
];
cdIsBinary: true,
pg_status: '8 active+clean, 2 down',
stats: {
- bytes_used: { latest: 0, rate: 0, series: [] },
- max_avail: { latest: 0, rate: 0, series: [] },
+ bytes_used: { latest: 5, rate: 0, series: [] },
+ max_avail: { latest: 15, rate: 0, series: [] },
rd: { latest: 0, rate: 0, series: [] },
rd_bytes: { latest: 6, rate: 4, series: [2, 6] },
wr: { latest: 0, rate: 0, series: [] },
wr_bytes: { latest: 0, rate: 0, series: [] }
- }
+ },
+ usage: 0.25
}
];
+ expect(component.transformPoolsData(pools)).toEqual(expected);
+ });
+ it('transforms pools data correctly if stats are missing', () => {
+ const pools = [{}];
+ const expected = [
+ {
+ cdIsBinary: true,
+ pg_status: '',
+ stats: {
+ bytes_used: { latest: 0, rate: 0, series: [] },
+ max_avail: { latest: 0, rate: 0, series: [] },
+ rd: { latest: 0, rate: 0, series: [] },
+ rd_bytes: { latest: 0, rate: 0, series: [] },
+ wr: { latest: 0, rate: 0, series: [] },
+ wr_bytes: { latest: 0, rate: 0, series: [] }
+ },
+ usage: 0
+ }
+ ];
expect(component.transformPoolsData(pools)).toEqual(expected);
});
it('transforms empty pools data correctly', () => {
const pools = undefined;
const expected = undefined;
-
expect(component.transformPoolsData(pools)).toEqual(expected);
});
});
import { URLBuilderService } from '../../../shared/services/url-builder.service';
import { PgCategoryService } from '../../shared/pg-category.service';
import { Pool } from '../pool';
+import { PoolStats } from '../pool-stat';
const BASE_URL = 'pool';
name: this.i18n('Crush Ruleset'),
flexGrow: 3
},
- { name: this.i18n('Usage'), cellTemplate: this.poolUsageTpl, flexGrow: 3 },
+ {
+ name: this.i18n('Usage'),
+ prop: 'usage',
+ cellTemplate: this.poolUsageTpl,
+ flexGrow: 3
+ },
{
prop: 'stats.rd_bytes.series',
name: this.i18n('Read bytes'),
_.forEach(pools, (pool: Pool) => {
pool['pg_status'] = this.transformPgStatus(pool['pg_status']);
- const stats = {};
+ const stats: PoolStats = {};
_.forEach(requiredStats, (stat) => {
stats[stat] = pool.stats && pool.stats[stat] ? pool.stats[stat] : emptyStat;
});
pool['stats'] = stats;
+ const avail = stats.bytes_used.latest + stats.max_avail.latest;
+ pool['usage'] = avail > 0 ? stats.bytes_used.latest / avail : avail;
['rd_bytes', 'wr_bytes'].forEach((stat) => {
pool.stats[stat].series = pool.stats[stat].series.map((point) => point[1]);
rate: number;
series: number[];
}
+
+export class PoolStats {
+ bytes_used?: PoolStat;
+ max_avail?: PoolStat;
+ rd_bytes?: PoolStat;
+ wr_bytes?: PoolStat;
+ rd?: PoolStat;
+ wr?: PoolStat;
+}
import { ExecutingTask } from '../../shared/models/executing-task';
-import { PoolStat } from './pool-stat';
+import { PoolStats } from './pool-stat';
export class Pool {
cache_target_full_ratio_micro: number;
min_write_recency_for_promote: number;
read_tier: number;
pg_status: string;
- stats?: {
- bytes_used?: PoolStat;
- max_avail?: PoolStat;
- rd_bytes?: PoolStat;
- wr_bytes?: PoolStat;
- rd?: PoolStat;
- wr?: PoolStat;
- };
+ stats?: PoolStats;
cdIsBinary?: boolean;
configuration: { source: number; name: string; value: string }[];
-import { TableColumn } from '@swimlane/ngx-datatable';
+import { TableColumn, TableColumnProp } from '@swimlane/ngx-datatable';
+
import { CellTemplate } from '../enum/cell-template.enum';
export interface CdTableColumn extends TableColumn {
cellTransformation?: CellTemplate;
isHidden?: boolean;
+ prop: TableColumnProp; // Enforces properties to get sortable columns
}