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 { CCI, DF, VLC, VNFPROFILE } 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): void => {
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)
215 .subscribe((nsData: NSInstanceDetails): void => {
216 this.nsData = nsData;
218 nsInstanceID: nsData._id,
220 nsOperationalStatus: nsData['operational-status'],
221 nsConfigStatus: nsData['config-status'],
222 nsDetailedStatus: nsData['detailed-status'],
223 nsResourceOrchestrator: nsData['resource-orchestrator']
225 if (this.nsData['constituent-vnfr-ref'] !== undefined) {
226 this.generateVNFRCPNodes();
228 if (this.nsData.vld !== undefined) {
229 this.generateVLDNetworkNodes();
231 setTimeout((): void => {
233 this.generateVNFDCP();
234 this.generateVLDCP();
235 this.isLoadingResults = false;
236 this.createNode(this.nodes, this.links);
238 }, (error: ERRORDATA): void => {
239 this.isLoadingResults = false;
240 if (error.error.status === HttpStatus.NOT_FOUND || error.error.status === HttpStatus.UNAUTHORIZED) {
241 this.router.navigateByUrl('404', { skipLocationChange: true }).catch();
243 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): void => {
250 this.restService.getResource(environment.VNFINSTANCES_URL + '/' + vnfdrID).subscribe((vndfrDetail: NSD): void => {
252 id: vndfrDetail['vnfd-ref'] + ':' + vndfrDetail['member-vnf-index-ref'],
254 cp: vndfrDetail['connection-point'],
255 vdur: vndfrDetail.vdur,
256 nsID: vndfrDetail['nsr-id-ref'],
257 vnfdID: vndfrDetail['vnfd-id'],
258 vimID: vndfrDetail['vim-account-id'],
259 vndfrID: vndfrDetail.id,
260 ipAddress: vndfrDetail['ip-address'],
261 memberIndex: vndfrDetail['member-vnf-index-ref'],
262 vnfdRef: vndfrDetail['vnfd-ref'],
263 selectorId: 'nsInst-' + vndfrDetail.id
265 // Fetching all the connection point of VNF & Interface
266 vndfrDetail['connection-point'].forEach((cp: CONNECTIONPOINT): void => {
268 id: cp.name + ':' + vndfrDetail['member-vnf-index-ref'],
269 vndfCPRef: vndfrDetail['vnfd-ref'] + ':' + vndfrDetail['member-vnf-index-ref'],
274 }, (error: ERRORDATA): void => {
275 this.restService.handleError(error, 'get');
279 /** Fetching all the VLD/Network Information @private */
280 private generateVLDNetworkNodes(): void {
281 this.nsdData = this.nsData.nsd;
282 this.nsdData['virtual-link-desc'].forEach((ref: NSDVLD): void => {
288 vnfdCP: this.nsdData.df,
289 vimNetworkName: ref['vim-network-name'],
290 selectorId: 'nsInst-' + ref.id
294 /** Pushing connection points of path/links nodes @private */
295 private pushAllNodes(): void {
296 this.nodes.forEach((nodeList: NSINSTANCENODES): void => {
297 if (nodeList.nodeTypeRef === 'vnfd') {
298 this.vnfdNodes.push(nodeList);
299 } else if (nodeList.nodeTypeRef === 'vld') {
300 this.vldNodes.push(nodeList);
301 } else if (nodeList.nodeTypeRef === 'cp') {
302 this.cpNodes.push(nodeList);
306 /** Get CP position based on vndf @private */
307 private generateVNFDCP(): void {
308 this.vnfdNodes.forEach((list: NSINSTANCENODES): void => {
309 const vndfPos: number = this.nodes.map((e: NSINSTANCENODES): string => { return e.id; }).indexOf(list.id);
311 this.nodes.forEach((res: NSINSTANCENODES): void => {
312 if (res.nodeTypeRef === 'cp' && res.vndfCPRef === list.id) {
313 this.links.push({ source: this.nodes[vndfPos], target: this.nodes[this.cpCount] });
319 /** Get CP position based on vld @private */
320 private generateVLDCP(): void {
321 let vldPos: number = 0;
322 this.vldNodes.forEach((list: NSINSTANCENODES): void => {
323 if (!isNullOrUndefined(list.vnfdCP)) {
324 list.vnfdCP.forEach((cpRef: DF): void => {
325 cpRef['vnf-profile'].forEach((vnfProfile: VNFPROFILE): void => {
326 vnfProfile['virtual-link-connectivity'].forEach((resultVLC: VLC, index: number): void => {
327 resultVLC['constituent-cpd-id'].forEach((resultCCI: CCI): void => {
329 this.nodes.forEach((res: NSINSTANCENODES): void => {
330 if (res.nodeTypeRef === 'cp' &&
331 res.id === resultCCI['constituent-cpd-id'] + ':' + resultCCI['constituent-base-element-id']) {
332 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', (): void => { this.keyDown(); });
349 d3.select(window).on('keyup', (): void => { 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): string => 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', (): void => { 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);
369 /** Update force layout (called automatically each iteration) @private */
370 private tick(): void {
371 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
372 // draw directed edges with proper padding from node centers
373 this.path.attr('class', 'link').attr('d', (d: Tick): string => {
374 const deltaX: number = d.target.x - d.source.x;
375 const deltaY: number = d.target.y - d.source.y;
376 const dist: number = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
377 const normX: number = deltaX / dist;
378 const normY: number = deltaY / dist;
379 const sourcePadding: number = d.left ? graphContainerAttr.sourcePaddingYes : graphContainerAttr.sourcePaddingNo;
380 const targetPadding: number = d.right ? graphContainerAttr.targetPaddingYes : graphContainerAttr.targetPaddingNo;
381 const sourceX: number = d.source.x + (sourcePadding * normX);
382 const sourceY: number = d.source.y + (sourcePadding * normY);
383 const targetX: number = d.target.x - (targetPadding * normX);
384 const targetY: number = d.target.y - (targetPadding * normY);
385 return `M${sourceX},${sourceY}L${targetX},${targetY}`;
387 this.network.attr('transform', (t: TickPath): string => `translate(${t.x},${t.y})`);
388 this.square.attr('transform', (t: TickPath): string => `translate(${t.x},${t.y})`);
389 this.circle.attr('transform', (t: TickPath): string => `translate(${t.x},${t.y})`);
391 /** Update graph (called when needed) @private */
392 private restart(nodes: NSINSTANCENODES[], links: {}[]): void {
393 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
394 this.path = this.path.data(links);
395 const vnfdNodes: {}[] = []; const vldNodes: {}[] = []; const cpNodes: {}[] = []; // NB: Nodes are known by id, not by index!
396 nodes.forEach((nodeList: NSINSTANCENODES): void => {
397 if (nodeList.nodeTypeRef === 'vnfd') { vnfdNodes.push(nodeList); }
398 else if (nodeList.nodeTypeRef === 'vld') { vldNodes.push(nodeList); }
399 else if (nodeList.nodeTypeRef === 'cp') { cpNodes.push(nodeList); }
401 this.square = this.square.data(vnfdNodes, (d: COMPOSERNODES): string => d.id);
402 this.network = this.network.data(vldNodes, (d: COMPOSERNODES): string => d.id);
403 this.circle = this.circle.data(cpNodes, (d: COMPOSERNODES): string => d.id);
404 this.resetAndCreateNodes();
405 this.force.nodes(nodes).force('link').links(links); //Set the graph in motion
406 this.force.alphaTarget(graphContainerAttr.alphaTarget).restart();
408 /** Rest and create nodes @private */
409 private resetAndCreateNodes(): void {
410 this.path.exit().remove();
411 this.square.exit().remove();
412 this.network.exit().remove();
413 this.circle.exit().remove();
414 // tslint:disable-next-line:no-any
415 const gPath: any = this.path.enter().append('svg:path').attr('class', 'link');
419 this.square = this.gSquare.merge(this.square);
420 this.network = this.gNetwork.merge(this.network);
421 this.path = gPath.merge(this.path);
422 this.circle = this.gCircle.merge(this.circle);
424 /** Events handles when Shift Click to perform create cp @private */
425 // tslint:disable-next-line: no-any
426 private singleClick(nodeSelected: any, d: COMPOSERNODES): void {
427 this.selectNodeExclusive(nodeSelected, d);
429 /** Selected nodes @private */
430 // tslint:disable-next-line: no-any
431 private selectNodeExclusive(nodeSelected: any, d: COMPOSERNODES): void {
432 const alreadyIsActive: boolean = nodeSelected.select('#' + d.selectorId).classed(this.activeClass);
433 this.deselectAllNodes();
434 nodeSelected.select('#' + d.selectorId).classed(this.activeClass, !alreadyIsActive);
435 if (d.nodeTypeRef === 'vld' && !alreadyIsActive) {
440 shortName: d.shortName,
441 vimNetworkName: d.vimNetworkName
443 this.showRightSideInfo(false, true, false);
444 } else if (d.nodeTypeRef === 'vnfd' && !alreadyIsActive) {
453 memberIndex: d.memberIndex
455 this.showRightSideInfo(false, false, true);
457 this.showRightSideInfo(true, false, false);
460 /** Setting all the square/vnf attributes of nodes @private */
461 private getgSquare(): void {
462 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
463 this.gSquare = this.square.enter().append('svg:g');
464 this.gSquare.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
465 this.gSquare.append('svg:image')
467 .attr('x', graphContainerAttr.imageX)
468 .attr('y', graphContainerAttr.imageY)
469 .call(this.onDragDrop())
470 .attr('id', (d: COMPOSERNODES): string => { return d.selectorId; })
471 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
472 .attr('xlink:href', 'assets/images/VNFD.svg')
473 .on('click', (d: COMPOSERNODES): void => { this.singleClick(this.gSquare, d); this.onNodeClickToggleSidebar(); });
474 this.gSquare.append('svg:text')
475 .attr('class', 'node_text')
476 .attr('y', graphContainerAttr.textY)
477 .text((d: COMPOSERNODES): string => d.id);
479 /** Settings all the network attributes of nodes @private */
480 private getgNetwork(): void {
481 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
482 this.gNetwork = this.network.enter().append('svg:g');
483 this.gNetwork.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
484 this.gNetwork.append('svg:image')
486 .attr('x', graphContainerAttr.imageX)
487 .attr('y', graphContainerAttr.imageY)
488 .call(this.onDragDrop())
489 .attr('id', (d: COMPOSERNODES): string => { return d.selectorId; })
490 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
491 .attr('xlink:href', 'assets/images/VL.svg')
492 .on('click', (d: COMPOSERNODES): void => { this.singleClick(this.gNetwork, d); this.onNodeClickToggleSidebar(); });
493 this.gNetwork.append('svg:text')
494 .attr('class', 'node_text')
495 .attr('y', graphContainerAttr.textY)
496 .text((d: COMPOSERNODES): string => d.name);
498 /** Settings all the connection point attributes of nodes @private */
499 private getgCircle(): void {
500 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
501 this.gCircle = this.circle.enter().append('svg:g');
502 this.gCircle.append('svg:circle').attr('r', graphContainerAttr.radius).style('fill', '#eeeeee');
503 this.gCircle.append('svg:image')
505 .attr('x', graphContainerAttr.imageX)
506 .attr('y', graphContainerAttr.imageY)
507 .call(this.onDragDrop())
508 .attr('class', 'node').attr('width', graphContainerAttr.nodeWidth).attr('height', graphContainerAttr.nodeHeight)
509 .attr('xlink:href', 'assets/images/CP.svg');
510 this.gCircle.append('svg:text')
511 .attr('class', 'node_text')
512 .attr('y', graphContainerAttr.textY)
513 .text((d: COMPOSERNODES): string => d.name);
515 /** drag event @private */
516 // tslint:disable-next-line: no-any
517 private onDragDrop(): any {
518 return d3.drag().filter(this.dragFilter)
519 .on('start', this.dragstarted)
520 .on('drag', this.dragged)
521 .on('end', this.dragended);
523 /** Key press event @private */
524 private keyDown(): void {
525 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
526 if (this.lastKeyDown !== -1) { return; }
527 this.lastKeyDown = d3.event.keyCode;
528 if (d3.event.keyCode === graphContainerAttr.shiftKeyCode) {
529 this.gSquare.call(d3.drag()
530 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
532 this.gNetwork.call(d3.drag()
533 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
535 this.gCircle.call(d3.drag()
536 .on('start', this.dragstarted).on('drag', this.dragged).on('end', this.dragended)
538 this.svg.classed('ctrl', true);
541 /** Key realse event @private */
542 private keyUp(): void {
543 const graphContainerAttr: GRAPHDETAILS = this.getGraphContainerAttr();
544 this.lastKeyDown = -1;
545 if (d3.event.keyCode === graphContainerAttr.shiftKeyCode) {
546 this.gSquare.on('.drag', null);
547 this.gNetwork.on('.drag', null);
548 this.gCircle.on('.drag', null);
549 this.svg.classed('ctrl', false);
552 /** Events handles when to drag using filter for the keys @private */
553 private dragFilter(): boolean {
554 return d3.event.ctrlKey && !d3.event.button;
556 /** Events handles when dragstarted @private */
557 private dragstarted(d: COMPOSERNODES): void {
561 /** Events handles when dragged @private */
562 private dragged(d: COMPOSERNODES): void {
563 d.fx = d.x = d3.event.x;
564 d.fy = d.y = d3.event.y;
566 /** Events handles when dragended @private */
567 private dragended(d: COMPOSERNODES): void {
568 if (this.forceSimulationActive) {
574 this.forceSimulationActive = false;
577 /** Events handles when node single click @private */
578 private onNodeClickToggleSidebar(): void {
579 this.sideBarOpened = true;