blob: 223be9f5364b0b5244f60cfcaeaa60051afd71bf [file] [log] [blame]
Matteo Scandolo6bc31bf2016-08-29 10:17:31 -07001(function () {
2 'use strict';
3
4 angular.module('xos.mcord-slicing')
5 .service('SliceGraph', function(_, NodePositioner){
6 const g = new graphlib.Graph();
7
8 /**
9 * @ngdoc method
10 * @name xos.mcord-slicing.SliceGraph#buildGraph
11 * @methodOf xos.mcord-slicing.SliceGraph
12 * @description
13 * buildGraph
14 * @param {object} data An object in the for of {nodes: [], links: []} describing the graph
15 * @returns {null}
16 **/
17 this.buildGraph = (data) => {
18 _.forEach(data.nodes, n => g.setNode(n.id, n));
19 _.forEach(data.links, n => g.setEdge(n.source, n.target, n));
20 };
21
22 this.getLinks = () => {
23 return g.edges().map(e => {
24 return {
25 source: g.node(e.v),
26 target: g.node(e.w),
27 data: g.edge(e)
28 }
29 });
30 }
31
32 this.getGraph = () => g;
33
34 // find the successor of a node
35 this.getNodeSuccessors = (node) => {
36 return _.map(g.successors(node.id), n => {
37 return g.node(n);
38 })
39 };
40
41 this.getNodePredecessors = (node) => {
42 return _.map(g.predecessors(node.id), n => {
43 return g.node(n);
44 });
45 };
46
47 // get data plane successors of a node
48 this.getNodeDataPlaneSuccessors = (node) => {
49 return _.filter(this.getNodeSuccessors(node), n => {
50 return n.plane === 'data';
51 });
52 };
53
54 // find the end of the graph toward upstream
55 this.getUpstreamSinks = (el) => {
56 const sinks = _.reduce(g.sinks(), (sinks, node, i) => {
57 let n = g.node(node);
58 if(n.type === 'upstream'){
59 sinks.push(n);
60 }
61 return sinks;
62 }, []);
63
64 return _.map(sinks, (s, i) => {
65 s.position = {
66 top: 0,
67 bottom: el.clientHeight,
68 total: sinks.length,
69 index: i + 1
70 };
71 return s;
72 })
73 };
74
75 this.positionGraph = (el) => {
76 // get root node
77 let nodes = this.getUpstreamSinks(el);
78
79 // find children, recursively
80 let children = [];
81 _.forEach(nodes, (n, i) => {
82 children = children.concat(this.findPredecessor(n));
83 });
84 nodes = nodes.concat(children);
85
86 // calculate the position for all nodes
87 nodes = _.map(nodes, r => {
88 return NodePositioner.getDataPlaneNodePos(r, el);
89 });
90
91 return nodes;
92 };
93
94 // this iterate on all the nodes, and add position information
95 this.findPredecessor = (node) => {
96 let preds = g.predecessors(node.id);
97
98 // saving predecessor information
99 preds = preds.map((p, i) => {
100 p = g.node(p);
101 const parentAvailableSpace = (node.position.bottom - node.position.top) / node.position.total;
102 const parentY = NodePositioner.getVpos(node);
103 p.position = {
104 top: parentY - (parentAvailableSpace / 2),
105 bottom: (parentY + (parentAvailableSpace / 2)),
106 total: preds.length,
107 index: i + 1
108 };
109 return p;
110 });
111
112 //recurse
113 const predsChild = _.reduce(preds, (list, p) => {
114 return list.concat(this.findPredecessor(p));
115 }, []);
116
117 return preds.concat(predsChild);
118 };
119
120 this.getGraphLinks = (nodes) => {
121 const links = [];
122 _.forEach(nodes, n => {
123 const edges = g.inEdges(n.id);
124 _.forEach(edges, e => {
125 links.push({
126 source: g.node(e.v),
127 target: g.node(e.w),
128 data: g.edge(e)
129 });
130 });
131 });
132 return links;
133 };
134
135 this.getDataPlaneForSlice = (ranRu, sliceId) => {
136 // hardcoded, likely to be improved
137 const ranCu = g.node(g.successors(ranRu.id)[0]);
138 const sgw = g.node(g.successors(ranCu.id)[0]);
139 const pgw = g.node(g.successors(sgw.id)[0]);
140
141 // augmenting nodes with sliceId
142 ranRu.sliceId = sliceId;
143 ranCu.sliceId = sliceId;
144 sgw.sliceId = sliceId;
145 pgw.sliceId = sliceId;
146 return [ranRu, ranCu, sgw, pgw];
147 };
148
149 this.getControlPlaneForSlice = (dataPlane, sliceId) => {
150 return _.reduce(dataPlane, (cp_nodes, dp_node) => {
151 // NOTE: looks that all the time the cplane version of the node is successors number 1, we may need to check
152 let cp_node = g.node(g.successors(dp_node.id)[1]);
153
154 // position relative to their data-plane node
155 cp_node = NodePositioner.getControlPlaneNodePos(cp_node, dp_node);
156 cp_node.sliceId = sliceId;
157 // hardcoded
158 // if control plane node is a sgw, there is an MME attached
159 if(cp_node.type === 'sgw'){
160 let mme = g.node(g.successors(cp_node.id)[1]);
161 // position relative to their data-plane node
162 mme = NodePositioner.getControlPlaneNodePos(mme, cp_node);
163 mme.sliceId = sliceId;
164 cp_nodes.push(mme);
165 }
166
167 return cp_nodes.concat(cp_node);
168 }, []);
169 };
170
171 this.activeSlices = [];
172 // this.usedSlicesId = [];
173 this.getSliceDetail= (node) => {
174 if(node.sliceId && this.activeSlices.indexOf(node.sliceId) > -1){
175 // the slice is already active, return an empty set
176 return [[], []];
177 }
178
179 // let sliceId;
180 // if (node.sliceId){
181 // sliceId = node.sliceId;
182 // }
183 // else{
184 const sliceId = _.min(this.activeSlices) ? _.min(this.activeSlices) + 1 : 1;
185 // }
186 this.activeSlices.push(sliceId);
187 // this.usedSlicesId.push(sliceId);
188
189 // getting the beginning of the slice
190 const ranRu = (function getRanRu(n) {
191 if(n.type === 'ran-ru'){
192 return n;
193 }
194 // we assume that in the slice node have only one predecessor
195 const pred = g.predecessors(n.id);
196 return getRanRu(g.node(pred[0]));
197 })(node);
198
199 // get data plane nodes for this slice (need them to get the corresponding control plane)
200 const dp_nodes = this.getDataPlaneForSlice(ranRu, sliceId);
201 // get control plane nodes for this slice
202 const cp_nodes = this.getControlPlaneForSlice(dp_nodes, sliceId);
203
204 const links = this.getGraphLinks(cp_nodes);
205
206 // add a close button
207 let closeButton = {
208 name: 'Close',
209 id: `close-button-${sliceId}`,
210 type: 'button',
211 sliceId: sliceId
212 };
213 closeButton = NodePositioner.getControlPlaneNodePos(closeButton, cp_nodes[3]);
214 cp_nodes.push(closeButton);
215
216 return [cp_nodes, links];
217 };
218
219 this.removeActiveSlice = sliceId => {
220 // nodes are remove from the d3 nodes identified by id
221 this.activeSlices.splice(this.activeSlices.indexOf(sliceId), 1);
222 };
223
224 })
225 .service('NodePositioner', function(_, sliceElOrder){
226
227 let el;
228
229 this.storeEl = (_el) => {
230 el = _el;
231 };
232
233 this.getHpos = (node, el) => {
234 let elPos = sliceElOrder.indexOf(node.type) + 1;
235
236 // hardcoded
237 if(node.type === 'mme'){
238 elPos = sliceElOrder.indexOf('sgw') + 1
239 }
240 if(node.type === 'button'){
241 elPos = sliceElOrder.indexOf('pgw') + 1
242 }
243 let x = (el.clientWidth / (sliceElOrder.length + 1)) * elPos;
244 return x;
245 };
246
247 this.getVpos = (node) => {
248 // calculate the available space to distribute items
249 const availableSpace = node.position.bottom - node.position.top;
250
251 // calculate the distance between each item
252 const step = availableSpace / (node.position.total + 1);
253
254 // vertical position
255 const y = (step * node.position.index) + node.position.top;
256 return y;
257 };
258
259 // for nodes that are part of the data plane
260 this.getDataPlaneNodePos = (node) => {
261 const x = this.getHpos(node, el);
262 const y = this.getVpos(node);
263 node.x = x;
264 node.y = y;
265 node.transform = `translate(${x}, ${y})`;
266 node.fixed = true;
267 return node;
268 };
269
270 // control element nodes are positioned relatively to their corresponding data plane node
271 this.getControlPlaneNodePos = (cp_node, dp_node) => {
272 const x = this.getHpos(cp_node, el);
273 const y = dp_node.y - 75;
274 cp_node.x = x;
275 cp_node.y = y;
276 cp_node.transform = `translate(${x}, ${y})`;
277 cp_node.fixed = true;
278 return cp_node;
279 };
280
281 })
282 .value('sliceElOrder', [
283 'ue',
284 'profile',
285 'ran-ru',
286 'ran-cu',
287 'sgw',
288 'pgw',
289 'upstream'
290 ]);
291})();