blob: 4d46c881c055aa48d169b3c9871498fa410ed64e [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;
33}
34
35export class XosGraphStore implements IXosGraphStore {
36 static $inject = [
37 '$log',
38 'XosModelStore',
39 'XosDebouncer'
40 ];
41
42 // state
43 private serviceInstanceShown: boolean = false;
44
45 // graphs
46 private serviceGraph: any;
47 private ServiceGraphSubject: BehaviorSubject<any>;
48
49 // datastore
50 private ServiceSubscription: Subscription;
51 private ServiceDependencySubscription: Subscription;
52 private ServiceInstanceSubscription: Subscription;
53 private ServiceInstanceLinkSubscription: Subscription;
54
55 // debounced
56 private efficientNext = this.XosDebouncer.debounce(this.callNext, 500, this, false);
57
58 constructor (
59 private $log: ng.ILogService,
60 private XosModelStore: IXosModelStoreService,
61 private XosDebouncer: IXosDebouncer
62 ) {
63 this.$log.info('[XosGraphStore] Setup');
64
65 this.serviceGraph = new Graph();
66 this.ServiceGraphSubject = new BehaviorSubject(this.serviceGraph);
67
68 this.loadData();
69 }
70
71 $onDestroy() {
72 this.ServiceSubscription.unsubscribe();
73 this.ServiceDependencySubscription.unsubscribe();
74 }
75
76 public nodesFromGraph(graph: Graph): IXosSgNode[] {
77 return _.map(graph.nodes(), (n: string) => {
78 const nodeData = graph.node(n);
79 return {
80 id: n,
81 type: this.getModelType(nodeData),
82 data: nodeData
83 };
84 });
85 }
86
87 public linksFromGraph(graph: Graph): IXosSgLink[] {
88 const nodes = this.nodesFromGraph(graph);
89
90 // NOTE we'll need some intelligence here to differentiate between:
91 // - ServiceDependency
92 // - ServiceInstanceLinks
93 // - Owners
94
95 return _.map(graph.edges(), l => {
96 const link = graph.edge(l);
97 const linkType = this.getModelType(link);
98
99 // FIXME consider ownership links
100 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':
108 sourceId = this.getServiceInstanceId(link.subscriber_service_instance_id);
109 targetId = this.getServiceInstanceId(link.provider_service_instance_id);
110 break;
111 case 'ownership':
112 sourceId = this.getServiceId(link.service);
113 targetId = this.getServiceInstanceId(link.service_instance);
114 }
115
116 // NOTE help while debugging
117 if (!sourceId || !targetId) {
118 this.$log.warn(`Link ${l.v}-${l.w} has missing source or target:`, l, link);
119 }
120
121 return {
122 id: `${l.v}-${l.w}`,
123 type: this.getModelType(link),
124 source: _.findIndex(nodes, {id: sourceId}),
125 target: _.findIndex(nodes, {id: targetId}),
126 data: link
127 };
128 });
129 }
130
131 public toggleServiceInstances(): Graph {
132 if (this.serviceInstanceShown) {
133 // NOTE remove subscriptions
134 this.ServiceInstanceSubscription.unsubscribe();
135 this.ServiceInstanceLinkSubscription.unsubscribe();
136
137 // remove nodes from the graph
138 this.removeElementsFromGraph('serviceinstance'); // NOTE links are automatically removed by the graph library
139 }
140 else {
141 // NOTE subscribe to ServiceInstance and ServiceInstanceLink observables
142 this.loadServiceInstances();
143 this.loadServiceInstanceLinks();
144 }
145 this.serviceInstanceShown = !this.serviceInstanceShown;
146 return this.serviceGraph;
147 }
148
149 public get(): Observable<Graph> {
150 return this.ServiceGraphSubject.asObservable();
151 }
152
153 private loadData() {
154 this.loadServices();
155 this.loadServiceDependencies();
156 }
157
158 // graph operations
159 private addNode(node: IXosBaseModel) {
160 const nodeId = this.getNodeId(node);
161 this.serviceGraph.setNode(nodeId, node);
162
163 const nodeType = this.getModelType(node);
164 if (nodeType === 'serviceinstance') {
165 // NOTE adding owner link
166 this.addOwnershipEdge({
167 service: node.owner_id,
168 service_instance: node.id,
169 type: 'ownership'
170 });
171 }
172 }
173
174 private addEdge(link: IXosBaseModel) {
175 const linkType = this.getModelType(link);
176 if (linkType === 'servicedependency') {
177 const sourceId = this.getServiceId(link.subscriber_service_id);
178 const targetId = this.getServiceId(link.provider_service_id);
179 this.serviceGraph.setEdge(sourceId, targetId, link);
180 }
181 if (linkType === 'serviceinstancelink') {
182 // NOTE serviceinstancelink can point also to services, networks, ...
183 const sourceId = this.getServiceInstanceId(link.provider_service_instance_id);
184 if (angular.isDefined(link.subscriber_service_instance_id)) {
185 const targetId = this.getServiceInstanceId(link.subscriber_service_instance_id);
186 this.serviceGraph.setEdge(sourceId, targetId, link);
187 }
188 }
189 }
190
191 private addOwnershipEdge(link: any) {
192 const sourceId = this.getServiceInstanceId(link.service_instance);
193 const targetId = this.getServiceId(link.service);
194 this.serviceGraph.setEdge(sourceId, targetId, link);
195 }
196
197 private removeElementsFromGraph(type: string) {
198 _.forEach(this.serviceGraph.nodes(), (n: string) => {
199 const node = this.serviceGraph.node(n);
200 const nodeType = this.getModelType(node);
201 if (nodeType === type) {
202 this.serviceGraph.removeNode(n);
203 }
204 });
205 // NOTE update the observable
206 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
207 }
208
209 // helpers
210 private getModelType(node: IXosBaseModel): string {
211 if (node.type) {
212 // NOTE we'll add "ownership" links
213 return node.type;
214 }
215 return node.class_names.split(',')[0].toLowerCase();
216 }
217
218 private getServiceId(id: number): string {
219 return `service~${id}`;
220 }
221
222 private getServiceInstanceId(id: number): string {
223 return `serviceinstance~${id}`;
224 }
225
226 private getNodeId(node: IXosBaseModel): string {
227
228 const nodeType = this.getModelType(node);
229 switch (nodeType) {
230 case 'service':
231 return this.getServiceId(node.id);
232 case 'serviceinstance':
233 return this.getServiceInstanceId(node.id);
234 }
235 }
236
237 // data loaders
238 private loadServices() {
239 this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
240 .subscribe(
241 (res) => {
242 if (res.length > 0) {
243 _.forEach(res, n => {
244 this.addNode(n);
245 });
246 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
247 }
248 },
249 (err) => {
250 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
251 }
252 );
253 }
254
255 private loadServiceDependencies() {
256 this.ServiceDependencySubscription = this.XosModelStore.query('ServiceDependency', '/core/servicedependencys')
257 .subscribe(
258 (res) => {
259 if (res.length > 0) {
260 _.forEach(res, l => {
261 this.addEdge(l);
262 });
263 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
264 }
265 },
266 (err) => {
267 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
268 }
269 );
270 }
271
272 private loadServiceInstances() {
273 this.ServiceInstanceSubscription = this.XosModelStore.query('ServiceInstance', '/core/serviceinstances')
274 .subscribe(
275 (res) => {
276 if (res.length > 0) {
277 _.forEach(res, n => {
278 this.addNode(n);
279 });
280 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
281 }
282 },
283 (err) => {
284 this.$log.error(`[XosServiceGraphStore] ServiceInstance Observable: `, err);
285 }
286 );
287 }
288
289 private loadServiceInstanceLinks() {
290 this.ServiceInstanceLinkSubscription = this.XosModelStore.query('ServiceInstanceLink', '/core/serviceinstancelinks')
291 .subscribe(
292 (res) => {
293 if (res.length > 0) {
294 _.forEach(res, l => {
295 this.addEdge(l);
296 });
297 this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
298 }
299 },
300 (err) => {
301 this.$log.error(`[XosServiceGraphStore] ServiceInstanceLinks Observable: `, err);
302 }
303 );
304 }
305
306 private callNext(subject: BehaviorSubject<any>, data: any) {
307 subject.next(data);
308 }
309}