blob: 9ab1a5d02090979924151d2f61affef52c4c851a [file] [log] [blame]
Matteo Scandolo968e7f22017-03-03 11:49:18 -08001import * as _ from 'lodash';
Matteo Scandoloa160eef2017-03-06 17:21:26 -08002import {Observable, BehaviorSubject, Subscription} from 'rxjs';
Matteo Scandoloa62adbc2017-03-02 15:37:34 -08003import {IXosModelStoreService} from '../../datasources/stores/model.store';
Matteo Scandolo968e7f22017-03-03 11:49:18 -08004import {
5 IXosServiceGraph, IXosServiceModel, IXosTenantModel, IXosCoarseGraphData,
Matteo Scandolo75171782017-03-08 14:17:01 -08006 IXosServiceGraphNode, IXosServiceGraphLink, IXosFineGrainedGraphData
Matteo Scandolo968e7f22017-03-03 11:49:18 -08007} from '../interfaces';
Matteo Scandoloa62adbc2017-03-02 15:37:34 -08008import {IXosDebouncer} from '../../core/services/helpers/debounce.helper';
Matteo Scandolo7629cc42017-03-13 14:12:15 -07009import {IXosServiceGraphExtender, IXosServiceGraphReducer} from './graph.extender';
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080010export interface IXosServiceGraphStore {
11 get(): Observable<IXosServiceGraph>;
Matteo Scandolo968e7f22017-03-03 11:49:18 -080012 getCoarse(): Observable<IXosServiceGraph>;
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080013}
14
15export class XosServiceGraphStore implements IXosServiceGraphStore {
16 static $inject = [
17 '$log',
18 'XosModelStore',
Matteo Scandolo7629cc42017-03-13 14:12:15 -070019 'XosDebouncer',
20 'XosServiceGraphExtender'
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080021 ];
Matteo Scandolo968e7f22017-03-03 11:49:18 -080022
23 // graph data store
Matteo Scandolo75171782017-03-08 14:17:01 -080024 private graphData: BehaviorSubject<IXosFineGrainedGraphData> = new BehaviorSubject({
Matteo Scandolo968e7f22017-03-03 11:49:18 -080025 services: [],
Matteo Scandolo75171782017-03-08 14:17:01 -080026 tenants: [],
27 networks: [],
28 subscribers: []
Matteo Scandolo968e7f22017-03-03 11:49:18 -080029 });
30
Matteo Scandolo75171782017-03-08 14:17:01 -080031 // representation of the graph as D3 requires
Matteo Scandolo968e7f22017-03-03 11:49:18 -080032 private d3CoarseGraph = new BehaviorSubject({});
33 private d3FineGrainedGraph = new BehaviorSubject({});
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080034
35 // storing locally reference to the data model
36 private services;
37 private tenants;
Matteo Scandolo75171782017-03-08 14:17:01 -080038 private subscribers;
39 private networks;
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080040
41 // debounced functions
42 private handleData;
43
44 // datastore
Matteo Scandoloa160eef2017-03-06 17:21:26 -080045 private ServiceSubscription: Subscription;
46 private TenantSubscription: Subscription;
Matteo Scandolo75171782017-03-08 14:17:01 -080047 private SubscriberSubscription: Subscription;
48 private NetworkSubscription: Subscription;
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080049
50 constructor (
51 private $log: ng.ILogService,
52 private XosModelStore: IXosModelStoreService,
Matteo Scandolo7629cc42017-03-13 14:12:15 -070053 private XosDebouncer: IXosDebouncer,
54 private XosServiceGraphExtender: IXosServiceGraphExtender
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080055 ) {
56
Matteo Scandolo968e7f22017-03-03 11:49:18 -080057 this.$log.info(`[XosServiceGraphStore] Setup`);
58
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080059 // we want to have a quiet period of 500ms from the last event before doing anything
Matteo Scandolo968e7f22017-03-03 11:49:18 -080060 this.handleData = this.XosDebouncer.debounce(this._handleData, 500, this, false);
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080061
Matteo Scandolo968e7f22017-03-03 11:49:18 -080062 // observe models and populate graphData
Matteo Scandolo75171782017-03-08 14:17:01 -080063 // TODO get Nodes (model that represent compute nodes in a pod)
64 // TODO get Instances (model that represent deployed VMs)
Matteo Scandoloa160eef2017-03-06 17:21:26 -080065 this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080066 .subscribe(
67 (res) => {
68 this.combineData(res, 'services');
69 },
70 (err) => {
Matteo Scandolo75171782017-03-08 14:17:01 -080071 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080072 }
73 );
74
Matteo Scandoloa160eef2017-03-06 17:21:26 -080075 this.TenantSubscription = this.XosModelStore.query('Tenant', '/core/tenants')
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080076 .subscribe(
77 (res) => {
78 this.combineData(res, 'tenants');
79 },
80 (err) => {
Matteo Scandolo75171782017-03-08 14:17:01 -080081 this.$log.error(`[XosServiceGraphStore] Tenant Observable: `, err);
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080082 }
83 );
Matteo Scandolo968e7f22017-03-03 11:49:18 -080084
Matteo Scandolo75171782017-03-08 14:17:01 -080085 this.SubscriberSubscription = this.XosModelStore.query('Subscriber', '/core/subscribers')
Matteo Scandolo968e7f22017-03-03 11:49:18 -080086 .subscribe(
Matteo Scandolo75171782017-03-08 14:17:01 -080087 (res) => {
88 this.combineData(res, 'subscribers');
89 },
90 (err) => {
91 this.$log.error(`[XosServiceGraphStore] Subscriber Observable: `, err);
92 }
93 );
94
95 this.NetworkSubscription = this.XosModelStore.query('Network', '/core/networks')
96 .subscribe(
97 (res) => {
98 this.combineData(res, 'networks');
Matteo Scandolo968e7f22017-03-03 11:49:18 -080099 },
100 (err) => {
101 this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
102 }
103 );
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800104
Matteo Scandolo75171782017-03-08 14:17:01 -0800105 // observe graphData and build Coarse and FineGrained graphs
106 this.graphData
107 .subscribe(
108 (res: IXosFineGrainedGraphData) => {
Matteo Scandolo98b5f5d2017-03-17 17:09:05 -0700109 this.$log.debug(`[XosServiceGraphStore] New graph data received`, res);
Matteo Scandolo75171782017-03-08 14:17:01 -0800110 this.graphDataToCoarseGraph(res);
111 this.graphDataToFineGrainedGraph(res);
112 },
113 (err) => {
114 this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
115 }
116 );
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800117 }
118
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800119 public get() {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800120 return this.d3FineGrainedGraph.asObservable();
121 }
122
123 public getCoarse() {
124 return this.d3CoarseGraph.asObservable();
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800125 }
126
Matteo Scandolo75171782017-03-08 14:17:01 -0800127 private combineData(data: any, type: 'services'|'tenants'|'subscribers'|'networks') {
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800128 switch (type) {
129 case 'services':
130 this.services = data;
131 break;
132 case 'tenants':
133 this.tenants = data;
134 break;
Matteo Scandolo75171782017-03-08 14:17:01 -0800135 case 'subscribers':
136 this.subscribers = data;
137 break;
138 case 'networks':
139 this.networks = data;
140 break;
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800141 }
142 this.handleData(this.services, this.tenants);
143 }
144
145 private _handleData(services: IXosServiceModel[], tenants: IXosTenantModel[]) {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800146 this.graphData.next({
147 services: this.services,
Matteo Scandolo75171782017-03-08 14:17:01 -0800148 tenants: this.tenants,
149 subscribers: this.subscribers,
150 networks: this.networks
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800151 });
152 }
153
Matteo Scandolo75171782017-03-08 14:17:01 -0800154 private getNodeIndexById(id: number | string, nodes: IXosServiceModel[]) {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800155 return _.findIndex(nodes, {id: id});
156 }
157
Matteo Scandolo75171782017-03-08 14:17:01 -0800158 private d3Id(type: string, id: number) {
159 return `${type.toLowerCase()}~${id}`;
160 }
161
162 private getTargetId(tenant: IXosTenantModel) {
163
164 let targetId;
165 if (tenant.subscriber_service_id) {
166 targetId = this.d3Id('service', tenant.subscriber_service_id);
167 }
168 else if (tenant.subscriber_tenant_id) {
169 targetId = this.d3Id('tenant', tenant.subscriber_tenant_id);
170 }
171 else if (tenant.subscriber_network_id) {
172 targetId = this.d3Id('network', tenant.subscriber_network_id);
173 }
174 else if (tenant.subscriber_root_id) {
175 targetId = this.d3Id('subscriber', tenant.subscriber_root_id);
176 }
177 return targetId;
178 }
179
180 private getSourceId(tenant: IXosTenantModel) {
181 return this.d3Id('service', tenant.provider_service_id);
182 }
183
184 private getNodeType(n: any) {
185 return n.class_names.split(',')[0].toLowerCase();
186 }
187
188 private getNodeLabel(n: any) {
189 if (this.getNodeType(n) === 'tenant') {
190 return n.id;
191 }
192 return n.humanReadableName ? n.humanReadableName : n.name;
193 }
194
195 private removeUnwantedFineGrainedData(data: IXosFineGrainedGraphData): IXosFineGrainedGraphData {
196 data.tenants = _.filter(data.tenants, t => t.kind !== 'coarse');
197 data.networks = _.filter(data.networks, n => {
198 const subscriber = _.findIndex(data.tenants, {subscriber_network_id: n.id});
199 return subscriber > -1;
200 });
201 return data;
202 }
203
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800204 private graphDataToCoarseGraph(data: IXosCoarseGraphData) {
Matteo Scandolo75171782017-03-08 14:17:01 -0800205
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700206 try {
207 const links: IXosServiceGraphLink[] = _.chain(data.tenants)
208 .filter((t: IXosTenantModel) => t.kind === 'coarse')
209 .map((t: IXosTenantModel) => {
210 return {
211 id: t.id,
212 source: this.getNodeIndexById(t.provider_service_id, data.services),
213 target: this.getNodeIndexById(t.subscriber_service_id, data.services),
214 model: t
215 };
216 })
217 .value();
218
219 const nodes: IXosServiceGraphNode[] = _.map(data.services, (s: IXosServiceModel) => {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800220 return {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700221 id: s.id,
222 label: s.name,
223 model: s
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800224 };
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700225 });
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800226
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700227 let graph: IXosServiceGraph = {
228 nodes,
229 links
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800230 };
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800231
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700232 _.forEach(this.XosServiceGraphExtender.getCoarse(), (r: IXosServiceGraphReducer) => {
233 graph = r.reducer(graph);
234 });
235
236 this.d3CoarseGraph.next(graph);
237 } catch (e) {
238 this.d3CoarseGraph.error(e);
239 }
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800240 }
241
Matteo Scandolo75171782017-03-08 14:17:01 -0800242 private graphDataToFineGrainedGraph(data: IXosFineGrainedGraphData) {
243
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700244 try {
245 data = this.removeUnwantedFineGrainedData(data);
Matteo Scandolo75171782017-03-08 14:17:01 -0800246
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700247 let nodes = _.reduce(Object.keys(data), (list: any[], k: string) => {
248 return list.concat(data[k]);
249 }, []);
Matteo Scandolo75171782017-03-08 14:17:01 -0800250
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700251 nodes = _.chain(nodes)
252 .map(n => {
253 n.d3Id = this.d3Id(this.getNodeType(n), n.id);
254 return n;
255 })
256 .map(n => {
257 let node: IXosServiceGraphNode = {
258 id: n.d3Id,
259 label: this.getNodeLabel(n),
260 model: n,
261 type: this.getNodeType(n)
262 };
263 return node;
264 })
265 .value();
266
267 const links = _.reduce(data.tenants, (links: IXosServiceGraphLink[], tenant: IXosTenantModel) => {
268 const sourceId = this.getSourceId(tenant);
269 const targetId = this.getTargetId(tenant);
270
271 if (!angular.isDefined(targetId)) {
272 // if the tenant is not pointing to anything, don't draw links
273 return links;
274 }
275
276 const tenantToProvider = {
277 id: `${sourceId}_${tenant.d3Id}`,
278 source: this.getNodeIndexById(sourceId, nodes),
279 target: this.getNodeIndexById(tenant.d3Id, nodes),
280 model: tenant
Matteo Scandolo75171782017-03-08 14:17:01 -0800281 };
Matteo Scandolo75171782017-03-08 14:17:01 -0800282
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700283 const tenantToSubscriber = {
284 id: `${tenant.d3Id}_${targetId}`,
285 source: this.getNodeIndexById(tenant.d3Id, nodes),
286 target: this.getNodeIndexById(targetId, nodes),
287 model: tenant
288 };
Matteo Scandolo75171782017-03-08 14:17:01 -0800289
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700290 links.push(tenantToProvider);
291 links.push(tenantToSubscriber);
Matteo Scandolo6d3e80e2017-03-10 11:34:43 -0800292 return links;
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700293 }, []);
294
295 if (nodes.length === 0 || links.length === 0) {
296 return;
Matteo Scandolo6d3e80e2017-03-10 11:34:43 -0800297 }
298
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700299 let graph: IXosServiceGraph = {
300 nodes,
301 links
Matteo Scandolo75171782017-03-08 14:17:01 -0800302 };
303
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700304 _.forEach(this.XosServiceGraphExtender.getFinegrained(), (r: IXosServiceGraphReducer) => {
305 graph = r.reducer(graph);
306 });
Matteo Scandolo75171782017-03-08 14:17:01 -0800307
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700308 this.d3FineGrainedGraph.next(graph);
309 } catch (e) {
310 this.d3FineGrainedGraph.error(e);
Matteo Scandolo75171782017-03-08 14:17:01 -0800311 }
Matteo Scandolo75171782017-03-08 14:17:01 -0800312 }
313
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800314}