]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Use task list service in pool list
authorStephan Müller <smueller@suse.com>
Wed, 15 Aug 2018 10:02:48 +0000 (12:02 +0200)
committerStephan Müller <smueller@suse.com>
Tue, 9 Oct 2018 13:56:28 +0000 (15:56 +0200)
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 <smueller@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool.ts

index ea84ca0d036aa11a029c3d1ceb35787b9e9a1bd6..7b5ec49d5ebe3722efc7f962a7c37744e0767a24 100644 (file)
@@ -1,7 +1,11 @@
 <tabset>
   <tab i18n-heading heading="Pools List">
-    <cd-table [data]="pools"
-              (fetchData)="getPoolList($event)"
+    <cd-view-cache *ngFor="let viewCacheStatus of viewCacheStatusList"
+                   [status]="viewCacheStatus.status"
+                   [statusFor]="viewCacheStatus.statusFor"></cd-view-cache>
+
+    <cd-table #table
+              [data]="pools"
               [columns]="columns"
               selectionType="single"
               (updateSelection)="updateSelection($event)">
index 318aea0f99ee71fbc3fce4337b59fd7300b27e68..8c3a56adba4efc2e62a7ddd8420029aeed5f9a32 100644 (file)
@@ -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<PoolListComponent>;
+  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();
+    });
+  });
 });
index 887b74dcf7e612cb0fafb0ba5e3b858e40ff0c24..f6ddb2e7be9656f303f18a7785b34bafd89eefea 100644 (file)
@@ -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, {
index 28a5bfd3a3d448bd322e33d96a40dc3efc44e200..cccab137439a41ca9a2b3ede51f1653dc9f41da6 100644 (file)
@@ -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;
+  }
 }