2 Copyright 2020 TATA ELXSI
4 Licensed under the Apache License, Version 2.0 (the 'License');
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
16 Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
19 * @file NS Topology Component
21 /* tslint:disable:no-increment-decrement */
22 import { Component, ElementRef, Injector, ViewChild, ViewEncapsulation } from '@angular/core';
23 import { ActivatedRoute } from '@angular/router';
24 import { Router } from '@angular/router';
25 import { TranslateService } from '@ngx-translate/core';
26 import { ERRORDATA } from 'CommonModel';
27 import * as d3 from 'd3';
28 import { environment } from 'environment';
29 import * as HttpStatus from 'http-status-codes';
30 import { VNFDCONNECTIONPOINTREF } from 'NSDModel';
31 import { COMPOSERNODES, CONNECTIONPOINT, NSD, NSDVLD, NSINFO, NSInstanceDetails, NSINSTANCENODES, VLINFO, VNFRINFO } from 'NSInstanceModel';
32 import { GRAPHDETAILS, Tick, TickPath } from 'NSTopologyModel';
33 import { RestService } from 'src/services/RestService';
34 import { isNullOrUndefined } from 'util';
38 * @Component takes NSTopologyComponent.html as template url
41 selector: 'app-ns-topology',
42 templateUrl: './NSTopologyComponent.html',
43 styleUrls: ['./NSTopologyComponent.scss'],
44 encapsulation: ViewEncapsulation.None
46 /** Exporting a class @exports NSTopologyComponent */
47 export class NSTopologyComponent {
48 /** Injector to invoke other services @public */
49 public injector: Injector;
50 /** View child contains graphContainer ref @public */
51 @ViewChild('graphContainer', { static: true }) public graphContainer: ElementRef;
52 /** Holds the basic information of NS @public */
53 public nsInfo: NSINFO;
54 /** Contains tranlsate instance @private */
55 public translateService: TranslateService;
56 /** Add the activeclass for the selected @public */
57 public activeClass: string = 'active';
58 /** Add the fixed class for the freeze @public */
59 public fixedClass: string = 'fixed';
60 /** Check the loading results @public */
61 public isLoadingResults: boolean = true;
62 /** Give the message for the loading @public */
63 public message: string = 'PLEASEWAIT';
64 /** Assign the forcesimulation active @public */
65 public forceSimulationActive: boolean = false;
66 /** Assign pinned class for the button when freezed @public */
67 public classApplied: boolean = false;
68 /** Contains sidebar open status @public */
69 public sideBarOpened: boolean = true;
70 /** Need to show the NS Details @public */
71 public isShowNSDetails: boolean = true;
72 /** Need to show the VL Details @public */
73 public isShowVLetails: boolean = false;
74 /** Need to show the VNFR Details @public */
75 public isShowVNFRDetails: boolean = false;
76 /** Show right side info of Virtual Link @public */
77 public virtualLink: VLINFO;
78 /** Show right side info of Virtual Link @public */
79 public vnfr: VNFRINFO;
81 /** Contains lastkeypressed instance @private */
82 private lastKeyDown: number = -1;
83 /** Instance of the rest service @private */
84 private restService: RestService;
85 /** Holds the instance of AuthService class of type AuthService @private */
86 private activatedRoute: ActivatedRoute;
87 /** Holds the NS Id @private */
88 private nsIdentifier: string;
89 /** Contains SVG attributes @private */
90 // tslint:disable-next-line:no-any
92 /** Contains forced node animations @private */
93 // tslint:disable-next-line:no-any
95 /** Contains path information of the node */
96 // tslint:disable-next-line:no-any
98 /** Contains node network @private */
99 // tslint:disable-next-line:no-any
100 private network: any;
101 /** Contains node square @private */
102 // tslint:disable-next-line:no-any
104 /** Contains node circle @private */
105 // tslint:disable-next-line:no-any
107 /** Contains the NS information @private */
108 private nsData: NSInstanceDetails;
109 /** Contains NDS information of a descriptors */
110 private nsdData: NSD;
111 /** Contains node information @private */
112 private nodes: NSINSTANCENODES[] = [];
113 /** Contains links information @private */
114 private links: {}[] = [];
115 /** holds cp count/iteration @private */
116 private cpCount: number;
117 /** VNFD nodes @private */
118 private vnfdNodes: {}[] = [];
119 /** VLD nodes @private */
120 private vldNodes: {}[] = [];
121 /** Connection CP nodes @private */
122 private cpNodes: {}[] = [];
123 /** Set timeout @private */
124 private TIMEOUT: number = 2000;
125 /** Rendered nodes represent vnf @private */
126 // tslint:disable-next-line:no-any
127 private gSquare: any;
128 /** Rendered nodes represent network @private */
129 // tslint:disable-next-line:no-any
130 private gNetwork: any;
131 /** Rendered nodes represent network @private */
132 // tslint:disable-next-line:no-any
133 private gCircle: any;
134 /** Service holds the router information @private */
135 private router: Router;
137 constructor(injector: Injector) {
138 this.injector = injector;
139 this.restService = this.injector.get(RestService);
140 this.activatedRoute = this.injector.get(ActivatedRoute);
141 this.translateService = this.injector.get(TranslateService);
142 this.router = this.injector.get(Router);
146 * Lifecyle Hooks the trigger before component is instantiate @public
148 public ngOnInit(): void {
149 // tslint:disable-next-line:no-backbone-get-set-outside-model
150 this.nsIdentifier = this.activatedRoute.snapshot.paramMap.get('id');
153 /** Event to freeze the animation @public */
154 public onFreeze(): void {
155 this.classApplied = !this.classApplied;
156 const alreadyFixedIsActive: boolean = d3.select('svg#graphContainer').classed(this.fixedClass);
157 d3.select('svg#graphContainer').classed(this.fixedClass, !alreadyFixedIsActive);
158 if (alreadyFixedIsActive) {
161 this.forceSimulationActive = alreadyFixedIsActive;
162 this.nodes.forEach((d: COMPOSERNODES) => {
163 d.fx = (alreadyFixedIsActive) ? null : d.x;
164 d.fy = (alreadyFixedIsActive) ? null : d.y;
166 if (alreadyFixedIsActive) {
167 this.force.restart();
170 /** Events handles when dragended @public */
171 public toggleSidebar(): void {
172 this.sideBarOpened = !this.sideBarOpened;
173 this.deselectAllNodes();
174 this.showRightSideInfo(true, false, false);
176 /** Get the default Configuration of containers @private */
177 private getGraphContainerAttr(): GRAPHDETAILS {
190 sourcePaddingYes: 17,
200 /** Show the right-side information @private */
201 private showRightSideInfo(nsDetails: boolean, vlDetails: boolean, vnfrDeails: boolean): void {
202 this.isShowNSDetails = nsDetails;
203 this.isShowVLetails = vlDetails;
204 this.isShowVNFRDetails = vnfrDeails;
206 /** De-select all the selected nodes @private */
207 private deselectAllNodes(): void {
208 this.square.select('image').classed(this.activeClass, false);
209 this.network.select('image').classed(this.activeClass, false);
210 this.circle.select('image').classed(this.activeClass, false);
212 /** Prepare all the information for node creation @private */
213 private generateData(): void {
214 this.restService.getResource(environment.NSINSTANCESCONTENT_URL + '/' + this.nsIdentifier).subscribe((nsData: NSInstanceDetails) => {
215 this.nsData = nsData;
217 nsInstanceID: nsData._id,
219 nsOperationalStatus: nsData['operational-status'],
220 nsConfigStatus: nsData['config-status'],
221 nsDetailedStatus: nsData['detailed-status'],
222 nsResourceOrchestrator: nsData['resource-orchestrator']
224 if (this.nsData['constituent-vnfr-ref'] !== undefined) {
225 this.generateVNFRCPNodes();
227 if (this.nsData.vld !== undefined) {
228 this.generateVLDNetworkNodes();
232 this.generateVNFDCP();
233 this.generateVLDCP();
234 this.isLoadingResults = false;
235 this.createNode(this.nodes, this.links);
237 }, (error: ERRORDATA) => {
238 this.isLoadingResults = false;
239 if (error.error.status === HttpStatus.NOT_FOUND || error.error.status === HttpStatus.UNAUTHORIZED) {
240 this.router.navigateByUrl('404', { skipLocationChange: true }).catch();
242 this.restService.handleError(error, 'get');
247 /** Fetching all the VNFR Information @private */
248 private generateVNFRCPNodes(): void {
249 this.nsData['constituent-vnfr-ref'].forEach((vnfdrID: string) => {
250 this.restService.getResource(environment.VNFINSTANCES_URL + '/' + vnfdrID).subscribe((vndfrDetail: NSD) => {
252 id: vndfrDetail['vnfd-ref'] + ':' + vndfrDetail['member-vnf-index-ref'],
254 cp: vndfrDetail['connection-point'],
255 vdur: vndfrDetail.vdur,
256 vld: vndfrDetail.vld,
257 nsID: vndfrDetail['nsr-id-ref'],
258 vnfdID: vndfrDetail['vnfd-id'],
259 vimID: vndfrDetail['vim-account-id'],
260 vndfrID: vndfrDetail.id,
261 ipAddress: vndfrDetail['ip-address'],
262 memberIndex: vndfrDetail['member-vnf-index-ref'],
263 vnfdRef: vndfrDetail['vnfd-ref'],
264 selectorId: 'nsInst-' + vndfrDetail.id
266 // Fetching all the connection point of VNF & Interface
267 vndfrDetail['connection-point'].forEach((cp: CONNECTIONPOINT) => {
269 id: cp.name + ':' + vndfrDetail['member-vnf-index-ref'],
270 vndfCPRef: vndfrDetail['vnfd-ref'] + ':' + vndfrDetail['member-vnf-index-ref'],
275 }, (error: ERRORDATA) => {
276 this.restService.handleError(error, 'get');
281 /** Fetching all the VLD/Network Information @private */
282 private generateVLDNetworkNodes(): void {
283 this.nsdData = this.nsData.nsd;
284 this.nsdData.vld.forEach((ref: NSDVLD) => {
290 vnfdCP: ref['vnfd-connection-point-ref'],
291 vimNetworkName: ref['vim-network-name'],
292 shortName: ref['short-name'],
293 selectorId: 'nsInst-' + ref.id
298 /** Pushing connection points of path/links nodes @private */
299 private pushAllNodes(): void {
300 this.nodes.forEach((nodeList: NSINSTANCENODES) => {
301 if (nodeList.nodeTypeRef === 'vnfd') {
302 this.vnfdNodes.push(nodeList);
303 } else if (nodeList.nodeTypeRef === 'vld') {
304 this.vldNodes.push(nodeList);
305 } else if (nodeList.nodeTypeRef === 'cp') {
306 this.cpNodes.push(nodeList);
311 /** Get CP position based on vndf @private */
312 private generateVNFDCP(): void {
313 this.vnfdNodes.forEach((list: NSINSTANCENODES) => {
314 const vndfPos: number = this.nodes.map((e: NSINSTANCENODES) => { return e.id; }).indexOf(list.id);
316 this.nodes.forEach((res: NSINSTANCENODES) => {
317 if (res.nodeTypeRef === 'cp' && res.vndfCPRef === list.id) {
318 this.links.push({ source: this.nodes[vndfPos], target: this.nodes[this.cpCount] });
325 /** Get CP position based on vld @private */
326 private generateVLDCP(): void {
327 let vldPos: number = 0;
328 this.vldNodes.forEach((list: NSINSTANCENODES) => {
329 if (!isNullOrUndefined(list.vnfdCP)) {
330 list.vnfdCP.forEach((cpRef: VNFDCONNECTIONPOINTREF) => {
332 this.nodes.forEach((res: NSINSTANCENODES) => {
333 if (res.nodeTypeRef === 'cp' && res.id === cpRef['vnfd-connection-point-ref'] + ':' + cpRef['member-vnf-index-ref']) {
334 this.links.push({ source: this.nodes[vldPos], target: this.nodes[this.cpCount] });
344 /** Node is created and render at D3 region @private */
345 private createNode(nodes: NSINSTANCENODES[], links: {}[]): void {
346 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
347 d3.selectAll('svg#graphContainer > *').remove();
348 d3.select(window).on('keydown', () => { this.keyDown(); });
349 d3.select(window).on('keyup', () => { this.keyUp(); });
350 this.svg = d3.select('#graphContainer')
351 .attr('oncontextmenu', 'return false;')
352 .attr('width', graphContainerAttr.width)
353 .attr('height', graphContainerAttr.height);
354 this.force = d3.forceSimulation()
355 .force('charge', d3.forceManyBody().strength(graphContainerAttr.strength))
356 .force('link', d3.forceLink().id((d: TickPath) => d.id).distance(graphContainerAttr.distance))
357 .force('center', d3.forceCenter(graphContainerAttr.width / graphContainerAttr.forcex,
358 graphContainerAttr.height / graphContainerAttr.forcey))
359 .force('x', d3.forceX(graphContainerAttr.width / graphContainerAttr.forcex))
360 .force('y', d3.forceY(graphContainerAttr.height / graphContainerAttr.forcey))
361 .on('tick', () => { this.tick(); });
362 // handles to link and node element groups
363 this.path = this.svg.append('svg:g').selectAll('path');
364 this.network = this.svg.append('svg:g').selectAll('network');
365 this.square = this.svg.append('svg:g').selectAll('rect');
366 this.circle = this.svg.append('svg:g').selectAll('circle');
367 this.restart(nodes, links);
370 /** Update force layout (called automatically each iteration) @private */
371 private tick(): void {
372 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
373 // draw directed edges with proper padding from node centers
374 this.path.attr('class', 'link').attr('d', (d: Tick) => {
375 const deltaX: number = d.target.x - d.source.x;
376 const deltaY: number = d.target.y - d.source.y;
377 const dist: number = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
378 const normX: number = deltaX / dist;
379 const normY: number = deltaY / dist;
380 const sourcePadding: number = d.left ? graphContainerAttr.sourcePaddingYes : graphContainerAttr.sourcePaddingNo;
381 const targetPadding: number = d.right ? graphContainerAttr.targetPaddingYes : graphContainerAttr.targetPaddingNo;
382 const sourceX: number = d.source.x + (sourcePadding * normX);
383 const sourceY: number = d.source.y + (sourcePadding * normY);
384 const targetX: number = d.target.x - (targetPadding * normX);
385 const targetY: number = d.target.y - (targetPadding * normY);
386 return `M${sourceX},${sourceY}L${targetX},${targetY}`;
388 this.network.attr('transform', (t: TickPath) => `translate(${t.x},${t.y})`);
389 this.square.attr('transform', (t: TickPath) => `translate(${t.x},${t.y})`);
390 this.circle.attr('transform', (t: TickPath) => `translate(${t.x},${t.y})`);
393 /** Update graph (called when needed) @private */
394 private restart(nodes: NSINSTANCENODES[], links: {}[]): void {
395 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
396 this.path = this.path.data(links);
397 const vnfdNodes: {}[] = []; const vldNodes: {}[] = []; const cpNodes: {}[] = []; // NB: Nodes are known by id, not by index!
398 nodes.forEach((nodeList: NSINSTANCENODES) => {
399 if (nodeList.nodeTypeRef === 'vnfd') { vnfdNodes.push(nodeList); }
400 else if (nodeList.nodeTypeRef === 'vld') { vldNodes.push(nodeList); }
401 else if (nodeList.nodeTypeRef === 'cp') { cpNodes.push(nodeList); }
403 this.square = this.square.data(vnfdNodes, (d: { id: number }) => d.id);
404 this.network = this.network.data(vldNodes, (d: { id: number }) => d.id);
405 this.circle = this.circle.data(cpNodes, (d: { id: number }) => d.id);
406 this.resetAndCreateNodes();
407 this.force.nodes(nodes).force('link').links(links); //Set the graph in motion
408 this.force.alphaTarget(graphContainerAttr.alphaTarget).restart();
411 /** Rest and create nodes @private */
412 private resetAndCreateNodes(): void {
413 this.path.exit().remove();
414 this.square.exit().remove();
415 this.network.exit().remove();
416 this.circle.exit().remove();
417 // tslint:disable-next-line:no-any
418 const gPath: any = this.path.enter().append('svg:path').attr('class', 'link');
422 this.square = this.gSquare.merge(this.square);
423 this.network = this.gNetwork.merge(this.network);
424 this.path = gPath.merge(this.path);
425 this.circle = this.gCircle.merge(this.circle);
428 /** Events handles when Shift Click to perform create cp @private */
429 // tslint:disable-next-line: no-any
430 private singleClick(nodeSelected: any, d: COMPOSERNODES): void {
431 this.selectNodeExclusive(nodeSelected, d);
433 /** Selected nodes @private */
434 // tslint:disable-next-line: no-any
435 private selectNodeExclusive(nodeSelected: any, d: COMPOSERNODES): void {
436 const alreadyIsActive: boolean = nodeSelected.select('#' + d.selectorId).classed(this.activeClass);
437 this.deselectAllNodes();
438 nodeSelected.select('#' + d.selectorId).classed(this.activeClass, !alreadyIsActive);
439 if (d.nodeTypeRef === 'vld' && !alreadyIsActive) {
444 shortName: d.shortName,
445 vimNetworkName: d.vimNetworkName
447 this.showRightSideInfo(false, true, false);
448 } else if (d.nodeTypeRef === 'vnfd' && !alreadyIsActive) {
457 memberIndex: d.memberIndex
459 this.showRightSideInfo(false, false, true);
461 this.showRightSideInfo(true, false, false);
464 /** Setting all the square/vnf attributes of nodes @private */
465 private getgSquare(): void {
466 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
467 this.gSquare = this.square.enter().append('svg:g');
468 this.gSquare.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
469 this.gSquare.append('svg:image')
471 .attr('x', graphContainerAttr.imageX)
472 .attr('y', graphContainerAttr.imageY)
473 .attr('id', (d: COMPOSERNODES) => { return d.selectorId; })
474 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
475 .attr('xlink:href', 'assets/images/VNFD.svg')
476 .on('click', (d: COMPOSERNODES) => { this.singleClick(this.gSquare, d); this.onNodeClickToggleSidebar(); });
477 this.gSquare.append('svg:text')
478 .attr('class', 'node_text')
479 .attr('y', graphContainerAttr.textY)
480 .text((d: COMPOSERNODES) => d.id);
483 /** Settings all the network attributes of nodes @private */
484 private getgNetwork(): void {
485 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
486 this.gNetwork = this.network.enter().append('svg:g');
487 this.gNetwork.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
488 this.gNetwork.append('svg:image')
490 .attr('x', graphContainerAttr.imageX)
491 .attr('y', graphContainerAttr.imageY)
492 .attr('id', (d: COMPOSERNODES) => { return d.selectorId; })
493 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
494 .attr('xlink:href', 'assets/images/VL.svg')
495 .on('click', (d: COMPOSERNODES) => { this.singleClick(this.gNetwork, d); this.onNodeClickToggleSidebar(); });
496 this.gNetwork.append('svg:text')
497 .attr('class', 'node_text')
498 .attr('y', graphContainerAttr.textY)
499 .text((d: COMPOSERNODES) => d.name);
502 /** Settings all the connection point attributes of nodes @private */
503 private getgCircle(): void {
504 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
505 this.gCircle = this.circle.enter().append('svg:g');
506 this.gCircle.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
507 this.gCircle.append('svg:image')
509 .attr('x', graphContainerAttr.imageX)
510 .attr('y', graphContainerAttr.imageY)
511 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
512 .attr('xlink:href', 'assets/images/CP.svg');
513 this.gCircle.append('svg:text')
514 .attr('class', 'node_text')
515 .attr('y', graphContainerAttr.textY)
516 .text((d: COMPOSERNODES) => d.name);
519 /** Key press event @private */
520 private keyDown(): void {
521 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
522 if (this.lastKeyDown !== -1) { return; }
523 this.lastKeyDown = d3.event.keyCode;
524 if (d3.event.keyCode === graphContainerAttr.shiftKeyCode) {
525 this.gSquare.call(d3.drag()
526 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
528 this.gNetwork.call(d3.drag()
529 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
531 this.gCircle.call(d3.drag()
532 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
534 this.svg.classed('ctrl', true);
537 /** Key realse event @private */
538 private keyUp(): void {
539 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
540 this.lastKeyDown = -1;
541 if (d3.event.keyCode === graphContainerAttr.shiftKeyCode) {
542 this.gSquare.on('.drag', null);
543 this.gNetwork.on('.drag', null);
544 this.gCircle.on('.drag', null);
545 this.svg.classed('ctrl', false);
548 /** Events handles when dragstarted @private */
549 private dragstarted(d: COMPOSERNODES): void {
553 /** Events handles when dragged @private */
554 private dragged(d: COMPOSERNODES): void {
555 d.fx = d.x = d3.event.x;
556 d.fy = d.y = d3.event.y;
558 /** Events handles when dragended @private */
559 private dragended(d: COMPOSERNODES): void {
560 if (this.forceSimulationActive) {
566 this.forceSimulationActive = false;
569 /** Events handles when node single click @private */
570 private onNodeClickToggleSidebar(): void {
571 this.sideBarOpened = true;