blob: 22663299db06c07d0385cf35a4ab979b3d11f1a8 [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 Scandolo265c2042017-03-20 10:15:40 -070031 private emptyGraph: IXosServiceGraph = {
32 nodes: [],
33 links: []
34 };
35
Matteo Scandolo75171782017-03-08 14:17:01 -080036 // representation of the graph as D3 requires
Matteo Scandolo265c2042017-03-20 10:15:40 -070037 private d3CoarseGraph = new BehaviorSubject(this.emptyGraph);
38 private d3FineGrainedGraph = new BehaviorSubject(this.emptyGraph);
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080039
40 // storing locally reference to the data model
41 private services;
42 private tenants;
Matteo Scandolo75171782017-03-08 14:17:01 -080043 private subscribers;
44 private networks;
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080045
46 // debounced functions
47 private handleData;
48
49 // datastore
Matteo Scandoloa160eef2017-03-06 17:21:26 -080050 private ServiceSubscription: Subscription;
51 private TenantSubscription: Subscription;
Matteo Scandolo75171782017-03-08 14:17:01 -080052 private SubscriberSubscription: Subscription;
53 private NetworkSubscription: Subscription;
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080054
55 constructor (
56 private $log: ng.ILogService,
57 private XosModelStore: IXosModelStoreService,
Matteo Scandolo7629cc42017-03-13 14:12:15 -070058 private XosDebouncer: IXosDebouncer,
59 private XosServiceGraphExtender: IXosServiceGraphExtender
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080060 ) {
61
Matteo Scandolo968e7f22017-03-03 11:49:18 -080062 this.$log.info(`[XosServiceGraphStore] Setup`);
63
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080064 // we want to have a quiet period of 500ms from the last event before doing anything
Matteo Scandolo968e7f22017-03-03 11:49:18 -080065 this.handleData = this.XosDebouncer.debounce(this._handleData, 500, this, false);
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080066
Matteo Scandolo968e7f22017-03-03 11:49:18 -080067 // observe models and populate graphData
Matteo Scandolo75171782017-03-08 14:17:01 -080068 // TODO get Nodes (model that represent compute nodes in a pod)
69 // TODO get Instances (model that represent deployed VMs)
Matteo Scandoloa160eef2017-03-06 17:21:26 -080070 this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080071 .subscribe(
72 (res) => {
73 this.combineData(res, 'services');
74 },
75 (err) => {
Matteo Scandolo75171782017-03-08 14:17:01 -080076 this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080077 }
78 );
79
Matteo Scandoloa160eef2017-03-06 17:21:26 -080080 this.TenantSubscription = this.XosModelStore.query('Tenant', '/core/tenants')
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080081 .subscribe(
82 (res) => {
83 this.combineData(res, 'tenants');
84 },
85 (err) => {
Matteo Scandolo75171782017-03-08 14:17:01 -080086 this.$log.error(`[XosServiceGraphStore] Tenant Observable: `, err);
Matteo Scandoloa62adbc2017-03-02 15:37:34 -080087 }
88 );
Matteo Scandolo968e7f22017-03-03 11:49:18 -080089
Matteo Scandolo75171782017-03-08 14:17:01 -080090 this.SubscriberSubscription = this.XosModelStore.query('Subscriber', '/core/subscribers')
Matteo Scandolo968e7f22017-03-03 11:49:18 -080091 .subscribe(
Matteo Scandolo75171782017-03-08 14:17:01 -080092 (res) => {
93 this.combineData(res, 'subscribers');
94 },
95 (err) => {
96 this.$log.error(`[XosServiceGraphStore] Subscriber Observable: `, err);
97 }
98 );
99
100 this.NetworkSubscription = this.XosModelStore.query('Network', '/core/networks')
101 .subscribe(
102 (res) => {
103 this.combineData(res, 'networks');
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800104 },
105 (err) => {
106 this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
107 }
108 );
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800109
Matteo Scandolo75171782017-03-08 14:17:01 -0800110 // observe graphData and build Coarse and FineGrained graphs
111 this.graphData
112 .subscribe(
113 (res: IXosFineGrainedGraphData) => {
Matteo Scandolo98b5f5d2017-03-17 17:09:05 -0700114 this.$log.debug(`[XosServiceGraphStore] New graph data received`, res);
Matteo Scandolo75171782017-03-08 14:17:01 -0800115 this.graphDataToCoarseGraph(res);
116 this.graphDataToFineGrainedGraph(res);
117 },
118 (err) => {
119 this.$log.error(`[XosServiceGraphStore] graphData Observable: `, err);
120 }
121 );
Matteo Scandoloa160eef2017-03-06 17:21:26 -0800122 }
123
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800124 public get() {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800125 return this.d3FineGrainedGraph.asObservable();
126 }
127
128 public getCoarse() {
129 return this.d3CoarseGraph.asObservable();
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800130 }
131
Matteo Scandolo75171782017-03-08 14:17:01 -0800132 private combineData(data: any, type: 'services'|'tenants'|'subscribers'|'networks') {
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800133 switch (type) {
134 case 'services':
135 this.services = data;
136 break;
137 case 'tenants':
138 this.tenants = data;
139 break;
Matteo Scandolo75171782017-03-08 14:17:01 -0800140 case 'subscribers':
141 this.subscribers = data;
142 break;
143 case 'networks':
144 this.networks = data;
145 break;
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800146 }
147 this.handleData(this.services, this.tenants);
148 }
149
150 private _handleData(services: IXosServiceModel[], tenants: IXosTenantModel[]) {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800151 this.graphData.next({
152 services: this.services,
Matteo Scandolo75171782017-03-08 14:17:01 -0800153 tenants: this.tenants,
154 subscribers: this.subscribers,
155 networks: this.networks
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800156 });
157 }
158
Matteo Scandolo75171782017-03-08 14:17:01 -0800159 private getNodeIndexById(id: number | string, nodes: IXosServiceModel[]) {
Matteo Scandolo0c61c9b2017-03-03 11:49:18 -0800160 return _.findIndex(nodes, {id: id});
161 }
162
Matteo Scandolo75171782017-03-08 14:17:01 -0800163 private d3Id(type: string, id: number) {
164 return `${type.toLowerCase()}~${id}`;
165 }
166
167 private getTargetId(tenant: IXosTenantModel) {
168
169 let targetId;
170 if (tenant.subscriber_service_id) {
171 targetId = this.d3Id('service', tenant.subscriber_service_id);
172 }
173 else if (tenant.subscriber_tenant_id) {
174 targetId = this.d3Id('tenant', tenant.subscriber_tenant_id);
175 }
176 else if (tenant.subscriber_network_id) {
177 targetId = this.d3Id('network', tenant.subscriber_network_id);
178 }
179 else if (tenant.subscriber_root_id) {
180 targetId = this.d3Id('subscriber', tenant.subscriber_root_id);
181 }
182 return targetId;
183 }
184
185 private getSourceId(tenant: IXosTenantModel) {
186 return this.d3Id('service', tenant.provider_service_id);
187 }
188
189 private getNodeType(n: any) {
190 return n.class_names.split(',')[0].toLowerCase();
191 }
192
193 private getNodeLabel(n: any) {
194 if (this.getNodeType(n) === 'tenant') {
195 return n.id;
196 }
197 return n.humanReadableName ? n.humanReadableName : n.name;
198 }
199
200 private removeUnwantedFineGrainedData(data: IXosFineGrainedGraphData): IXosFineGrainedGraphData {
201 data.tenants = _.filter(data.tenants, t => t.kind !== 'coarse');
202 data.networks = _.filter(data.networks, n => {
203 const subscriber = _.findIndex(data.tenants, {subscriber_network_id: n.id});
204 return subscriber > -1;
205 });
206 return data;
207 }
208
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800209 private graphDataToCoarseGraph(data: IXosCoarseGraphData) {
Matteo Scandolo75171782017-03-08 14:17:01 -0800210
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700211 try {
212 const links: IXosServiceGraphLink[] = _.chain(data.tenants)
213 .filter((t: IXosTenantModel) => t.kind === 'coarse')
214 .map((t: IXosTenantModel) => {
215 return {
216 id: t.id,
217 source: this.getNodeIndexById(t.provider_service_id, data.services),
218 target: this.getNodeIndexById(t.subscriber_service_id, data.services),
219 model: t
220 };
221 })
222 .value();
223
224 const nodes: IXosServiceGraphNode[] = _.map(data.services, (s: IXosServiceModel) => {
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800225 return {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700226 id: s.id,
227 label: s.name,
228 model: s
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800229 };
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700230 });
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800231
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700232 let graph: IXosServiceGraph = {
233 nodes,
234 links
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800235 };
Matteo Scandolo968e7f22017-03-03 11:49:18 -0800236
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700237 _.forEach(this.XosServiceGraphExtender.getCoarse(), (r: IXosServiceGraphReducer) => {
238 graph = r.reducer(graph);
239 });
240
241 this.d3CoarseGraph.next(graph);
242 } catch (e) {
243 this.d3CoarseGraph.error(e);
244 }
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800245 }
246
Matteo Scandolo75171782017-03-08 14:17:01 -0800247 private graphDataToFineGrainedGraph(data: IXosFineGrainedGraphData) {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700248 try {
249 data = this.removeUnwantedFineGrainedData(data);
Matteo Scandolo75171782017-03-08 14:17:01 -0800250
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700251 let nodes = _.reduce(Object.keys(data), (list: any[], k: string) => {
252 return list.concat(data[k]);
253 }, []);
Matteo Scandolo75171782017-03-08 14:17:01 -0800254
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700255 nodes = _.chain(nodes)
256 .map(n => {
257 n.d3Id = this.d3Id(this.getNodeType(n), n.id);
258 return n;
259 })
260 .map(n => {
261 let node: IXosServiceGraphNode = {
262 id: n.d3Id,
263 label: this.getNodeLabel(n),
264 model: n,
265 type: this.getNodeType(n)
266 };
267 return node;
268 })
269 .value();
270
271 const links = _.reduce(data.tenants, (links: IXosServiceGraphLink[], tenant: IXosTenantModel) => {
272 const sourceId = this.getSourceId(tenant);
273 const targetId = this.getTargetId(tenant);
274
275 if (!angular.isDefined(targetId)) {
276 // if the tenant is not pointing to anything, don't draw links
277 return links;
278 }
279
280 const tenantToProvider = {
281 id: `${sourceId}_${tenant.d3Id}`,
282 source: this.getNodeIndexById(sourceId, nodes),
283 target: this.getNodeIndexById(tenant.d3Id, nodes),
284 model: tenant
Matteo Scandolo75171782017-03-08 14:17:01 -0800285 };
Matteo Scandolo75171782017-03-08 14:17:01 -0800286
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700287 const tenantToSubscriber = {
288 id: `${tenant.d3Id}_${targetId}`,
289 source: this.getNodeIndexById(tenant.d3Id, nodes),
290 target: this.getNodeIndexById(targetId, nodes),
291 model: tenant
292 };
Matteo Scandolo75171782017-03-08 14:17:01 -0800293
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700294 links.push(tenantToProvider);
295 links.push(tenantToSubscriber);
Matteo Scandolo6d3e80e2017-03-10 11:34:43 -0800296 return links;
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700297 }, []);
298
Matteo Scandolo265c2042017-03-20 10:15:40 -0700299 if (nodes.length === 0 && links.length === 0) {
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700300 return;
Matteo Scandolo6d3e80e2017-03-10 11:34:43 -0800301 }
302
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700303 let graph: IXosServiceGraph = {
304 nodes,
305 links
Matteo Scandolo75171782017-03-08 14:17:01 -0800306 };
307
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700308 _.forEach(this.XosServiceGraphExtender.getFinegrained(), (r: IXosServiceGraphReducer) => {
309 graph = r.reducer(graph);
310 });
Matteo Scandolo7629cc42017-03-13 14:12:15 -0700311 this.d3FineGrainedGraph.next(graph);
312 } catch (e) {
313 this.d3FineGrainedGraph.error(e);
Matteo Scandolo75171782017-03-08 14:17:01 -0800314 }
Matteo Scandolo75171782017-03-08 14:17:01 -0800315 }
Matteo Scandoloa62adbc2017-03-02 15:37:34 -0800316}