blob: 52100e7abfad29c49e5debe29ec71424bcda565e [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[];
32 toggleServiceInstances(): Graph;
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080033 toggleInstances(): Graph;
34 toggleNetwork(): Graph;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080035}
36
37export class XosGraphStore implements IXosGraphStore {
38 static $inject = [
39 '$log',
40 'XosModelStore',
41 'XosDebouncer'
42 ];
43
44 // state
45 private serviceInstanceShown: boolean = false;
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080046 private instanceShown: boolean = false;
47 private networkShown: boolean = false;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080048
49 // 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,
69 private XosDebouncer: IXosDebouncer
70 ) {
71 this.$log.info('[XosGraphStore] Setup');
72
73 this.serviceGraph = new Graph();
74 this.ServiceGraphSubject = new BehaviorSubject(this.serviceGraph);
75
76 this.loadData();
77 }
78
79 $onDestroy() {
80 this.ServiceSubscription.unsubscribe();
81 this.ServiceDependencySubscription.unsubscribe();
82 }
83
84 public nodesFromGraph(graph: Graph): IXosSgNode[] {
85 return _.map(graph.nodes(), (n: string) => {
86 const nodeData = graph.node(n);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -080087
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080088 return {
89 id: n,
90 type: this.getModelType(nodeData),
91 data: nodeData
92 };
93 });
94 }
95
96 public linksFromGraph(graph: Graph): IXosSgLink[] {
97 const nodes = this.nodesFromGraph(graph);
98
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080099 return _.map(graph.edges(), l => {
100 const link = graph.edge(l);
101 const linkType = this.getModelType(link);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800102 let sourceId, targetId;
103
104 switch (linkType) {
105 case 'servicedependency':
106 sourceId = this.getServiceId(link.subscriber_service_id);
107 targetId = this.getServiceId(link.provider_service_id);
108 break;
109 case 'serviceinstancelink':
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800110 // NOTE ServiceInstanceLink can actually also connect to a service and a network
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800111 sourceId = this.getServiceInstanceId(link.subscriber_service_instance_id);
112 targetId = this.getServiceInstanceId(link.provider_service_instance_id);
113 break;
114 case 'ownership':
115 sourceId = this.getServiceId(link.service);
116 targetId = this.getServiceInstanceId(link.service_instance);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800117 break;
118 case 'instance_ownership':
119 sourceId = this.getServiceInstanceId(link.id);
120 targetId = this.getInstanceId(link.instance_id);
121 break;
122 case 'port':
123 sourceId = this.getInstanceId(link.instance_id);
124 targetId = this.getNetworkId(link.network_id);
125 break;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800126 }
127
128 // NOTE help while debugging
129 if (!sourceId || !targetId) {
130 this.$log.warn(`Link ${l.v}-${l.w} has missing source or target:`, l, link);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800131 // TODO return null and then filter out so that we don't break the rendering
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800132 }
133
134 return {
135 id: `${l.v}-${l.w}`,
136 type: this.getModelType(link),
137 source: _.findIndex(nodes, {id: sourceId}),
138 target: _.findIndex(nodes, {id: targetId}),
139 data: link
140 };
141 });
142 }
143
144 public toggleServiceInstances(): Graph {
145 if (this.serviceInstanceShown) {
146 // NOTE remove subscriptions
147 this.ServiceInstanceSubscription.unsubscribe();
148 this.ServiceInstanceLinkSubscription.unsubscribe();
149
150 // remove nodes from the graph
151 this.removeElementsFromGraph('serviceinstance'); // NOTE links are automatically removed by the graph library
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800152
153 if (this.instanceShown) {
154 // NOTE if we remove ServiceInstances we also need to remove Instances
155 this.removeElementsFromGraph('instance');
156 this.instanceShown = false;
157 }
158
159 if (this.networkShown) {
160 // NOTE if we remove ServiceInstances we also need to remove Networks
161 this.removeElementsFromGraph('network');
162 this.networkShown = false;
163 }
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800164 }
165 else {
166 // NOTE subscribe to ServiceInstance and ServiceInstanceLink observables
167 this.loadServiceInstances();
168 this.loadServiceInstanceLinks();
169 }
170 this.serviceInstanceShown = !this.serviceInstanceShown;
171 return this.serviceGraph;
172 }
173
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800174 public toggleInstances(): Graph {
175 if (this.instanceShown) {
176
177 this.InstanceSubscription.unsubscribe();
178 this.TenantWithContainerSubscription.unsubscribe();
179
180 this.removeElementsFromGraph('instance'); // NOTE links are automatically removed by the graph library
181
182 if (this.networkShown) {
183 // NOTE if we remove Instances we also need to remove Networks
184 this.removeElementsFromGraph('network');
185 this.networkShown = false;
186 }
187 }
188 else {
189 this.loadInstances();
190 this.loadInstanceLinks();
191 }
192 this.instanceShown = !this.instanceShown;
193 return this.serviceGraph;
194 }
195
196 public toggleNetwork() {
197 if (this.networkShown) {
198 this.NetworkSubscription.unsubscribe();
199 this.PortSubscription.unsubscribe();
200 this.removeElementsFromGraph('network');
201 }
202 else {
203 this.loadNetworks();
204 this.loadPorts(); // Ports define the connection of an Instance to a Network
205 }
206
207 this.networkShown = !this.networkShown;
208 return this.serviceGraph;
209 }
210
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800211 public get(): Observable<Graph> {
212 return this.ServiceGraphSubject.asObservable();
213 }
214
215 private loadData() {
216 this.loadServices();
217 this.loadServiceDependencies();
218 }
219
220 // graph operations
221 private addNode(node: IXosBaseModel) {
222 const nodeId = this.getNodeId(node);
223 this.serviceGraph.setNode(nodeId, node);
224
225 const nodeType = this.getModelType(node);
226 if (nodeType === 'serviceinstance') {
227 // NOTE adding owner link
228 this.addOwnershipEdge({
229 service: node.owner_id,
230 service_instance: node.id,
231 type: 'ownership'
232 });
233 }
234 }
235
236 private addEdge(link: IXosBaseModel) {
237 const linkType = this.getModelType(link);
238 if (linkType === 'servicedependency') {
239 const sourceId = this.getServiceId(link.subscriber_service_id);
240 const targetId = this.getServiceId(link.provider_service_id);
241 this.serviceGraph.setEdge(sourceId, targetId, link);
242 }
243 if (linkType === 'serviceinstancelink') {
244 // NOTE serviceinstancelink can point also to services, networks, ...
245 const sourceId = this.getServiceInstanceId(link.provider_service_instance_id);
246 if (angular.isDefined(link.subscriber_service_instance_id)) {
247 const targetId = this.getServiceInstanceId(link.subscriber_service_instance_id);
248 this.serviceGraph.setEdge(sourceId, targetId, link);
249 }
250 }
251 }
252
253 private addOwnershipEdge(link: any) {
254 const sourceId = this.getServiceInstanceId(link.service_instance);
255 const targetId = this.getServiceId(link.service);
256 this.serviceGraph.setEdge(sourceId, targetId, link);
257 }
258
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800259 private addInstanceOwner(tenantWithContainer: any) {
260 // NOTE some TenantWithContainer don't have an instance
261 if (tenantWithContainer.instance_id) {
262 const sourceId = this.getServiceInstanceId(tenantWithContainer.id);
263 const targetId = this.getInstanceId(tenantWithContainer.instance_id);
264 this.serviceGraph.setEdge(sourceId, targetId, angular.merge(tenantWithContainer, {type: 'instance_ownership'}));
265 }
266 }
267
268 private addNetworkLink(port: any) {
269 // ports are connected to 1 Instance and 1 Network
270 const sourceId = this.getInstanceId(port.instance_id);
271 const targetId = this.getNetworkId(port.network_id);
272 this.serviceGraph.setEdge(sourceId, targetId, angular.merge(port, {type: 'port'}));
273 }
274
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800275 private removeElementsFromGraph(type: string) {
276 _.forEach(this.serviceGraph.nodes(), (n: string) => {
277 const node = this.serviceGraph.node(n);
278 const nodeType = this.getModelType(node);
279 if (nodeType === type) {
280 this.serviceGraph.removeNode(n);
281 }
282 });
283 // NOTE update the observable
284 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
285 }
286
287 // helpers
288 private getModelType(node: IXosBaseModel): string {
289 if (node.type) {
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800290 // NOTE handling "ownership" links
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800291 return node.type;
292 }
293 return node.class_names.split(',')[0].toLowerCase();
294 }
295
296 private getServiceId(id: number): string {
297 return `service~${id}`;
298 }
299
300 private getServiceInstanceId(id: number): string {
301 return `serviceinstance~${id}`;
302 }
303
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800304 private getInstanceId(id: number): string {
305 return `instance~${id}`;
306 }
307
308 private getNetworkId(id: number): string {
309 return `network~${id}`;
310 }
311
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800312 private getNodeId(node: IXosBaseModel): string {
313
314 const nodeType = this.getModelType(node);
315 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}