From e5a04a3d486456b221d4d841e441b54467035c8e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stephan=20M=C3=BCller?= Date: Wed, 15 Aug 2018 12:02:48 +0200 Subject: [PATCH] mgr/dashboard: Use task list service in pool list MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Now the pool list uses the task list service to monitor all tasks, in order to extend each pool item with corresponding tasks if any. If there is a running task for a pool you will see what action runs on it in the listing. Fixes: https://tracker.ceph.com/issues/36355 Signed-off-by: Stephan Müller --- .../pool/pool-list/pool-list.component.html | 8 +- .../pool-list/pool-list.component.spec.ts | 85 ++++++++++++++++++- .../pool/pool-list/pool-list.component.ts | 48 +++++++---- .../frontend/src/app/ceph/pool/pool.ts | 8 ++ 4 files changed, 129 insertions(+), 20 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html index ea84ca0d036..7b5ec49d5eb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html @@ -1,7 +1,11 @@ - + + 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 318aea0f99e..8c3a56adba4 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 @@ -4,17 +4,22 @@ import { RouterTestingModule } from '@angular/router/testing'; import { ToastModule } from 'ng2-toastr'; import { BsModalService, TabsModule } from 'ngx-bootstrap'; +import { of } from 'rxjs'; import { configureTestBed } from '../../../../testing/unit-test-helper'; import { PoolService } from '../../../shared/api/pool.service'; import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component'; +import { ExecutingTask } from '../../../shared/models/executing-task'; +import { SummaryService } from '../../../shared/services/summary.service'; import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; import { SharedModule } from '../../../shared/shared.module'; +import { Pool } from '../pool'; import { PoolListComponent } from './pool-list.component'; describe('PoolListComponent', () => { let component: PoolListComponent; let fixture: ComponentFixture; + let poolService: PoolService; configureTestBed({ declarations: [PoolListComponent], @@ -30,8 +35,9 @@ describe('PoolListComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(PoolListComponent); component = fixture.componentInstance; - fixture.detectChanges(); component.permission.read = true; + poolService = TestBed.get(PoolService); + fixture.detectChanges(); }); it('should create', () => { @@ -39,7 +45,6 @@ describe('PoolListComponent', () => { }); describe('pool deletion', () => { - let poolService: PoolService; let taskWrapper: TaskWrapperService; const setSelectedPool = (poolName: string) => { @@ -74,7 +79,6 @@ describe('PoolListComponent', () => { content: Object.assign(new deletionClass(), config.initialState) }; }); - poolService = TestBed.get(PoolService); spyOn(poolService, 'delete').and.stub(); taskWrapper = TestBed.get(TaskWrapperService); spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough(); @@ -85,4 +89,79 @@ describe('PoolListComponent', () => { testPoolDeletion('aDifferentPoolName'); }); }); + + describe('handling of executing tasks', () => { + let pools: Pool[]; + let summaryService: SummaryService; + + const addPool = (name) => { + const pool = new Pool(name); + pool.pg_num = 256; + pools.push(pool); + }; + + const addTask = (name: string, pool: string) => { + const task = new ExecutingTask(); + task.name = name; + task.metadata = { pool_name: pool }; + summaryService.addRunningTask(task); + }; + + beforeEach(() => { + summaryService = TestBed.get(SummaryService); + summaryService['summaryDataSource'].next({ executing_tasks: [], finished_tasks: [] }); + pools = []; + addPool('a'); + addPool('b'); + addPool('c'); + component.pools = pools; + spyOn(poolService, 'getList').and.callFake(() => of(pools)); + fixture.detectChanges(); + }); + + it('gets all pools without executing pools', () => { + expect(component.pools.length).toBe(3); + expect(component.pools.every((pool) => !pool.executingTasks)).toBeTruthy(); + }); + + it('gets a pool from a task during creation', () => { + addTask('pool/create', 'd'); + expect(component.pools.length).toBe(4); + expect(component.pools[3].cdExecuting).toBe('Creating'); + }); + + it('gets all pools with one executing pools', () => { + addTask('pool/create', 'a'); + expect(component.pools.length).toBe(3); + expect(component.pools[0].cdExecuting).toBe('Creating'); + expect(component.pools[1].cdExecuting).toBeFalsy(); + expect(component.pools[2].cdExecuting).toBeFalsy(); + }); + + it('gets all pools with multiple executing pools', () => { + addTask('pool/create', 'a'); + addTask('pool/edit', 'a'); + addTask('pool/delete', 'a'); + addTask('pool/edit', 'b'); + addTask('pool/delete', 'b'); + addTask('pool/delete', 'c'); + expect(component.pools.length).toBe(3); + expect(component.pools[0].cdExecuting).toBe('Creating, Updating, Deleting'); + expect(component.pools[1].cdExecuting).toBe('Updating, Deleting'); + expect(component.pools[2].cdExecuting).toBe('Deleting'); + }); + + it('gets all pools with multiple executing tasks (not only pool tasks', () => { + addTask('rbd/create', 'a'); + addTask('rbd/edit', 'a'); + addTask('pool/delete', 'a'); + addTask('pool/edit', 'b'); + addTask('rbd/delete', 'b'); + addTask('rbd/delete', 'c'); + expect(component.pools.length).toBe(3); + expect(component.pools[0].cdExecuting).toBe('Deleting'); + expect(component.pools[1].cdExecuting).toBe('Updating'); + expect(component.pools[2].cdExecuting).toBeFalsy(); + }); + }); }); 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 887b74dcf7e..f6ddb2e7be9 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,26 +1,33 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { BsModalRef, BsModalService } from 'ngx-bootstrap'; import { PoolService } from '../../../shared/api/pool.service'; import { DeletionModalComponent } from '../../../shared/components/deletion-modal/deletion-modal.component'; +import { TableComponent } from '../../../shared/datatable/table/table.component'; +import { CellTemplate } from '../../../shared/enum/cell-template.enum'; +import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum'; import { CdTableAction } from '../../../shared/models/cd-table-action'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; -import { CdTableFetchDataContext } from '../../../shared/models/cd-table-fetch-data-context'; import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { ExecutingTask } from '../../../shared/models/executing-task'; import { FinishedTask } from '../../../shared/models/finished-task'; import { Permission } from '../../../shared/models/permissions'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; +import { TaskListService } from '../../../shared/services/task-list.service'; import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; import { Pool } from '../pool'; @Component({ selector: 'cd-pool-list', templateUrl: './pool-list.component.html', + providers: [TaskListService], styleUrls: ['./pool-list.component.scss'] }) -export class PoolListComponent { +export class PoolListComponent implements OnInit { + @ViewChild(TableComponent) + table: TableComponent; + pools: Pool[] = []; columns: CdTableColumn[]; selection = new CdTableSelection(); @@ -28,11 +35,13 @@ export class PoolListComponent { executingTasks: ExecutingTask[] = []; permission: Permission; tableActions: CdTableAction[]; + viewCacheStatusList: any[]; constructor( private poolService: PoolService, private taskWrapper: TaskWrapperService, private authStorageService: AuthStorageService, + private taskListService: TaskListService, private modalService: BsModalService ) { this.permission = this.authStorageService.getPermissions().pool; @@ -55,7 +64,8 @@ export class PoolListComponent { { prop: 'pool_name', name: 'Name', - flexGrow: 3 + flexGrow: 3, + cellTransformation: CellTemplate.executing }, { prop: 'type', @@ -98,21 +108,29 @@ export class PoolListComponent { ]; } - updateSelection(selection: CdTableSelection) { - this.selection = selection; - } - - getPoolList(context: CdTableFetchDataContext) { - this.poolService.getList().subscribe( - (pools: Pool[]) => { - this.pools = pools; - }, + ngOnInit() { + this.taskListService.init( + () => this.poolService.getList(), + undefined, + (pools) => (this.pools = pools), () => { - context.error(); - } + this.table.reset(); // Disable loading indicator. + this.viewCacheStatusList = [{ status: ViewCacheStatus.ValueException }]; + }, + (task) => task.name.startsWith('pool/'), + (pool, task) => task.metadata['pool_name'] === pool.pool_name, + { default: (task: ExecutingTask) => new Pool(task.metadata['pool_name']) } ); } + updateSelection(selection: CdTableSelection) { + if (selection.hasSingleSelection && Object.keys(selection.first()).length === 3) { + selection.selected = []; + selection.update(); + } + this.selection = selection; + } + deletePoolModal() { const name = this.selection.first().pool_name; this.modalRef = this.modalService.show(DeletionModalComponent, { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool.ts index 28a5bfd3a3d..cccab137439 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool.ts @@ -1,3 +1,5 @@ +import { ExecutingTask } from '../../shared/models/executing-task'; + export class Pool { cache_target_full_ratio_micro: number; fast_read: boolean; @@ -33,6 +35,8 @@ export class Pool { cache_target_dirty_ratio_micro: number; pool: number; removed_snaps: string; + cdExecuting?: string; + executingTasks?: ExecutingTask[]; crush_rule: string; tiers: any[]; hit_set_params: { @@ -56,4 +60,8 @@ export class Pool { last_change: string; min_write_recency_for_promote: number; read_tier: number; + + constructor(name) { + this.pool_name = name; + } } -- 2.39.5