blob: 0dd6653659b8abe30be9592c6bd1f6828ad6e57d [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';
26
27
28export interface IXosGraphStore {
29 get(): Observable<Graph>;
30 nodesFromGraph(graph: Graph): IXosSgNode[];
31 linksFromGraph(graph: Graph): IXosSgLink[];
Matteo Scandolob8cdf552018-02-12 17:56:26 -080032 addServiceInstances(): Graph;
33 removeServiceInstances(): Graph;
34 addInstances(): Graph;
35 removeInstances(): Graph;
36 addNetworks(): Graph;
37 removeNetworks(): Graph;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080038}
39
40export class XosGraphStore implements IXosGraphStore {
41 static $inject = [
42 '$log',
43 'XosModelStore',
44 'XosDebouncer'
45 ];
46
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080047 // graphs
48 private serviceGraph: any;
49 private ServiceGraphSubject: BehaviorSubject<any>;
50
51 // datastore
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080052 private InstanceSubscription: Subscription;
53 private NetworkSubscription: Subscription;
54 private PortSubscription: Subscription;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080055 private ServiceSubscription: Subscription;
56 private ServiceDependencySubscription: Subscription;
57 private ServiceInstanceSubscription: Subscription;
58 private ServiceInstanceLinkSubscription: Subscription;
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080059 private TenantWithContainerSubscription: Subscription;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080060
61 // debounced
62 private efficientNext = this.XosDebouncer.debounce(this.callNext, 500, this, false);
63
64 constructor (
65 private $log: ng.ILogService,
66 private XosModelStore: IXosModelStoreService,
67 private XosDebouncer: IXosDebouncer
68 ) {
69 this.$log.info('[XosGraphStore] Setup');
70
71 this.serviceGraph = new Graph();
72 this.ServiceGraphSubject = new BehaviorSubject(this.serviceGraph);
73
74 this.loadData();
75 }
76
77 $onDestroy() {
78 this.ServiceSubscription.unsubscribe();
79 this.ServiceDependencySubscription.unsubscribe();
80 }
81
82 public nodesFromGraph(graph: Graph): IXosSgNode[] {
83 return _.map(graph.nodes(), (n: string) => {
84 const nodeData = graph.node(n);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080085
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080086 return {
87 id: n,
88 type: this.getModelType(nodeData),
89 data: nodeData
90 };
91 });
92 }
93
94 public linksFromGraph(graph: Graph): IXosSgLink[] {
95 const nodes = this.nodesFromGraph(graph);
96
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080097 return _.map(graph.edges(), l => {
98 const link = graph.edge(l);
99 const linkType = this.getModelType(link);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800100 let sourceId, targetId;
101
102 switch (linkType) {
103 case 'servicedependency':
104 sourceId = this.getServiceId(link.subscriber_service_id);
105 targetId = this.getServiceId(link.provider_service_id);
106 break;
107 case 'serviceinstancelink':
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800108 // NOTE ServiceInstanceLink can actually also connect to a service and a network
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800109 sourceId = this.getServiceInstanceId(link.subscriber_service_instance_id);
110 targetId = this.getServiceInstanceId(link.provider_service_instance_id);
111 break;
112 case 'ownership':
113 sourceId = this.getServiceId(link.service);
114 targetId = this.getServiceInstanceId(link.service_instance);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800115 break;
116 case 'instance_ownership':
117 sourceId = this.getServiceInstanceId(link.id);
118 targetId = this.getInstanceId(link.instance_id);
119 break;
120 case 'port':
121 sourceId = this.getInstanceId(link.instance_id);
122 targetId = this.getNetworkId(link.network_id);
123 break;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800124 }
125
126 // NOTE help while debugging
127 if (!sourceId || !targetId) {
128 this.$log.warn(`Link ${l.v}-${l.w} has missing source or target:`, l, link);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800129 // TODO return null and then filter out so that we don't break the rendering
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800130 }
131
132 return {
133 id: `${l.v}-${l.w}`,
134 type: this.getModelType(link),
135 source: _.findIndex(nodes, {id: sourceId}),
136 target: _.findIndex(nodes, {id: targetId}),
137 data: link
138 };
139 });
140 }
141
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800142 public addServiceInstances(): Graph {
143 this.loadServiceInstances();
144 this.loadServiceInstanceLinks();
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800145 return this.serviceGraph;
146 }
147
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800148 public removeServiceInstances(): Graph {
149 // NOTE remove subscriptions
150 this.ServiceInstanceSubscription.unsubscribe();
151 this.ServiceInstanceLinkSubscription.unsubscribe();
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800152
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800153 // remove nodes from the graph
154 this.removeElementsFromGraph('serviceinstance');
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800155 return this.serviceGraph;
156 }
157
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800158 public addInstances(): Graph {
159 this.loadInstances();
160 this.loadInstanceLinks();
161 return this.serviceGraph;
162 }
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800163
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800164 public removeInstances(): Graph {
165 this.InstanceSubscription.unsubscribe();
166 this.TenantWithContainerSubscription.unsubscribe();
167
168 this.removeElementsFromGraph('instance');
169
170 return this.serviceGraph;
171 }
172
173 public addNetworks(): Graph {
174 this.loadNetworks();
175 this.loadPorts();
176 return this.serviceGraph;
177 }
178
179 public removeNetworks(): Graph {
180 this.NetworkSubscription.unsubscribe();
181 this.PortSubscription.unsubscribe();
182 this.removeElementsFromGraph('network');
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800183 return this.serviceGraph;
184 }
185
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800186 public get(): Observable<Graph> {
187 return this.ServiceGraphSubject.asObservable();
188 }
189
190 private loadData() {
191 this.loadServices();
192 this.loadServiceDependencies();
193 }
194
195 // graph operations
196 private addNode(node: IXosBaseModel) {
197 const nodeId = this.getNodeId(node);
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800198
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800199 this.serviceGraph.setNode(nodeId, node);
200
201 const nodeType = this.getModelType(node);
202 if (nodeType === 'serviceinstance') {
203 // NOTE adding owner link
204 this.addOwnershipEdge({
205 service: node.owner_id,
206 service_instance: node.id,
207 type: 'ownership'
208 });
209 }
210 }
211
212 private addEdge(link: IXosBaseModel) {
213 const linkType = this.getModelType(link);
214 if (linkType === 'servicedependency') {
215 const sourceId = this.getServiceId(link.subscriber_service_id);
216 const targetId = this.getServiceId(link.provider_service_id);
217 this.serviceGraph.setEdge(sourceId, targetId, link);
218 }
219 if (linkType === 'serviceinstancelink') {
220 // NOTE serviceinstancelink can point also to services, networks, ...
221 const sourceId = this.getServiceInstanceId(link.provider_service_instance_id);
222 if (angular.isDefined(link.subscriber_service_instance_id)) {
223 const targetId = this.getServiceInstanceId(link.subscriber_service_instance_id);
224 this.serviceGraph.setEdge(sourceId, targetId, link);
225 }
226 }
227 }
228
229 private addOwnershipEdge(link: any) {
230 const sourceId = this.getServiceInstanceId(link.service_instance);
231 const targetId = this.getServiceId(link.service);
232 this.serviceGraph.setEdge(sourceId, targetId, link);
233 }
234
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800235 private addInstanceOwner(tenantWithContainer: any) {
236 // NOTE some TenantWithContainer don't have an instance
237 if (tenantWithContainer.instance_id) {
238 const sourceId = this.getServiceInstanceId(tenantWithContainer.id);
239 const targetId = this.getInstanceId(tenantWithContainer.instance_id);
240 this.serviceGraph.setEdge(sourceId, targetId, angular.merge(tenantWithContainer, {type: 'instance_ownership'}));
241 }
242 }
243
244 private addNetworkLink(port: any) {
245 // ports are connected to 1 Instance and 1 Network
246 const sourceId = this.getInstanceId(port.instance_id);
247 const targetId = this.getNetworkId(port.network_id);
248 this.serviceGraph.setEdge(sourceId, targetId, angular.merge(port, {type: 'port'}));
249 }
250
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800251 private removeElementsFromGraph(type: string) {
252 _.forEach(this.serviceGraph.nodes(), (n: string) => {
253 const node = this.serviceGraph.node(n);
254 const nodeType = this.getModelType(node);
255 if (nodeType === type) {
256 this.serviceGraph.removeNode(n);
257 }
258 });
259 // NOTE update the observable
260 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
261 }
262
263 // helpers
264 private getModelType(node: IXosBaseModel): string {
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800265 if (!node) {
266 this.$log.warn(`[XosGraphStore] Someone called getModelType with an empty model: ${node}`);
267 return 'missing-node';
268 }
269
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800270 if (node.type) {
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800271 // NOTE handling "ownership" links
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800272 return node.type;
273 }
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800274
275 if (node.class_names.indexOf('ServiceInstance,') > -1 ) {
276 return 'serviceinstance';
277 }
278
279 if (node.class_names.indexOf('Service,') > -1 ) {
280 return 'service';
281 }
282
283 if (node.class_names.indexOf('Instance,') > -1 ) {
284 return 'instance';
285 }
286
287 if (node.class_names.indexOf('Network,') > -1 ) {
288 return 'network';
289 }
290
291 this.$log.warn(`[XosGraphStore] Unkown model type: ${node.class_names}`);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800292 return node.class_names.split(',')[0].toLowerCase();
293 }
294
295 private getServiceId(id: number): string {
296 return `service~${id}`;
297 }
298
299 private getServiceInstanceId(id: number): string {
300 return `serviceinstance~${id}`;
301 }
302
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800303 private getInstanceId(id: number): string {
304 return `instance~${id}`;
305 }
306
307 private getNetworkId(id: number): string {
308 return `network~${id}`;
309 }
310
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800311 private getNodeId(node: IXosBaseModel): string {
312
313 const nodeType = this.getModelType(node);
Matteo Scandolo865b11c2018-02-14 16:57:44 -0800314
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800315 switch (nodeType) {
316 case 'service':
317 return this.getServiceId(node.id);
318 case 'serviceinstance':
319 return this.getServiceInstanceId(node.id);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800320 case 'instance':
321 return this.getInstanceId(node.id);
322 case 'network':
323 return this.getNetworkId(node.id);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800324 }
325 }
326
327 // data loaders
328 private loadServices() {
329 this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
330 .subscribe(
331 (res) => {
332 if (res.length > 0) {
333 _.forEach(res, n => {
334 this.addNode(n);
335 });
336 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
337 }
338 },
339 (err) => {
340 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
341 }
342 );
343 }
344
345 private loadServiceDependencies() {
346 this.ServiceDependencySubscription = this.XosModelStore.query('ServiceDependency', '/core/servicedependencys')
347 .subscribe(
348 (res) => {
349 if (res.length > 0) {
350 _.forEach(res, l => {
351 this.addEdge(l);
352 });
353 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
354 }
355 },
356 (err) => {
357 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
358 }
359 );
360 }
361
362 private loadServiceInstances() {
363 this.ServiceInstanceSubscription = this.XosModelStore.query('ServiceInstance', '/core/serviceinstances')
364 .subscribe(
365 (res) => {
366 if (res.length > 0) {
367 _.forEach(res, n => {
368 this.addNode(n);
369 });
370 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
371 }
372 },
373 (err) => {
374 this.$log.error(`[XosServiceGraphStore] ServiceInstance Observable: `, err);
375 }
376 );
377 }
378
379 private loadServiceInstanceLinks() {
380 this.ServiceInstanceLinkSubscription = this.XosModelStore.query('ServiceInstanceLink', '/core/serviceinstancelinks')
381 .subscribe(
382 (res) => {
383 if (res.length > 0) {
384 _.forEach(res, l => {
385 this.addEdge(l);
386 });
387 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
388 }
389 },
390 (err) => {
391 this.$log.error(`[XosServiceGraphStore] ServiceInstanceLinks Observable: `, err);
392 }
393 );
394 }
395
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800396 private loadInstances() {
397 this.InstanceSubscription = this.XosModelStore.query('Instance', '/core/instances')
398 .subscribe(
399 (res) => {
400 if (res.length > 0) {
401 _.forEach(res, n => {
402 this.addNode(n);
403 });
404 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
405 }
406 },
407 (err) => {
408 this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
409 }
410 );
411 }
412
413 private loadInstanceLinks() {
414 this.TenantWithContainerSubscription = this.XosModelStore.query('TnantWithContainer', '/core/tenantwithcontainers')
415 .subscribe(
416 (res) => {
417 if (res.length > 0) {
418 _.forEach(res, n => {
419 this.addInstanceOwner(n);
420 });
421 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
422 }
423 },
424 (err) => {
425 this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
426 }
427 );
428 }
429
430 private loadNetworks() {
431 this.NetworkSubscription = this.XosModelStore.query('Network', '/core/networks')
432 .subscribe(
433 (res) => {
434 if (res.length > 0) {
435 _.forEach(res, n => {
436 this.addNode(n);
437 });
438 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
439 }
440 },
441 (err) => {
442 this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
443 }
444 );
445 }
446
447 private loadPorts() {
448 this.PortSubscription = this.XosModelStore.query('Port', '/core/ports')
449 .subscribe(
450 (res) => {
451 if (res.length > 0) {
452 _.forEach(res, n => {
453 this.addNetworkLink(n);
454 });
455 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
456 }
457 },
458 (err) => {
459 this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
460 }
461 );
462 }
463
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800464 private callNext(subject: BehaviorSubject<any>, data: any) {
465 subject.next(data);
466 }
467}