From: Ricardo Marques Date: Wed, 31 Jan 2018 13:57:36 +0000 (+0000) Subject: mgr/dashboard_v2: Add block pools page X-Git-Tag: v13.0.2~84^2~83 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=407c84b80aa0c543caa38923301ba5dc457f3f48;p=ceph.git mgr/dashboard_v2: Add block pools page Signed-off-by: Ricardo Marques --- diff --git a/src/pybind/mgr/dashboard_v2/controllers/block_pool.py b/src/pybind/mgr/dashboard_v2/controllers/block_pool.py new file mode 100644 index 0000000000000..c62f3500f9e57 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/controllers/block_pool.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +import cherrypy +import rbd + +from ..tools import ApiController, AuthRequired, BaseController, ViewCache + + +@ApiController('block_pool') +@AuthRequired() +class BlockPool(BaseController): + + def __init__(self): + self.rbd = None + + def _format_bitmask(self, features): + RBD_FEATURES_NAME_MAPPING = { + rbd.RBD_FEATURE_LAYERING: "layering", + rbd.RBD_FEATURE_STRIPINGV2: "striping", + rbd.RBD_FEATURE_EXCLUSIVE_LOCK: "exclusive-lock", + rbd.RBD_FEATURE_OBJECT_MAP: "object-map", + rbd.RBD_FEATURE_FAST_DIFF: "fast-diff", + rbd.RBD_FEATURE_DEEP_FLATTEN: "deep-flatten", + rbd.RBD_FEATURE_JOURNALING: "journaling", + rbd.RBD_FEATURE_DATA_POOL: "data-pool", + rbd.RBD_FEATURE_OPERATIONS: "operations", + } + names = [val for key, val in RBD_FEATURES_NAME_MAPPING.items() + if key & features == key] + return ', '.join(sorted(names)) + + @ViewCache() + def _rbd_list(self, pool_name): + ioctx = self.mgr.rados.open_ioctx(pool_name) + self.rbd = rbd.RBD() + names = self.rbd.list(ioctx) + result = [] + for name in names: + i = rbd.Image(ioctx, name) + stat = i.stat() + stat['name'] = name + features = i.features() + stat['features'] = features + stat['features_name'] = self._format_bitmask(features) + + try: + parent_info = i.parent_info() + parent = "{}@{}".format(parent_info[0], parent_info[1]) + if parent_info[0] != pool_name: + parent = "{}/{}".format(parent_info[0], parent) + stat['parent'] = parent + except rbd.ImageNotFound: + pass + result.append(stat) + return result + + @cherrypy.expose + @cherrypy.tools.json_out() + def rbd_pool_data(self, pool_name): + # pylint: disable=unbalanced-tuple-unpacking + status, value = self._rbd_list(pool_name) + if status == ViewCache.VALUE_EXCEPTION: + raise value + return {'status': status, 'value': value} diff --git a/src/pybind/mgr/dashboard_v2/controllers/dashboard.py b/src/pybind/mgr/dashboard_v2/controllers/dashboard.py index ea1c06d98f459..1ec3bd0d79c20 100644 --- a/src/pybind/mgr/dashboard_v2/controllers/dashboard.py +++ b/src/pybind/mgr/dashboard_v2/controllers/dashboard.py @@ -9,7 +9,7 @@ import time import cherrypy from mgr_module import CommandResult -from ..tools import ApiController, AuthRequired, BaseController, NotificationQueue +from ..tools import ApiController, AuthRequired, BaseController, NotificationQueue, ViewCache LOG_BUFFER_SIZE = 30 @@ -54,6 +54,13 @@ class Dashboard(BaseController): for l in lines: buf.appendleft(l) + @ViewCache() + def _rbd_pool_ls(self): + osd_map = self.mgr.get("osd_map") + rbd_pools = [pool['pool_name'] for pool in osd_map['pools'] if + 'rbd' in pool.get('application_metadata', {})] + return rbd_pools + @cherrypy.expose @cherrypy.tools.json_out() def toplevel(self): @@ -67,9 +74,16 @@ class Dashboard(BaseController): for f in fsmap['filesystems'] ] + _, data = self._rbd_pool_ls() + if data is None: + self.mgr.log.warning("Failed to get RBD pool list") + data = [] + data.sort() + return { 'health_status': self.health_data()['status'], 'filesystems': filesystems, + 'rbd_pools': data } # pylint: disable=R0914 diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts index 237867c83ec59..231da6ae79083 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/app-routing.module.ts @@ -1,6 +1,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { PoolDetailComponent } from './ceph/block/pool-detail/pool-detail.component'; import { HostsComponent } from './ceph/cluster/hosts/hosts.component'; import { DashboardComponent } from './ceph/dashboard/dashboard/dashboard.component'; import { LoginComponent } from './core/auth/login/login.component'; @@ -14,7 +15,8 @@ const routes: Routes = [ canActivate: [AuthGuardService] }, { path: 'login', component: LoginComponent }, - { path: 'hosts', component: HostsComponent, canActivate: [AuthGuardService] } + { path: 'hosts', component: HostsComponent, canActivate: [AuthGuardService] }, + { path: 'block/pool/:name', component: PoolDetailComponent, canActivate: [AuthGuardService] } ]; @NgModule({ diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts index e7925a0743f2d..3cca10d09170b 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/app.component.spec.ts @@ -4,6 +4,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { ToastModule } from 'ng2-toastr'; import { AppComponent } from './app.component'; +import { BlockModule } from './ceph/block/block.module'; import { ClusterModule } from './ceph/cluster/cluster.module'; import { CoreModule } from './core/core.module'; import { SharedModule } from './shared/shared.module'; @@ -17,7 +18,8 @@ describe('AppComponent', () => { CoreModule, SharedModule, ToastModule.forRoot(), - ClusterModule + ClusterModule, + BlockModule ], declarations: [AppComponent] }).compileComponents(); diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/block.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/block.module.ts new file mode 100644 index 0000000000000..da6c9c1f3a2ad --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/block.module.ts @@ -0,0 +1,24 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; + +import { AlertModule, TabsModule } from 'ngx-bootstrap'; + +import { ComponentsModule } from '../../shared/components/components.module'; +import { PipesModule } from '../../shared/pipes/pipes.module'; +import { SharedModule } from '../../shared/shared.module'; +import { PoolDetailComponent } from './pool-detail/pool-detail.component'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + TabsModule.forRoot(), + AlertModule.forRoot(), + SharedModule, + ComponentsModule, + PipesModule + ], + declarations: [PoolDetailComponent] +}) +export class BlockModule { } diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.html new file mode 100644 index 0000000000000..6bad01787ce92 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.html @@ -0,0 +1,34 @@ + + + + Images + + + + + + + Still working, please wait{{ retries | ellipsis }} + + + + Cannot load images within {{ name }}. + + +
+ There are no images within {{ name }} pool. +
+ + + diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.scss b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.spec.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.spec.ts new file mode 100644 index 0000000000000..6a208af532691 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.spec.ts @@ -0,0 +1,39 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { AlertModule, TabsModule } from 'ngx-bootstrap'; + +import { ComponentsModule } from '../../../shared/components/components.module'; +import { SharedModule } from '../../../shared/shared.module'; +import { PoolDetailComponent } from './pool-detail.component'; + +describe('PoolDetailComponent', () => { + let component: PoolDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + SharedModule, + TabsModule.forRoot(), + AlertModule.forRoot(), + ComponentsModule, + RouterTestingModule, + HttpClientTestingModule + ], + declarations: [ PoolDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PoolDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.ts new file mode 100644 index 0000000000000..b93a98f4c4733 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/block/pool-detail/pool-detail.component.ts @@ -0,0 +1,95 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum'; +import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe'; +import { DimlessPipe } from '../../../shared/pipes/dimless.pipe'; +import { FormatterService } from '../../../shared/services/formatter.service'; +import { PoolService } from '../../../shared/services/pool.service'; + +@Component({ + selector: 'cd-pool-detail', + templateUrl: './pool-detail.component.html', + styleUrls: ['./pool-detail.component.scss'] +}) +export class PoolDetailComponent implements OnInit, OnDestroy { + + name: string; + images: any; + columns: any; + retries: number; + maxRetries = 5; + routeParamsSubscribe: any; + + constructor(private route: ActivatedRoute, + private poolService: PoolService, + dimlessBinaryPipe: DimlessBinaryPipe, + dimlessPipe: DimlessPipe) { + this.columns = [ + { + name: 'Name', + prop: 'name', + width: 100 + }, + { + name: 'Size', + prop: 'size', + width: 50, + cellClass: 'text-right', + pipe: dimlessBinaryPipe + }, + { + name: 'Objects', + prop: 'num_objs', + width: 50, + cellClass: 'text-right', + pipe: dimlessPipe + }, + { + name: 'Object size', + prop: 'obj_size', + width: 50, + cellClass: 'text-right', + pipe: dimlessBinaryPipe + }, + { + name: 'Features', + prop: 'features_name', + width: 150}, + { + name: 'Parent', + prop: 'parent', + width: 100 + } + ]; + } + + ngOnInit() { + this.routeParamsSubscribe = this.route.params.subscribe((params: { name: string }) => { + this.name = params.name; + this.images = null; + this.retries = 0; + this.loadImages(); + }); + } + + ngOnDestroy() { + this.routeParamsSubscribe.unsubscribe(); + } + + loadImages() { + this.poolService.rbdPoolImages(this.name).then((resp) => { + if (resp.status === ViewCacheStatus.ValueNone) { + setTimeout(() => { + this.retries++; + if (this.retries <= this.maxRetries) { + this.loadImages(); + } + }, 1000); + } else { + this.retries = 0; + this.images = resp.value; + } + }); + } +} diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts index 00f9548728cbb..2bdca2340d5ee 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/ceph/ceph.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { BlockModule } from './block/block.module'; import { ClusterModule } from './cluster/cluster.module'; import { DashboardModule } from './dashboard/dashboard.module'; @@ -8,7 +9,8 @@ import { DashboardModule } from './dashboard/dashboard.module'; imports: [ CommonModule, ClusterModule, - DashboardModule + DashboardModule, + BlockModule ], declarations: [] }) diff --git a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html index e93c304f21a43..2d3a261ea638d 100644 --- a/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html +++ b/src/pybind/mgr/dashboard_v2/frontend/src/app/core/navigation/navigation/navigation.component.html @@ -41,12 +41,6 @@ routerLink="/cephOsds">OSDs -
  • - RBDs - -
  • + +