Merge "All TCP & UDP SG Rules : Implementation and Unit tests added."
[netvirt.git] / ovsdb-ui / module / src / main / resources / ovsdb / ovsdb.services.js
1 /*
2  * Copyright (c) 2015 Inocybe Technologies and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 define(['app/ovsdb/ovsdb.module', 'app/ovsdb/OvsCore', 'underscore', 'app/ovsdb/ovsdb.constant'], function(ovsdb, OvsCore, _) {
9   'use strict';
10
11   ovsdb.register.factory('OvsdbRestangular', ['Restangular', 'ENV', function(Restangular, ENV) {
12     return Restangular.withConfig(function(RestangularConfig) {
13       RestangularConfig.setBaseUrl(ENV.getBaseURL("MD_SAL"));
14     });
15   }]);
16
17   // nbv2 support depricated in dlux
18   ovsdb.register.factory('NeutronRestangular', ['Restangular', function(Restangular) {
19     return Restangular.withConfig(function(RestangularConfig) {
20       RestangularConfig.setBaseUrl('http://localhost:8080/controller/nb/v2/neutron');
21     });
22   }]);
23
24   ovsdb.register.factory('CacheFactory', function($q) {
25     var svc = {},
26       ovsCache = {};
27     /*BUG : Using the persistant cache make the physical
28      * graph links to stop reacting with the force layout
29      * algorithm. The current behavior is to use the cache
30      * only the pile up the datas.
31      */
32     svc.obtainDataFromCache = function(key, fn, ctx) {
33       var cacheDefer = $q.defer();
34
35       if (angular.isUndefined(ovsCache[key])) {
36         fn.call(ctx, function(data) {
37             ovsCache[key] = {
38               obj : data,
39               timestamp : Date.now() + 2000//300000 // 5 mintues
40             };
41             cacheDefer.resolve(data);
42         });
43       } else {
44         var cacheObj = ovsCache[key];
45         if (cacheObj.timestamp < Date.now() ||  _.isEmpty(cacheObj.obj)) {
46           fn.call(ctx, function(data) {
47               ovsCache[key] = {
48                 obj : data,
49                 timestamp : Date.now() + 2000//300000 // 5 mintues
50               };
51               cacheDefer.resolve(data);
52           });
53         } else {
54           cacheDefer.resolve(cacheObj.obj);
55         }
56       }
57
58       return cacheDefer.promise;
59     };
60
61     svc.getCacheObj = function(key) {
62       if (angular.isUndefined(ovsCache[key])) {
63         ovsCache[key] = {};
64       }
65       return ovsCache[key];
66     };
67
68     return svc;
69   });
70
71   var TopologySvc = function(OvsdbRestangular, nodeIdentifier, ovsNodeKeys, bridgeNodeKeys, tpKeys, flowInfoKeys, linkIdentifier, $q, $http, CacheFactory) {
72     var svc = {
73       base: function(type) {
74         return OvsdbRestangular.one('restconf').one(type);
75       }
76     };
77
78     function parseOvsdbNode(node) {
79       var inetMgr = '',
80         inetNode = '',
81         otherLocalIp = '',
82         otherInfo = null,
83         connectionInfo = null;
84
85       connectionInfo = node[ovsNodeKeys.CONNECTION_INFO];
86       otherInfo = node[ovsNodeKeys.OTHER_CONFIG];
87
88       if (_.isObject(connectionInfo)) {
89         inetMgr = connectionInfo[ovsNodeKeys.LOCAL_IP] + ':' + connectionInfo[ovsNodeKeys.LOCAL_PORT];
90         inetNode = connectionInfo[ovsNodeKeys.REMOTE_IP] + ':' + connectionInfo[ovsNodeKeys.REMOTE_PORT];
91       }
92
93       if (_.isArray(otherInfo)) {
94         _.each(otherInfo, function(value) {
95           if (value[ovsNodeKeys.OTHER_CONFIG_KEY] === 'local_ip') {
96             otherLocalIp = value[ovsNodeKeys.OTHER_CONFIG_VALUE];
97           }
98         });
99       }
100
101       return new OvsCore.OvsNode(node[ovsNodeKeys.NODE_ID], inetMgr, inetNode, otherLocalIp, node[ovsNodeKeys.OVS_VERSION]);
102     }
103
104     function parseBridgeNode(node) {
105       var bridgeNode = null,
106         controllerTarget = '',
107         controllerConnected = false,
108         tp = node[bridgeNodeKeys.TP],
109         controllerEntries = node[bridgeNodeKeys.CONTROLLER_ENTRY];
110
111       _.each(controllerEntries, function(value) {
112         controllerTarget = value[bridgeNodeKeys.TARGET];
113         controllerEntries = value[bridgeNodeKeys.IS_CONNECTED];
114         return false; // break the anonymus function
115       });
116
117       bridgeNode = new OvsCore.BridgeNode(node[bridgeNodeKeys.NODE_ID], node[bridgeNodeKeys.DATA_PATH], node[bridgeNodeKeys.BRIDGE_NAME], controllerTarget, controllerConnected);
118
119       _.each(tp, function(value) {
120         var tp = parseBridgeTP(value);
121
122         if (tp.ofPort == '65534' && (tp.name === 'br-ex' || tp.name === 'br-int')) {
123           return;
124         } else {
125           bridgeNode.addTerminationPoint(tp);
126         }
127
128       });
129
130       return bridgeNode;
131     }
132
133     function parseBridgeTP(tp) {
134       var mac = '',
135         ifaceId = '',
136         extInfo = tp['ovsdb:port-external-ids'] || tp['ovsdb:interface-external-ids'];
137
138       _.each(extInfo, function(ext) {
139         if (ext[tpKeys.EXTERNAL_KEY_ID] === tpKeys.ATTACHED_MAC) {
140           mac = ext[tpKeys.EXTERNAL_KEY_VALUE];
141         }
142         if (ext[tpKeys.EXTERNAL_KEY_ID] === tpKeys.IFACE_ID) {
143           ifaceId = ext[tpKeys.EXTERNAL_KEY_VALUE] || '';
144         }
145       });
146
147       return new OvsCore.TerminationPoint(tp[tpKeys.NAME], tp[tpKeys.OF_PORT], tp[tpKeys.INTERFACE_TYPE], mac, ifaceId);
148     }
149
150     function fetchTopology(cb) {
151       var invNodeDefer = this.base('operational').one('opendaylight-inventory:nodes').getList();
152       var netTopoDefer = this.base('operational').one('network-topology:network-topology').getList();
153
154       // be sure all data are loaded
155       $q.all([invNodeDefer, netTopoDefer]).then(function(values) {
156           var invNode = values[0],
157             netTopo = values[1],
158             index_hash = [],
159             i = 0;
160
161           // check if the data look fine in network topology
162           if (!netTopo || !netTopo['network-topology'] || !netTopo['network-topology'].topology) {
163             throw new Error('Invalid json format while parsing network-topology');
164           }
165
166           // check if the data look fine in inventory node
167           if (!invNode || !invNode.nodes || !invNode.nodes.node) {
168             throw new Error('Invalid JSON format while parsing inventory-node');
169           }
170
171           // get all topologies and start looping
172           var topologies = netTopo['network-topology'].topology,
173             nodes = invNode.nodes.node,
174             topo = new OvsCore.Topology();
175
176           _.each(topologies, function(topology, topo_index) {
177             if (!topology.hasOwnProperty('topology-id')) {
178               throw new Error('Invalide JSON format, no topology-id for the topology [' + topo_index + ']');
179             }
180
181             // if there no node it will be an empty array so noop
182             (topology.node || []).forEach(function(node) {
183               if (!node[nodeIdentifier.ID]) {
184                 throw new Error('Unexpected node : undefined ' + nodeIdentifier.ID + ' key');
185               }
186               index_hash[node[nodeIdentifier.ID]] = i++;
187
188               if (node['ovsdb:bridge-name']) {
189                 //bridge Node
190                 topo.registerBridgeNode(parseBridgeNode(node));
191               } else if (node['ovsdb:connection-info']) {
192                 // obsvdb Node
193                 topo.registerOvsdbNode(parseOvsdbNode(node));
194               }
195             });
196
197             // if there no link it will be an empty array so noop
198             (topology.link || []).forEach(function(link) {
199
200               var source = link[linkIdentifier.SRC]['source-node'],
201                 dest = link[linkIdentifier.DEST]['dest-node'];
202
203               topo.registerLink(new OvsCore.Link(link[linkIdentifier.ID], source, dest));
204             });
205
206           });
207
208           _.each(nodes, function(node, index) {
209             if (!node.id) {
210               return;
211             }
212
213             var bridgeId = node.id;
214
215             var bridgeNode = _.filter(topo.bridgeNodes, function(bridgeNode) {
216               return bridgeNode.getFLowName() === bridgeId;
217             })[0];
218
219             // match info for bridge node
220             if (bridgeNode) {
221               bridgeNode.flowInfo.features = node[flowInfoKeys.FEATURE];
222               bridgeNode.flowInfo.software = node[flowInfoKeys.SOFTWARE];
223               bridgeNode.flowInfo.hardware = node[flowInfoKeys.HARDWARE];
224               bridgeNode.flowInfo.manufacturer = node[flowInfoKeys.MANUFACTURER];
225               bridgeNode.flowInfo.ip = node[flowInfoKeys.IP];
226
227               _.each(node[flowInfoKeys.TABLE], function(entry) {
228                 if (!_.isUndefined(entry.id)) {
229                     _.each(entry.flow, function(flow) {
230                     bridgeNode.addFlowTableInfo({ key: flow.table_id, value: flow.id});
231                   });
232                 }
233               });
234             }
235           });
236
237           // show relation between ovsNode and switch with a link
238           _.each(topo.ovsdbNodes, function(node, index) {
239             var bridges = _.filter(topo.bridgeNodes, function(bnode) {
240               return bnode.nodeId.indexOf(node.nodeId) > -1;
241             });
242             _.each(bridges, function(bridge) {
243               var size = _.size(topo.links),
244                 link = new OvsCore.BridgeOvsLink(++size, node.nodeId, bridge.nodeId);
245               topo.registerLink(link);
246             });
247           });
248
249           function findVxlan(bridgeNode) {
250             var tunnels = [];
251
252             _.each(bridgeNode, function(node) {
253               var ovsdbNode = _.find(topo.ovsdbNodes, function(oNode) {
254                 return node.nodeId.indexOf(oNode.nodeId) > -1;
255               });
256               if (!ovsdbNode) {
257                 return false;
258               }
259               _.each(node.tPs, function(tp, index) {
260                 if (tp.name.indexOf('vxlan-') > -1) {
261                   tunnels.push({
262                     port : tp,
263                     bridge : node,
264                     ovsIp : ovsdbNode.otherLocalIp || ovsdbNode.inetMgr
265                   });
266                 }
267               });
268             });
269
270             return tunnels;
271           }
272
273           // extract all tunnel paired with their bridge
274           var tunnels = findVxlan(topo.bridgeNodes);
275           // loop over all pairs
276           _.each(tunnels, function(tunnel, index) {
277             var currIp = tunnel.ovsIp,
278               destIp = tunnel.port.name.replace('vxlan-', ''),
279               linkedBridge = _.find(tunnels.slice(index), function(t) {
280                 var vxlanIp =  t.port.name.replace('vxlan-', '');
281                 return vxlanIp === currIp;
282               });
283
284               if (linkedBridge) {
285                   topo.registerLink(new OvsCore.TunnelLink(tunnel.port.name + linkedBridge.port.name, tunnel.bridge.nodeId, linkedBridge.bridge.nodeId));
286               }
287           });
288
289           topo.updateLink();
290           cb(topo);
291         },
292         function(err) {
293           throw err;
294         }
295       );
296     }
297
298     svc.getTopologies = function() {
299       return CacheFactory.obtainDataFromCache('topologies', fetchTopology, this);
300     };
301
302     return svc;
303   };
304   TopologySvc.$inject = ['OvsdbRestangular', 'nodeIdentifier', 'ovsNodeKeys', 'bridgeNodeKeys', 'tpKeys', 'flowInfoKeys', 'linkIdentifier', '$q', '$http', 'CacheFactory'];
305
306   var NeutronSvc = function(NeutronRestangular, CacheFactory, $q, $http) {
307     var svc = {
308       base: function(type) {
309         return NeutronRestangular.one(type);
310       }
311     },
312     tenant_hash = {};
313
314     function fetchSubNetworks(cb) {
315       var subnetskDefer = svc.base('subnets').getList();
316       subnetskDefer.then(function(data) {
317         var subnets = data,
318           subnetHash = {};
319
320         if (!subnets || !subnets.subnets) {
321           throw new Error('Invalid format from neutron subnets');
322         }
323
324         _.each(subnets.subnets, function(subnet) {
325             if (!subnetHash[subnet.network_id]) {
326               subnetHash[subnet.network_id] = [];
327             }
328             tenant_hash[subnet.tenant_id] = {};
329             subnetHash[subnet.network_id].push(new OvsCore.Neutron.SubNet(
330               subnet.id,
331               subnet.network_id,
332               subnet.name,
333               subnet.ip_version,
334               subnet.cidr,
335               subnet.gateway_ip,
336               subnet.tenant_id
337             ));
338         });
339         cb(subnetHash);
340       });
341     }
342
343     function fetchNetworks(cb) {
344       var networkDefer = svc.base('networks').getList();
345       var subnetskDefer = svc.getSubNets();
346
347       $q.all([subnetskDefer, networkDefer]).then(function(datas) {
348         var subnetsHash = datas[0],
349           networks = datas[1],
350           networkArray = [];
351
352         if (!networks || !networks.networks) {
353           throw new Error('Invalid format from neutron networks');
354         }
355
356         _.each(networks.networks, function(network) {
357           var net = new OvsCore.Neutron.Network(
358             network.id,
359             network.name,
360             network.shared,
361             network.status,
362             network['router:external'],
363             network.tenant_id
364           );
365           tenant_hash[net.tenantId] = {};
366           net.addSubNets(subnetsHash[net.id]);
367           networkArray.push(net);
368         });
369           cb(networkArray);
370       });
371     }
372
373     function fetchRouters(cb) {
374       var routerDefer = svc.base('routers').getList();
375       routerDefer.then(function(data) {
376         var routers = data.routers,
377           routerArray = [];
378
379         if (!routers) {
380           throw new Error('Invalid format from neutron routers');
381         }
382         _.each(routers, function(router) {
383             var id = router.id,
384             name = router.name,
385             status = router.status,
386             tenantId = router.tenant_id,
387             extGateWayInfo = router.external_gateway_info;
388             tenant_hash[tenantId] = {};
389             routerArray.push(new OvsCore.Neutron.Router(
390               id, name, status, tenantId, extGateWayInfo
391             ));
392         });
393         cb(routerArray);
394       });
395     }
396
397     function fetchPorts(cb) {
398       var portDefer = svc.base('ports').getList();
399       portDefer.then(function(data){
400         var ports = data.ports,
401           portArray = [];
402
403         if (!ports) {
404           throw new Error('Invalid format from neutron ports');
405         }
406         _.each(ports, function(port) {
407           tenant_hash[port.tenant_id] = {};
408           portArray.push(new OvsCore.Neutron.Port(
409             port.id,
410             port.network_id,
411             port.name,
412             port.tenant_id,
413             port.device_id,
414             port.device_owner,
415             port.fixed_ips,
416             port.mac_address
417           ));
418         });
419         cb(portArray);
420       });
421     }
422
423     function fetchFloatingIps(cb) {
424       var floatingIpDefer = svc.base('floatingips').getList();
425       floatingIpDefer.then(function(data) {
426         var floatingIps = data.floatingips,
427           floatingIpArray = [];
428
429         if (!floatingIps) {
430           throw new Error('Invalid format from neutron floatingIps');
431         }
432
433         _.each(floatingIps, function(fIp) {
434           tenant_hash[fIp.tenant_id] = {};
435           floatingIpArray.push(new OvsCore.Neutron.FloatingIp(
436             fIp.id,
437             fIp.floating_network_id,
438             fIp.port_id,
439             fIp.fixed_ip_address,
440             fIp.floating_ip_address,
441             fIp.tenant_id,
442             fIp.status
443           ));
444         });
445
446         cb(floatingIpArray);
447       });
448     }
449
450     svc.getNetworks = function() {
451         return CacheFactory.obtainDataFromCache('networks', fetchNetworks, this);
452     };
453
454     svc.getSubNets = function() {
455       return CacheFactory.obtainDataFromCache('subnet', fetchSubNetworks, this);
456     };
457
458     svc.getPorts = function() {
459       return CacheFactory.obtainDataFromCache('ports', fetchPorts, this);
460     };
461
462     svc.getRouters = function() {
463       return CacheFactory.obtainDataFromCache('routers', fetchRouters, this);
464     };
465
466     svc.getFloatingIps = function() {
467       return CacheFactory.obtainDataFromCache('floatingips', fetchFloatingIps, this);
468     };
469
470     svc.getAllTenants = function() {
471       return Object.keys(tenant_hash);
472     };
473
474     return svc;
475   };
476   NeutronSvc.$inject = ['NeutronRestangular', 'CacheFactory', '$q', '$http'];
477
478   var OvsUtil = function(NeutronSvc, TopologySvc, CacheFactory, $q) {
479     var svc = {};
480
481     function findOvsdbNodeForBridge(ovsdbNodes, bridge) {
482       return _.find(ovsdbNodes, function(node) {
483         return bridge.nodeId.indexOf(node.nodeId) > -1;
484       });
485     }
486
487     function pileUpTopologyData(cb) {
488       var networksDefer = NeutronSvc.getNetworks(),
489         routersDefer = NeutronSvc.getRouters(),
490         portsDefer = NeutronSvc.getPorts(),
491         floatingDefer = NeutronSvc.getFloatingIps(),
492         netTopoDefer = TopologySvc.getTopologies();
493
494       $q.all([networksDefer, routersDefer, portsDefer, floatingDefer, netTopoDefer]).then(function (datas) {
495         var networks = datas[0],
496          routers = datas[1],
497          ports = datas[2],
498          floatingIps = datas[3],
499          topo = datas[4];
500
501         // match ports with elements
502          _.each(ports, function(port) {
503            port.topoInfo = [];
504            // corelate port.topoInfo data with network topology termination point
505            _.each(topo.bridgeNodes, function(bridge) {
506              _.each(bridge.tPs, function(tp) {
507                if (tp.ifaceId === port.id) {
508                  port.topoInfo.push({
509                    name : tp.name,
510                    ofPort : tp.ofPort,
511                    mac : bridge.dpIp,
512                    bridge : bridge,
513                    ovsNode : findOvsdbNodeForBridge(topo.ovsdbNodes, bridge)
514                  });
515                }
516              });
517            });
518
519            switch(port.deviceOwner) {
520              case 'network:router_gateway':
521              case 'network:router_interface':
522               var router = _.find(routers, function(r) { return r.id === port.deviceId; });
523               if (router) {
524                 router.interfaces.push({
525                   id: port.id,
526                   networkId : port.networkId,
527                   ip : port.fixed_ips[0],
528                   mac : port.mac,
529                   type: port.deviceOwner.replace('network:', ''),
530                   tenantId : port.tenantId,
531                   topoInfo: port.topoInfo
532                 });
533               }
534               break;
535             case 'compute:None':
536             case 'compute:nova':
537             case 'network:dhcp':
538               var network = _.find(networks, function(n) { return n.id === port.networkId;}),
539                 inst = null;
540
541               if (network) {
542                 inst = new OvsCore.Neutron.Instance(port.id,  port.networkId,
543                   port.name, port.fixed_ips[0].ip_address, port.mac,
544                   port.deviceOwner, port.tenantId, port.topoInfo );
545
546                 inst.extractFloatingIps(floatingIps);
547                 network.instances.push(inst);
548               }
549               break;
550            }
551
552          });
553
554          // find all routers for a specific network
555          _.each(networks, function(network) {
556            network.routers = _.filter(routers, function(router) {
557              return network.id === router.externalGateway.network_id;
558            });
559
560            // order instance by ip
561            network.instances.sort(function(a, b) {
562              var ipA = a.ip.slice(a.ip.lastIndexOf('.') + 1),
563                ipB = b.ip.slice(b.ip.lastIndexOf('.') + 1);
564                return ipA - ipB;
565            });
566          });
567
568          cb(networks);
569       });
570     }
571
572     svc.getLogicalTopology = function() {
573       return CacheFactory.obtainDataFromCache('logicalTopology', pileUpTopologyData, this);
574     };
575
576     svc.extractLogicalByTenant = function(tenantId, subSet) {
577       var lTopoDefer = svc.getLogicalTopology(),
578         resultDefer = $q.defer();
579       lTopoDefer.then(function() {
580         var ports = CacheFactory.getCacheObj('ports').obj,
581           filteredPorts = _.filter(ports, function(p) {
582             return p.tenantId === tenantId;
583           });
584
585         if (!_.isEmpty(filteredPorts)) {
586           var bridgeHash = {};
587           _.each(filteredPorts, function(p) {
588             if (!_.isEmpty(p.topoInfo) && !bridgeHash[p.topoInfo[0].bridge.nodeId]) {
589               bridgeHash[p.topoInfo[0].bridge.nodeId] = {};
590             }
591           });
592           var ovsdbHash = {};
593           _.each(filteredPorts, function(p) {
594             if (!_.isEmpty(p.topoInfo) && !ovsdbHash[p.topoInfo[0].ovsNode.nodeId]) {
595               ovsdbHash[p.topoInfo[0].ovsNode.nodeId] = {};
596             }
597           });
598
599           resultDefer.resolve([Object.keys(bridgeHash), Object.keys(ovsdbHash)]);
600         } else {
601           resultDefer.resolve([], []);
602         }
603       });
604       return resultDefer.promise;
605     };
606
607     svc.extractLogicalBySubnet = function(subnets, subSet) {
608       var lTopoDefer = svc.getLogicalTopology(),
609         resultDefer = $q.defer();
610       lTopoDefer.then(function() {
611         var ports = CacheFactory.getCacheObj('ports').obj,
612           networks = CacheFactory.getCacheObj('networks').obj;
613
614         var filteredPorts = _.filter(ports, function(p) {
615           var net = _.find(networks, function(d) {
616             return d.id === p.networkId;
617           });
618
619           return net.asSubnet(subnets);
620         });
621         if (!_.isEmpty(filteredPorts)) {
622           var bridgeHash = {};
623           _.each(filteredPorts, function(p) {
624             if (!_.isEmpty(p.topoInfo) && !bridgeHash[p.topoInfo[0].bridge.nodeId]) {
625               bridgeHash[p.topoInfo[0].bridge.nodeId] = {};
626             }
627           });
628           var ovsdbHash = {};
629           _.each(filteredPorts, function(p) {
630             if (!_.isEmpty(p.topoInfo) && !ovsdbHash[p.topoInfo[0].ovsNode.nodeId]) {
631               ovsdbHash[p.topoInfo[0].ovsNode.nodeId] = {};
632             }
633           });
634           resultDefer.resolve([Object.keys(bridgeHash), Object.keys(ovsdbHash)]);
635         } else {
636           resultDefer.resolve([], []);
637         }
638       });
639       return resultDefer.promise;
640     };
641
642     return svc;
643   };
644
645   OvsUtil.$inject = ['NeutronSvc', 'TopologySvc', 'CacheFactory', '$q'];
646
647   ovsdb.register.factory('TopologySvc', TopologySvc);
648   ovsdb.register.factory('NeutronSvc', NeutronSvc);
649   ovsdb.register.factory('OvsUtil', OvsUtil);
650 });