From: Alfonso Martínez Date: Wed, 2 Dec 2020 12:53:34 +0000 (+0100) Subject: mgr/dashboard: Replace Replica size and Erasure code profile with a single column X-Git-Tag: v16.1.0~293^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=413d7a7adb8cbaf3bc8d82a33cb1a6aa0688d4e5;p=ceph.git mgr/dashboard: Replace Replica size and Erasure code profile with a single column Replaces Type, Replica Size & Erasure Coded Profile columns with 'Data Protection' column. Hidden Crush Rule set column in case if any admin finds it useful to show it can still be done by removing isHidden: true line. Fixes: https://tracker.ceph.com/issues/47901 Signed-off-by: Avan Thakkar Signed-off-by: Alfonso Martínez --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.spec.ts index 72871636adcc..f30f954b5b65 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.spec.ts @@ -129,7 +129,8 @@ describe('PoolDetailsComponent', () => { pg_placement_num_target: 256, pool: 2, pool_name: 'somePool', - type: 'replicated' + type: 'replicated', + size: 3 } }, Mocks.getPool('somePool', 2) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts index f7f8141255c9..8a8af7b73494 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts @@ -11,8 +11,10 @@ import { of } from 'rxjs'; import { RbdConfigurationListComponent } from '~/app/ceph/block/rbd-configuration-list/rbd-configuration-list.component'; import { PgCategoryService } from '~/app/ceph/shared/pg-category.service'; import { ConfigurationService } from '~/app/shared/api/configuration.service'; +import { ErasureCodeProfileService } from '~/app/shared/api/erasure-code-profile.service'; import { PoolService } from '~/app/shared/api/pool.service'; import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; +import { ErasureCodeProfile } from '~/app/shared/models/erasure-code-profile'; import { ExecutingTask } from '~/app/shared/models/executing-task'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { ModalService } from '~/app/shared/services/modal.service'; @@ -28,10 +30,19 @@ describe('PoolListComponent', () => { let component: PoolListComponent; let fixture: ComponentFixture; let poolService: PoolService; + let getECPList: jasmine.Spy; const getPoolList = (): Pool[] => { return [Mocks.getPool('a', 0), Mocks.getPool('b', 1), Mocks.getPool('c', 2)]; }; + const getECPProfiles = (): ErasureCodeProfile[] => { + const ecpProfile = new ErasureCodeProfile(); + ecpProfile.name = 'default'; + ecpProfile.k = 2; + ecpProfile.m = 1; + + return [ecpProfile]; + }; configureTestBed({ declarations: [PoolListComponent, PoolDetailsComponent, RbdConfigurationListComponent], @@ -52,6 +63,8 @@ describe('PoolListComponent', () => { component.permissions.pool.read = true; poolService = TestBed.inject(PoolService); spyOn(poolService, 'getList').and.callFake(() => of(getPoolList())); + getECPList = spyOn(TestBed.inject(ErasureCodeProfileService), 'list'); + getECPList.and.returnValue(of(getECPProfiles())); fixture.detectChanges(); }); @@ -305,7 +318,8 @@ describe('PoolListComponent', () => { wr: { latest: 0, rate: 0, rates: [] }, wr_bytes: { latest: 0, rate: 0, rates: [] } }, - usage: 0 + usage: 0, + data_protection: 'replica: ×3' }), o ) @@ -315,7 +329,7 @@ describe('PoolListComponent', () => { pool = Mocks.getPool('a', 0); }); - it('transforms pools data correctly', () => { + it('transforms replicated pools data correctly', () => { pool = _.merge(pool, { stats: { bytes_used: { latest: 5, rate: 0, rates: [] }, @@ -341,7 +355,22 @@ describe('PoolListComponent', () => { percent_used: { latest: 0.25, rate: 0, rates: [] }, rd_bytes: { latest: 6, rate: 4, rates: [2, 6] } }, - usage: 0.25 + usage: 0.25, + data_protection: 'replica: ×3' + }) + ); + }); + + it('transforms erasure pools data correctly', () => { + pool.type = 'erasure'; + pool.erasure_code_profile = 'default'; + component.ecProfileList = getECPProfiles(); + + expect(component.transformPoolsData([pool])).toEqual( + getPoolData({ + type: 'erasure', + erasure_code_profile: 'default', + data_protection: 'EC: 2+1' }) ); }); @@ -370,7 +399,8 @@ describe('PoolListComponent', () => { pg_num: 32, pg_num_target: 16, pg_placement_num: 32, - pg_placement_num_target: 16 + pg_placement_num_target: 16, + data_protection: 'replica: ×3' }) ); }); @@ -391,7 +421,8 @@ describe('PoolListComponent', () => { pg_num: 32, pg_num_target: 16, pg_placement_num: 32, - pg_placement_num_target: 16 + pg_placement_num_target: 16, + data_protection: 'replica: ×3' }) ); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts index 24773c2f2882..ba2d9cbe521b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts @@ -1,9 +1,11 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import _ from 'lodash'; +import { mergeMap } from 'rxjs/operators'; import { PgCategoryService } from '~/app/ceph/shared/pg-category.service'; import { ConfigurationService } from '~/app/shared/api/configuration.service'; +import { ErasureCodeProfileService } from '~/app/shared/api/erasure-code-profile.service'; import { PoolService } from '~/app/shared/api/pool.service'; import { ListWithDetails } from '~/app/shared/classes/list-with-details.class'; import { TableStatusViewCache } from '~/app/shared/classes/table-status-view-cache'; @@ -16,6 +18,7 @@ import { ViewCacheStatus } from '~/app/shared/enum/view-cache-status.enum'; import { CdTableAction } from '~/app/shared/models/cd-table-action'; import { CdTableColumn } from '~/app/shared/models/cd-table-column'; import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; +import { ErasureCodeProfile } from '~/app/shared/models/erasure-code-profile'; import { ExecutingTask } from '~/app/shared/models/executing-task'; import { FinishedTask } from '~/app/shared/models/finished-task'; import { Permissions } from '~/app/shared/models/permissions'; @@ -57,10 +60,12 @@ export class PoolListComponent extends ListWithDetails implements OnInit { tableStatus = new TableStatusViewCache(); cacheTiers: any[] = []; monAllowPoolDelete = false; + ecProfileList: ErasureCodeProfile[]; constructor( private poolService: PoolService, private taskWrapper: TaskWrapperService, + private ecpService: ErasureCodeProfileService, private authStorageService: AuthStorageService, public taskListService: TaskListService, private modalService: ModalService, @@ -120,44 +125,42 @@ export class PoolListComponent extends ListWithDetails implements OnInit { cellTransformation: CellTemplate.executing }, { - prop: 'type', - name: $localize`Type`, - flexGrow: 2 + prop: 'data_protection', + name: $localize`Data Protection`, + cellTransformation: CellTemplate.badge, + customTemplateConfig: { + class: 'badge-background-gray' + }, + flexGrow: 1.3 }, { prop: 'application_metadata', name: $localize`Applications`, - flexGrow: 3 + cellTransformation: CellTemplate.badge, + customTemplateConfig: { + class: 'badge-background-primary' + }, + flexGrow: 1.5 }, { prop: 'pg_status', name: $localize`PG Status`, - flexGrow: 3, + flexGrow: 1.2, cellClass: ({ row, column, value }): any => { return this.getPgStatusCellClass(row, column, value); } }, - { - prop: 'size', - name: $localize`Replica Size`, - flexGrow: 2, - cellClass: 'text-right' - }, - { - prop: 'erasure_code_profile', - name: $localize`Erasure Coded Profile`, - flexGrow: 2 - }, { prop: 'crush_rule', name: $localize`Crush Ruleset`, - flexGrow: 3 + isHidden: true, + flexGrow: 2 }, { name: $localize`Usage`, prop: 'usage', cellTemplate: this.poolUsageTpl, - flexGrow: 3 + flexGrow: 1.2 }, { prop: 'stats.rd_bytes.rates', @@ -165,7 +168,7 @@ export class PoolListComponent extends ListWithDetails implements OnInit { comparator: (_valueA: any, _valueB: any, rowA: Pool, rowB: Pool) => compare('stats.rd_bytes.latest', rowA, rowB), cellTransformation: CellTemplate.sparkline, - flexGrow: 3 + flexGrow: 1.5 }, { prop: 'stats.wr_bytes.rates', @@ -173,7 +176,7 @@ export class PoolListComponent extends ListWithDetails implements OnInit { comparator: (_valueA: any, _valueB: any, rowA: Pool, rowB: Pool) => compare('stats.wr_bytes.latest', rowA, rowB), cellTransformation: CellTemplate.sparkline, - flexGrow: 3 + flexGrow: 1.5 }, { prop: 'stats.rd.rate', @@ -192,7 +195,13 @@ export class PoolListComponent extends ListWithDetails implements OnInit { ]; this.taskListService.init( - () => this.poolService.getList(), + () => + this.ecpService.list().pipe( + mergeMap((ecProfileList: ErasureCodeProfile[]) => { + this.ecProfileList = ecProfileList; + return this.poolService.getList(); + }) + ), undefined, (pools) => { this.pools = this.transformPoolsData(pools); @@ -232,6 +241,16 @@ export class PoolListComponent extends ListWithDetails implements OnInit { }; } + getErasureCodeProfile(erasureCodeProfile: string) { + let ecpInfo = ''; + _.forEach(this.ecProfileList, (ecpKey) => { + if (ecpKey['name'] === erasureCodeProfile) { + ecpInfo = `EC: ${ecpKey['k']}+${ecpKey['m']}`; + } + }); + return ecpInfo; + } + transformPoolsData(pools: any) { const requiredStats = [ 'bytes_used', @@ -265,6 +284,14 @@ export class PoolListComponent extends ListWithDetails implements OnInit { pool.stats[stat].rates = pool.stats[stat].rates.map((point: any) => point[1]); }); pool.cdIsBinary = true; + + if (pool['type'] === 'erasure') { + const erasureCodeProfile = pool['erasure_code_profile']; + pool['data_protection'] = this.getErasureCodeProfile(erasureCodeProfile); + } + if (pool['type'] === 'replicated') { + pool['data_protection'] = `replica: ×${pool['size']}`; + } }); return pools; diff --git a/src/pybind/mgr/dashboard/frontend/src/styles.scss b/src/pybind/mgr/dashboard/frontend/src/styles.scss index 7d3538f5bf57..b4dcf0e431eb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles.scss @@ -88,11 +88,13 @@ $grid-breakpoints: ( } // Custom badges. +.badge-background-gray, .badge-hdd { background-color: $gray-600; color: $white; } +.badge-background-primary, .badge-ssd { background-color: $primary; color: $white; diff --git a/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts b/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts index 28f88e1fac86..3e3d27a7ce64 100644 --- a/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts +++ b/src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts @@ -404,7 +404,8 @@ export class Mocks { pg_num: 256, pg_placement_num: 256, pg_num_target: 256, - pg_placement_num_target: 256 + pg_placement_num_target: 256, + size: 3 }); };