blob: cf438e65fe317943b6863e0b254b3418e19e35d5 [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);
198 this.serviceGraph.setNode(nodeId, node);
199
200 const nodeType = this.getModelType(node);
201 if (nodeType === 'serviceinstance') {
202 // NOTE adding owner link
203 this.addOwnershipEdge({
204 service: node.owner_id,
205 service_instance: node.id,
206 type: 'ownership'
207 });
208 }
209 }
210
211 private addEdge(link: IXosBaseModel) {
212 const linkType = this.getModelType(link);
213 if (linkType === 'servicedependency') {
214 const sourceId = this.getServiceId(link.subscriber_service_id);
215 const targetId = this.getServiceId(link.provider_service_id);
216 this.serviceGraph.setEdge(sourceId, targetId, link);
217 }
218 if (linkType === 'serviceinstancelink') {
219 // NOTE serviceinstancelink can point also to services, networks, ...
220 const sourceId = this.getServiceInstanceId(link.provider_service_instance_id);
221 if (angular.isDefined(link.subscriber_service_instance_id)) {
222 const targetId = this.getServiceInstanceId(link.subscriber_service_instance_id);
223 this.serviceGraph.setEdge(sourceId, targetId, link);
224 }
225 }
226 }
227
228 private addOwnershipEdge(link: any) {
229 const sourceId = this.getServiceInstanceId(link.service_instance);
230 const targetId = this.getServiceId(link.service);
231 this.serviceGraph.setEdge(sourceId, targetId, link);
232 }
233
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800234 private addInstanceOwner(tenantWithContainer: any) {
235 // NOTE some TenantWithContainer don't have an instance
236 if (tenantWithContainer.instance_id) {
237 const sourceId = this.getServiceInstanceId(tenantWithContainer.id);
238 const targetId = this.getInstanceId(tenantWithContainer.instance_id);
239 this.serviceGraph.setEdge(sourceId, targetId, angular.merge(tenantWithContainer, {type: 'instance_ownership'}));
240 }
241 }
242
243 private addNetworkLink(port: any) {
244 // ports are connected to 1 Instance and 1 Network
245 const sourceId = this.getInstanceId(port.instance_id);
246 const targetId = this.getNetworkId(port.network_id);
247 this.serviceGraph.setEdge(sourceId, targetId, angular.merge(port, {type: 'port'}));
248 }
249
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800250 private removeElementsFromGraph(type: string) {
251 _.forEach(this.serviceGraph.nodes(), (n: string) => {
252 const node = this.serviceGraph.node(n);
253 const nodeType = this.getModelType(node);
254 if (nodeType === type) {
255 this.serviceGraph.removeNode(n);
256 }
257 });
258 // NOTE update the observable
259 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
260 }
261
262 // helpers
263 private getModelType(node: IXosBaseModel): string {
264 if (node.type) {
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800265 // NOTE handling "ownership" links
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800266 return node.type;
267 }
268 return node.class_names.split(',')[0].toLowerCase();
269 }
270
271 private getServiceId(id: number): string {
272 return `service~${id}`;
273 }
274
275 private getServiceInstanceId(id: number): string {
276 return `serviceinstance~${id}`;
277 }
278
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800279 private getInstanceId(id: number): string {
280 return `instance~${id}`;
281 }
282
283 private getNetworkId(id: number): string {
284 return `network~${id}`;
285 }
286
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800287 private getNodeId(node: IXosBaseModel): string {
288
289 const nodeType = this.getModelType(node);
290 switch (nodeType) {
291 case 'service':
292 return this.getServiceId(node.id);
293 case 'serviceinstance':
294 return this.getServiceInstanceId(node.id);
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800295 case 'instance':
296 return this.getInstanceId(node.id);
297 case 'network':
298 return this.getNetworkId(node.id);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800299 }
300 }
301
302 // data loaders
303 private loadServices() {
304 this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
305 .subscribe(
306 (res) => {
307 if (res.length > 0) {
308 _.forEach(res, n => {
309 this.addNode(n);
310 });
311 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
312 }
313 },
314 (err) => {
315 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
316 }
317 );
318 }
319
320 private loadServiceDependencies() {
321 this.ServiceDependencySubscription = this.XosModelStore.query('ServiceDependency', '/core/servicedependencys')
322 .subscribe(
323 (res) => {
324 if (res.length > 0) {
325 _.forEach(res, l => {
326 this.addEdge(l);
327 });
328 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
329 }
330 },
331 (err) => {
332 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
333 }
334 );
335 }
336
337 private loadServiceInstances() {
338 this.ServiceInstanceSubscription = this.XosModelStore.query('ServiceInstance', '/core/serviceinstances')
339 .subscribe(
340 (res) => {
341 if (res.length > 0) {
342 _.forEach(res, n => {
343 this.addNode(n);
344 });
345 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
346 }
347 },
348 (err) => {
349 this.$log.error(`[XosServiceGraphStore] ServiceInstance Observable: `, err);
350 }
351 );
352 }
353
354 private loadServiceInstanceLinks() {
355 this.ServiceInstanceLinkSubscription = this.XosModelStore.query('ServiceInstanceLink', '/core/serviceinstancelinks')
356 .subscribe(
357 (res) => {
358 if (res.length > 0) {
359 _.forEach(res, l => {
360 this.addEdge(l);
361 });
362 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
363 }
364 },
365 (err) => {
366 this.$log.error(`[XosServiceGraphStore] ServiceInstanceLinks Observable: `, err);
367 }
368 );
369 }
370
Matteo Scandolo1888b2a2018-01-08 16:49:06 -0800371 private loadInstances() {
372 this.InstanceSubscription = this.XosModelStore.query('Instance', '/core/instances')
373 .subscribe(
374 (res) => {
375 if (res.length > 0) {
376 _.forEach(res, n => {
377 this.addNode(n);
378 });
379 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
380 }
381 },
382 (err) => {
383 this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
384 }
385 );
386 }
387
388 private loadInstanceLinks() {
389 this.TenantWithContainerSubscription = this.XosModelStore.query('TnantWithContainer', '/core/tenantwithcontainers')
390 .subscribe(
391 (res) => {
392 if (res.length > 0) {
393 _.forEach(res, n => {
394 this.addInstanceOwner(n);
395 });
396 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
397 }
398 },
399 (err) => {
400 this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
401 }
402 );
403 }
404
405 private loadNetworks() {
406 this.NetworkSubscription = this.XosModelStore.query('Network', '/core/networks')
407 .subscribe(
408 (res) => {
409 if (res.length > 0) {
410 _.forEach(res, n => {
411 this.addNode(n);
412 });
413 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
414 }
415 },
416 (err) => {
417 this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
418 }
419 );
420 }
421
422 private loadPorts() {
423 this.PortSubscription = this.XosModelStore.query('Port', '/core/ports')
424 .subscribe(
425 (res) => {
426 if (res.length > 0) {
427 _.forEach(res, n => {
428 this.addNetworkLink(n);
429 });
430 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
431 }
432 },
433 (err) => {
434 this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
435 }
436 );
437 }
438
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800439 private callNext(subject: BehaviorSubject<any>, data: any) {
440 subject.next(data);
441 }
442}