blob: 54bc0831b769632b12c29a1852a7f59499e1760d [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) => {
109 this.graphDataToCoarseGraph(res);
110 this.graphDataToFineGrainedGraph(res);
111 },
112 (err) => {
113 this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
114 }
115 );
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800116 }
117
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800118 public get() {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800119 return this.d3FineGrainedGraph.asObservable();
120 }
121
122 public getCoarse() {
123 return this.d3CoarseGraph.asObservable();
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800124 }
125
Matteo Scandolo75171782017-03-08 14:17:01 -0800126 private combineData(data: any, type: 'services'|'tenants'|'subscribers'|'networks') {
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800127 switch (type) {
128 case 'services':
129 this.services = data;
130 break;
131 case 'tenants':
132 this.tenants = data;
133 break;
Matteo Scandolo75171782017-03-08 14:17:01 -0800134 case 'subscribers':
135 this.subscribers = data;
136 break;
137 case 'networks':
138 this.networks = data;
139 break;
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800140 }
141 this.handleData(this.services, this.tenants);
142 }
143
144 private _handleData(services: IXosServiceModel[], tenants: IXosTenantModel[]) {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800145 this.graphData.next({
146 services: this.services,
Matteo Scandolo75171782017-03-08 14:17:01 -0800147 tenants: this.tenants,
148 subscribers: this.subscribers,
149 networks: this.networks
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800150 });
151 }
152
Matteo Scandolo75171782017-03-08 14:17:01 -0800153 private getNodeIndexById(id: number | string, nodes: IXosServiceModel[]) {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800154 return _.findIndex(nodes, {id: id});
155 }
156
Matteo Scandolo75171782017-03-08 14:17:01 -0800157 private d3Id(type: string, id: number) {
158 return `${type.toLowerCase()}~${id}`;
159 }
160
161 private getTargetId(tenant: IXosTenantModel) {
162
163 let targetId;
164 if (tenant.subscriber_service_id) {
165 targetId = this.d3Id('service', tenant.subscriber_service_id);
166 }
167 else if (tenant.subscriber_tenant_id) {
168 targetId = this.d3Id('tenant', tenant.subscriber_tenant_id);
169 }
170 else if (tenant.subscriber_network_id) {
171 targetId = this.d3Id('network', tenant.subscriber_network_id);
172 }
173 else if (tenant.subscriber_root_id) {
174 targetId = this.d3Id('subscriber', tenant.subscriber_root_id);
175 }
176 return targetId;
177 }
178
179 private getSourceId(tenant: IXosTenantModel) {
180 return this.d3Id('service', tenant.provider_service_id);
181 }
182
183 private getNodeType(n: any) {
184 return n.class_names.split(',')[0].toLowerCase();
185 }
186
187 private getNodeLabel(n: any) {
188 if (this.getNodeType(n) === 'tenant') {
189 return n.id;
190 }
191 return n.humanReadableName ? n.humanReadableName : n.name;
192 }
193
194 private removeUnwantedFineGrainedData(data: IXosFineGrainedGraphData): IXosFineGrainedGraphData {
195 data.tenants = _.filter(data.tenants, t => t.kind !== 'coarse');
196 data.networks = _.filter(data.networks, n => {
197 const subscriber = _.findIndex(data.tenants, {subscriber_network_id: n.id});
198 return subscriber > -1;
199 });
200 return data;
201 }
202
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800203 private graphDataToCoarseGraph(data: IXosCoarseGraphData) {
Matteo Scandolo75171782017-03-08 14:17:01 -0800204
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700205 try {
206 const links: IXosServiceGraphLink[] = _.chain(data.tenants)
207 .filter((t: IXosTenantModel) => t.kind === 'coarse')
208 .map((t: IXosTenantModel) => {
209 return {
210 id: t.id,
211 source: this.getNodeIndexById(t.provider_service_id, data.services),
212 target: this.getNodeIndexById(t.subscriber_service_id, data.services),
213 model: t
214 };
215 })
216 .value();
217
218 const nodes: IXosServiceGraphNode[] = _.map(data.services, (s: IXosServiceModel) => {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800219 return {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700220 id: s.id,
221 label: s.name,
222 model: s
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800223 };
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700224 });
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800225
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700226 let graph: IXosServiceGraph = {
227 nodes,
228 links
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800229 };
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800230
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700231 _.forEach(this.XosServiceGraphExtender.getCoarse(), (r: IXosServiceGraphReducer) => {
232 graph = r.reducer(graph);
233 });
234
235 this.d3CoarseGraph.next(graph);
236 } catch (e) {
237 this.d3CoarseGraph.error(e);
238 }
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800239 }
240
Matteo Scandolo75171782017-03-08 14:17:01 -0800241 private graphDataToFineGrainedGraph(data: IXosFineGrainedGraphData) {
242
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700243 try {
244 data = this.removeUnwantedFineGrainedData(data);
Matteo Scandolo75171782017-03-08 14:17:01 -0800245
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700246 let nodes = _.reduce(Object.keys(data), (list: any[], k: string) => {
247 return list.concat(data[k]);
248 }, []);
Matteo Scandolo75171782017-03-08 14:17:01 -0800249
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700250 nodes = _.chain(nodes)
251 .map(n => {
252 n.d3Id = this.d3Id(this.getNodeType(n), n.id);
253 return n;
254 })
255 .map(n => {
256 let node: IXosServiceGraphNode = {
257 id: n.d3Id,
258 label: this.getNodeLabel(n),
259 model: n,
260 type: this.getNodeType(n)
261 };
262 return node;
263 })
264 .value();
265
266 const links = _.reduce(data.tenants, (links: IXosServiceGraphLink[], tenant: IXosTenantModel) => {
267 const sourceId = this.getSourceId(tenant);
268 const targetId = this.getTargetId(tenant);
269
270 if (!angular.isDefined(targetId)) {
271 // if the tenant is not pointing to anything, don't draw links
272 return links;
273 }
274
275 const tenantToProvider = {
276 id: `${sourceId}_${tenant.d3Id}`,
277 source: this.getNodeIndexById(sourceId, nodes),
278 target: this.getNodeIndexById(tenant.d3Id, nodes),
279 model: tenant
Matteo Scandolo75171782017-03-08 14:17:01 -0800280 };
Matteo Scandolo75171782017-03-08 14:17:01 -0800281
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700282 const tenantToSubscriber = {
283 id: `${tenant.d3Id}_${targetId}`,
284 source: this.getNodeIndexById(tenant.d3Id, nodes),
285 target: this.getNodeIndexById(targetId, nodes),
286 model: tenant
287 };
Matteo Scandolo75171782017-03-08 14:17:01 -0800288
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700289 links.push(tenantToProvider);
290 links.push(tenantToSubscriber);
Matteo Scandolo6d3e80e2017-03-10 11:34:43 -0800291 return links;
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700292 }, []);
293
294 if (nodes.length === 0 || links.length === 0) {
295 return;
Matteo Scandolo6d3e80e2017-03-10 11:34:43 -0800296 }
297
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700298 let graph: IXosServiceGraph = {
299 nodes,
300 links
Matteo Scandolo75171782017-03-08 14:17:01 -0800301 };
302
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700303 _.forEach(this.XosServiceGraphExtender.getFinegrained(), (r: IXosServiceGraphReducer) => {
304 graph = r.reducer(graph);
305 });
Matteo Scandolo75171782017-03-08 14:17:01 -0800306
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700307 this.d3FineGrainedGraph.next(graph);
308 } catch (e) {
309 this.d3FineGrainedGraph.error(e);
Matteo Scandolo75171782017-03-08 14:17:01 -0800310 }
Matteo Scandolo75171782017-03-08 14:17:01 -0800311 }
312
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800313}