blob: 0edba0fe4639aaca2a5d39b826ffb9a531323f1e [file] [log] [blame]
Matteo Scandolo8cf33a32017-11-14 15:52:29 -08001/*
2 * Copyright 2017-present Open Networking Foundation
3
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
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
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.
15 */
16
17
18import * as _ from 'lodash';
19import {Graph} from 'graphlib';
20import {IXosModelStoreService} from '../../datasources/stores/model.store';
21import {IXosDebouncer} from '../../core/services/helpers/debounce.helper';
22import {Subscription} from 'rxjs/Subscription';
23import {BehaviorSubject} from 'rxjs/BehaviorSubject';
24import {Observable} from 'rxjs/Observable';
25import {IXosBaseModel, IXosSgLink, IXosSgNode} from '../interfaces';
Matteo Scandolo209fc8a2018-03-14 18:14:21 -070026import {IWSEvent, IWSEventService} from '../../datasources/websocket/global';
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080027
28
29export interface IXosGraphStore {
30 get(): Observable<Graph>;
31 nodesFromGraph(graph: Graph): IXosSgNode[];
32 linksFromGraph(graph: Graph): IXosSgLink[];
Matteo Scandolob8cdf552018-02-12 17:56:26 -080033 addServiceInstances(): Graph;
34 removeServiceInstances(): Graph;
35 addInstances(): Graph;
36 removeInstances(): Graph;
37 addNetworks(): Graph;
38 removeNetworks(): Graph;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080039}
40
41export class XosGraphStore implements IXosGraphStore {
42 static $inject = [
43 '$log',
44 'XosModelStore',
Matteo Scandolo209fc8a2018-03-14 18:14:21 -070045 'XosDebouncer',
46 'WebSocket'
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080047 ];
48
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080049 // graphs
50 private serviceGraph: any;
51 private ServiceGraphSubject: BehaviorSubject<any>;
52
53 // datastore
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080054 private InstanceSubscription: Subscription;
55 private NetworkSubscription: Subscription;
56 private PortSubscription: Subscription;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080057 private ServiceSubscription: Subscription;
58 private ServiceDependencySubscription: Subscription;
59 private ServiceInstanceSubscription: Subscription;
60 private ServiceInstanceLinkSubscription: Subscription;
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080061 private TenantWithContainerSubscription: Subscription;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080062
63 // debounced
64 private efficientNext = this.XosDebouncer.debounce(this.callNext, 500, this, false);
65
66 constructor (
67 private $log: ng.ILogService,
68 private XosModelStore: IXosModelStoreService,
Matteo Scandolo209fc8a2018-03-14 18:14:21 -070069 private XosDebouncer: IXosDebouncer,
70 private webSocket: IWSEventService,
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080071 ) {
72 this.$log.info('[XosGraphStore] Setup');
73
74 this.serviceGraph = new Graph();
75 this.ServiceGraphSubject = new BehaviorSubject(this.serviceGraph);
76
77 this.loadData();
Matteo Scandolo209fc8a2018-03-14 18:14:21 -070078
79 // handle model deletion
80 this.webSocket.list()
81 .filter((e: IWSEvent) => {
82
83 const model = this.getModelType(e.msg.object);
84
85 switch (model) {
86 case 'service':
87 case 'serviceinstance':
88 case 'instance':
89 case 'network':
90 return true;
91 case 'servicedependency':
92 case 'serviceinstanceLink':
93 // NOTE ServiceDependency are considered links, they are automatically removed by the graph library
94 return false;
95 default:
96 return false;
97 }
98 })
99 .subscribe((event: IWSEvent) => {
100 if (event.deleted) {
101 const nodeId = this.getNodeId(event.msg.object);
102 this.serviceGraph.removeNode(nodeId);
103 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
104 }
105 });
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800106 }
107
108 $onDestroy() {
109 this.ServiceSubscription.unsubscribe();
110 this.ServiceDependencySubscription.unsubscribe();
111 }
112
113 public nodesFromGraph(graph: Graph): IXosSgNode[] {
114 return _.map(graph.nodes(), (n: string) => {
115 const nodeData = graph.node(n);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800116
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800117 return {
118 id: n,
119 type: this.getModelType(nodeData),
120 data: nodeData
121 };
122 });
123 }
124
125 public linksFromGraph(graph: Graph): IXosSgLink[] {
126 const nodes = this.nodesFromGraph(graph);
127
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800128 return _.map(graph.edges(), l => {
129 const link = graph.edge(l);
130 const linkType = this.getModelType(link);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800131 let sourceId, targetId;
132
133 switch (linkType) {
134 case 'servicedependency':
135 sourceId = this.getServiceId(link.subscriber_service_id);
136 targetId = this.getServiceId(link.provider_service_id);
137 break;
138 case 'serviceinstancelink':
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800139 // NOTE ServiceInstanceLink can actually also connect to a service and a network
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800140 sourceId = this.getServiceInstanceId(link.subscriber_service_instance_id);
141 targetId = this.getServiceInstanceId(link.provider_service_instance_id);
142 break;
143 case 'ownership':
144 sourceId = this.getServiceId(link.service);
145 targetId = this.getServiceInstanceId(link.service_instance);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800146 break;
147 case 'instance_ownership':
148 sourceId = this.getServiceInstanceId(link.id);
149 targetId = this.getInstanceId(link.instance_id);
150 break;
151 case 'port':
152 sourceId = this.getInstanceId(link.instance_id);
153 targetId = this.getNetworkId(link.network_id);
154 break;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800155 }
156
157 // NOTE help while debugging
158 if (!sourceId || !targetId) {
159 this.$log.warn(`Link ${l.v}-${l.w} has missing source or target:`, l, link);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800160 // TODO return null and then filter out so that we don't break the rendering
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800161 }
162
163 return {
164 id: `${l.v}-${l.w}`,
165 type: this.getModelType(link),
166 source: _.findIndex(nodes, {id: sourceId}),
167 target: _.findIndex(nodes, {id: targetId}),
168 data: link
169 };
170 });
171 }
172
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800173 public addServiceInstances(): Graph {
174 this.loadServiceInstances();
175 this.loadServiceInstanceLinks();
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800176 return this.serviceGraph;
177 }
178
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800179 public removeServiceInstances(): Graph {
180 // NOTE remove subscriptions
181 this.ServiceInstanceSubscription.unsubscribe();
182 this.ServiceInstanceLinkSubscription.unsubscribe();
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800183
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800184 // remove nodes from the graph
185 this.removeElementsFromGraph('serviceinstance');
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800186 return this.serviceGraph;
187 }
188
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800189 public addInstances(): Graph {
190 this.loadInstances();
191 this.loadInstanceLinks();
192 return this.serviceGraph;
193 }
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800194
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800195 public removeInstances(): Graph {
196 this.InstanceSubscription.unsubscribe();
197 this.TenantWithContainerSubscription.unsubscribe();
198
199 this.removeElementsFromGraph('instance');
200
201 return this.serviceGraph;
202 }
203
204 public addNetworks(): Graph {
205 this.loadNetworks();
206 this.loadPorts();
207 return this.serviceGraph;
208 }
209
210 public removeNetworks(): Graph {
211 this.NetworkSubscription.unsubscribe();
212 this.PortSubscription.unsubscribe();
213 this.removeElementsFromGraph('network');
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800214 return this.serviceGraph;
215 }
216
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800217 public get(): Observable<Graph> {
218 return this.ServiceGraphSubject.asObservable();
219 }
220
221 private loadData() {
222 this.loadServices();
223 this.loadServiceDependencies();
224 }
225
226 // graph operations
227 private addNode(node: IXosBaseModel) {
228 const nodeId = this.getNodeId(node);
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800229
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800230 this.serviceGraph.setNode(nodeId, node);
231
232 const nodeType = this.getModelType(node);
233 if (nodeType === 'serviceinstance') {
234 // NOTE adding owner link
235 this.addOwnershipEdge({
236 service: node.owner_id,
237 service_instance: node.id,
238 type: 'ownership'
239 });
240 }
241 }
242
243 private addEdge(link: IXosBaseModel) {
244 const linkType = this.getModelType(link);
245 if (linkType === 'servicedependency') {
246 const sourceId = this.getServiceId(link.subscriber_service_id);
247 const targetId = this.getServiceId(link.provider_service_id);
248 this.serviceGraph.setEdge(sourceId, targetId, link);
249 }
250 if (linkType === 'serviceinstancelink') {
251 // NOTE serviceinstancelink can point also to services, networks, ...
252 const sourceId = this.getServiceInstanceId(link.provider_service_instance_id);
253 if (angular.isDefined(link.subscriber_service_instance_id)) {
254 const targetId = this.getServiceInstanceId(link.subscriber_service_instance_id);
255 this.serviceGraph.setEdge(sourceId, targetId, link);
256 }
257 }
258 }
259
260 private addOwnershipEdge(link: any) {
261 const sourceId = this.getServiceInstanceId(link.service_instance);
262 const targetId = this.getServiceId(link.service);
263 this.serviceGraph.setEdge(sourceId, targetId, link);
264 }
265
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800266 private addInstanceOwner(tenantWithContainer: any) {
267 // NOTE some TenantWithContainer don't have an instance
268 if (tenantWithContainer.instance_id) {
269 const sourceId = this.getServiceInstanceId(tenantWithContainer.id);
270 const targetId = this.getInstanceId(tenantWithContainer.instance_id);
271 this.serviceGraph.setEdge(sourceId, targetId, angular.merge(tenantWithContainer, {type: 'instance_ownership'}));
272 }
273 }
274
275 private addNetworkLink(port: any) {
276 // ports are connected to 1 Instance and 1 Network
277 const sourceId = this.getInstanceId(port.instance_id);
278 const targetId = this.getNetworkId(port.network_id);
279 this.serviceGraph.setEdge(sourceId, targetId, angular.merge(port, {type: 'port'}));
280 }
281
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800282 private removeElementsFromGraph(type: string) {
283 _.forEach(this.serviceGraph.nodes(), (n: string) => {
284 const node = this.serviceGraph.node(n);
285 const nodeType = this.getModelType(node);
286 if (nodeType === type) {
287 this.serviceGraph.removeNode(n);
288 }
289 });
290 // NOTE update the observable
291 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
292 }
293
294 // helpers
295 private getModelType(node: IXosBaseModel): string {
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800296 if (!node) {
297 this.$log.warn(`[XosGraphStore] Someone called getModelType with an empty model: ${node}`);
298 return 'missing-node';
299 }
300
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800301 if (node.type) {
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800302 // NOTE handling "ownership" links
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800303 return node.type;
304 }
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800305
306 if (node.class_names.indexOf('ServiceInstance,') > -1 ) {
307 return 'serviceinstance';
308 }
309
310 if (node.class_names.indexOf('Service,') > -1 ) {
311 return 'service';
312 }
313
314 if (node.class_names.indexOf('Instance,') > -1 ) {
315 return 'instance';
316 }
317
318 if (node.class_names.indexOf('Network,') > -1 ) {
319 return 'network';
320 }
321
322 this.$log.warn(`[XosGraphStore] Unkown model type: ${node.class_names}`);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800323 return node.class_names.split(',')[0].toLowerCase();
324 }
325
326 private getServiceId(id: number): string {
327 return `service~${id}`;
328 }
329
330 private getServiceInstanceId(id: number): string {
331 return `serviceinstance~${id}`;
332 }
333
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800334 private getInstanceId(id: number): string {
335 return `instance~${id}`;
336 }
337
338 private getNetworkId(id: number): string {
339 return `network~${id}`;
340 }
341
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800342 private getNodeId(node: IXosBaseModel): string {
343
344 const nodeType = this.getModelType(node);
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800345
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800346 switch (nodeType) {
347 case 'service':
348 return this.getServiceId(node.id);
349 case 'serviceinstance':
350 return this.getServiceInstanceId(node.id);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800351 case 'instance':
352 return this.getInstanceId(node.id);
353 case 'network':
354 return this.getNetworkId(node.id);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800355 }
356 }
357
358 // data loaders
359 private loadServices() {
360 this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
361 .subscribe(
362 (res) => {
363 if (res.length > 0) {
364 _.forEach(res, n => {
365 this.addNode(n);
366 });
367 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
368 }
369 },
370 (err) => {
371 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
372 }
373 );
374 }
375
376 private loadServiceDependencies() {
377 this.ServiceDependencySubscription = this.XosModelStore.query('ServiceDependency', '/core/servicedependencys')
378 .subscribe(
379 (res) => {
380 if (res.length > 0) {
381 _.forEach(res, l => {
382 this.addEdge(l);
383 });
384 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
385 }
386 },
387 (err) => {
388 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
389 }
390 );
391 }
392
393 private loadServiceInstances() {
394 this.ServiceInstanceSubscription = this.XosModelStore.query('ServiceInstance', '/core/serviceinstances')
395 .subscribe(
396 (res) => {
397 if (res.length > 0) {
398 _.forEach(res, n => {
399 this.addNode(n);
400 });
401 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
402 }
403 },
404 (err) => {
405 this.$log.error(`[XosServiceGraphStore] ServiceInstance Observable: `, err);
406 }
407 );
408 }
409
410 private loadServiceInstanceLinks() {
411 this.ServiceInstanceLinkSubscription = this.XosModelStore.query('ServiceInstanceLink', '/core/serviceinstancelinks')
412 .subscribe(
413 (res) => {
414 if (res.length > 0) {
415 _.forEach(res, l => {
416 this.addEdge(l);
417 });
418 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
419 }
420 },
421 (err) => {
422 this.$log.error(`[XosServiceGraphStore] ServiceInstanceLinks Observable: `, err);
423 }
424 );
425 }
426
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800427 private loadInstances() {
428 this.InstanceSubscription = this.XosModelStore.query('Instance', '/core/instances')
429 .subscribe(
430 (res) => {
431 if (res.length > 0) {
432 _.forEach(res, n => {
433 this.addNode(n);
434 });
435 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
436 }
437 },
438 (err) => {
439 this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
440 }
441 );
442 }
443
444 private loadInstanceLinks() {
445 this.TenantWithContainerSubscription = this.XosModelStore.query('TnantWithContainer', '/core/tenantwithcontainers')
446 .subscribe(
447 (res) => {
448 if (res.length > 0) {
449 _.forEach(res, n => {
450 this.addInstanceOwner(n);
451 });
452 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
453 }
454 },
455 (err) => {
456 this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
457 }
458 );
459 }
460
461 private loadNetworks() {
462 this.NetworkSubscription = this.XosModelStore.query('Network', '/core/networks')
463 .subscribe(
464 (res) => {
465 if (res.length > 0) {
466 _.forEach(res, n => {
467 this.addNode(n);
468 });
469 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
470 }
471 },
472 (err) => {
473 this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
474 }
475 );
476 }
477
478 private loadPorts() {
479 this.PortSubscription = this.XosModelStore.query('Port', '/core/ports')
480 .subscribe(
481 (res) => {
482 if (res.length > 0) {
483 _.forEach(res, n => {
484 this.addNetworkLink(n);
485 });
486 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
487 }
488 },
489 (err) => {
490 this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
491 }
492 );
493 }
494
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800495 private callNext(subject: BehaviorSubject<any>, data: any) {
496 subject.next(data);
497 }
498}