Skip to content

Commit

Permalink
mgr/dashboard: CRUSH map viewer/architectural viewer
Browse files Browse the repository at this point in the history
Signed-off-by: guodan1 <guodan1@lenovo.com>
  • Loading branch information
guodan1 committed Nov 3, 2018
1 parent a71a984 commit f3157db
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/pybind/mgr/dashboard/frontend/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"node_modules/fork-awesome/css/fork-awesome.css",
"node_modules/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css",
"node_modules/ngx-bootstrap/datepicker/bs-datepicker.css",
"src/styles.scss"
"src/styles.scss",
"node_modules/ng2-tree/styles.css"
],
"scripts": [
"node_modules/chart.js/dist/Chart.bundle.js"
Expand Down
5 changes: 5 additions & 0 deletions src/pybind/mgr/dashboard/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/pybind/mgr/dashboard/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"moment": "2.22.2",
"ng2-charts": "1.6.0",
"ng2-toastr": "zzakir/ng2-toastr#0eafd72",
"ng2-tree": "2.0.0-rc.11",
"ngx-bootstrap": "2.0.5",
"rxjs": "6.2.2",
"rxjs-compat": "6.2.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RbdImagesComponent } from './ceph/block/rbd-images/rbd-images.component
import { CephfsListComponent } from './ceph/cephfs/cephfs-list/cephfs-list.component';
import { ConfigurationFormComponent } from './ceph/cluster/configuration/configuration-form/configuration-form.component';
import { ConfigurationComponent } from './ceph/cluster/configuration/configuration.component';
import { CrushmapComponent } from './ceph/cluster/crushmap/crushmap.component';
import { HostsComponent } from './ceph/cluster/hosts/hosts.component';
import { MonitorComponent } from './ceph/cluster/monitor/monitor.component';
import { OsdListComponent } from './ceph/cluster/osd/osd-list/osd-list.component';
Expand Down Expand Up @@ -95,6 +96,12 @@ const routes: Routes = [
}
]
},
{
path: 'crush-map',
component: CrushmapComponent,
canActivate: [AuthGuardService],
data: { breadcrumbs: 'Cluster/CRUSH map' }
},
{
path: 'perf_counters/:type/:id',
component: PerformanceCounterComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

import { TreeModule } from 'ng2-tree';
import { AlertModule } from 'ngx-bootstrap/alert';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { ModalModule } from 'ngx-bootstrap/modal';
Expand All @@ -14,6 +15,7 @@ import { PerformanceCounterModule } from '../performance-counter/performance-cou
import { ConfigurationDetailsComponent } from './configuration/configuration-details/configuration-details.component';
import { ConfigurationFormComponent } from './configuration/configuration-form/configuration-form.component';
import { ConfigurationComponent } from './configuration/configuration.component';
import { CrushmapComponent } from './crushmap/crushmap.component';
import { HostDetailsComponent } from './hosts/host-details/host-details.component';
import { HostsComponent } from './hosts/hosts.component';
import { MonitorComponent } from './monitor/monitor.component';
Expand Down Expand Up @@ -42,7 +44,8 @@ import { OsdScrubModalComponent } from './osd/osd-scrub-modal/osd-scrub-modal.co
BsDropdownModule.forRoot(),
ModalModule.forRoot(),
AlertModule.forRoot(),
TooltipModule.forRoot()
TooltipModule.forRoot(),
TreeModule
],
declarations: [
HostsComponent,
Expand All @@ -56,7 +59,8 @@ import { OsdScrubModalComponent } from './osd/osd-scrub-modal/osd-scrub-modal.co
HostDetailsComponent,
ConfigurationDetailsComponent,
ConfigurationFormComponent,
OsdReweightModalComponent
OsdReweightModalComponent,
CrushmapComponent
]
})
export class ClusterModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div class="row">
<div class="col-sm-12 col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<span i18n>{{ panelTitle }}</span>
</h3>
</div>
<div class="panel-body">
<div class="col-sm-6 col-lg-6">
<tree [tree]="tree"
(nodeSelected)="onNodeSelected($event)"></tree>
</div>
<div class="col-sm-6 col-lg-6 metadata">
<cd-table-key-value *ngIf="metadata"
[data]="metadata">
</cd-table-key-value>
</div>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
::ng-deep tree-internal .tree li {
cursor: pointer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { of } from 'rxjs';

import { TreeModule } from 'ng2-tree';
import { TabsModule } from 'ngx-bootstrap/tabs';

import { configureTestBed } from '../../../../testing/unit-test-helper';
import { DashboardService } from '../../../shared/api/dashboard.service';
import { SharedModule } from '../../../shared/shared.module';
import { CrushmapComponent } from './crushmap.component';

describe('CrushmapComponent', () => {
let component: CrushmapComponent;
let fixture: ComponentFixture<CrushmapComponent>;
let debugElement: DebugElement;
configureTestBed({
imports: [HttpClientTestingModule, TreeModule, TabsModule.forRoot(), SharedModule],
declarations: [CrushmapComponent],
providers: [DashboardService]
});

beforeEach(() => {
fixture = TestBed.createComponent(CrushmapComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should display right title', () => {
fixture.detectChanges();
const span = debugElement.nativeElement.querySelector('span');
expect(span.textContent).toContain(component.panelTitle);
});

describe('test tree', () => {
let dashboardService: DashboardService;
const prepareGetHealth = (nodes: object[]) => {
spyOn(dashboardService, 'getHealth').and.returnValue(
of({ osd_map: { tree: { nodes: nodes } } })
);
fixture.detectChanges();
};

beforeEach(() => {
dashboardService = debugElement.injector.get(DashboardService);
});

it('should display "No nodes!" if ceph tree nodes is empty array', () => {
prepareGetHealth([]);
expect(dashboardService.getHealth).toHaveBeenCalled();
expect(component.tree.value).toEqual('No nodes!');
});

describe('ceph tree nodes has data', () => {
beforeEach(() => {
prepareGetHealth([
{ children: [-2], type: 'root', name: 'default', id: -1 },
{ children: [1, 0, 2], type: 'host', name: 'my-host', id: -2 },
{ status: 'up', type: 'osd', name: 'osd.0', id: 0 },
{ status: 'down', type: 'osd', name: 'osd.1', id: 1 },
{ status: 'up', type: 'osd', name: 'osd.2', id: 2 }
]);
});

it('has a tree structure deriving from root', () => {
expect(component.tree.value).toBe('default (root)');
});

it('has a tree structure with one host child with 3 children', () => {
expect(component.tree.children.length).toBe(1);
expect(component.tree.children[0].value).toBe('my-host (host)');
expect(component.tree.children[0].children.length).toBe(3);
});

it('has sorted all host children by id', () => {
expect(component.tree.children[0].children[0].value).toBe('osd.0 (osd)--up');
expect(component.tree.children[0].children[1].value).toBe('osd.1 (osd)--down');
expect(component.tree.children[0].children[2].value).toBe('osd.2 (osd)--up');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Component, OnInit } from '@angular/core';

import { NodeEvent, TreeModel } from 'ng2-tree';

import { DashboardService } from '../../../shared/api/dashboard.service';

@Component({
selector: 'cd-crushmap',
templateUrl: './crushmap.component.html',
styleUrls: ['./crushmap.component.scss']
})
export class CrushmapComponent implements OnInit {
panelTitle: string;
tree: TreeModel;
metadata: any;
metadataKeyMap: { [key: number]: number } = {};

constructor(private dashboardService: DashboardService) {
this.panelTitle = 'CRUSH map viewer';
}

ngOnInit() {
this.dashboardService.getHealth().subscribe((data: any) => {
this.tree = this._abstractTreeData(data);
});
}

_abstractTreeData(data: any): TreeModel {
const nodes = data.osd_map.tree.nodes || [];
const treeNodeMap: { [key: number]: any } = {};

if (0 === nodes.length) {
return {
value: 'No nodes!',
settings: { static: true }
};
}

const rootNodeId = nodes[0].id || null;
nodes.reverse().forEach((node) => {
treeNodeMap[node.id] = this.generateTreeLeaf(node, treeNodeMap);
});

return treeNodeMap[rootNodeId];
}

private generateTreeLeaf(node: any, treeNodeMap) {
const id = node.id;
this.metadataKeyMap[id] = node;
const settings = { static: true };

let value: string = node.name + ' (' + node.type + ')';
if (node.status) {
value += '--' + node.status;
}

const children: any[] = [];
if (node.children) {
node.children.sort().forEach((childId) => {
children.push(treeNodeMap[childId]);
});

return { value, settings, id, children };
}

return { value, settings, id };
}

onNodeSelected(e: NodeEvent) {
this.metadata = this.metadataKeyMap[e.node.id];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@
routerLink="/configuration">Configuration
</a>
</li>
<li routerLinkActive="active"
class="tc_submenuitem tc_submenuitem_crush"
*ngIf="permissions.hosts.read && permissions.osd.read">
<a i18n
class="dropdown-item"
routerLink="/crush-map">CRUSH map
</a>
</li>
</ul>
</li>

Expand Down

0 comments on commit f3157db

Please sign in to comment.