Merge pull request #99 from stevenvanrossem/master
[osm/vim-emu.git] / src / emuvim / dcemulator / net.py
index f7fafdf..7f31a46 100755 (executable)
@@ -9,10 +9,10 @@ import time
 from subprocess import Popen
 import os
 import re
 from subprocess import Popen
 import os
 import re
+import urllib2
+from functools import partial
 
 
-
-
-from mininet.net import Dockernet
+from mininet.net import Containernet
 from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
 from mininet.cli import CLI
 from mininet.link import TCLink
 from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
 from mininet.cli import CLI
 from mininet.link import TCLink
@@ -21,9 +21,9 @@ from emuvim.dcemulator.monitoring import DCNetworkMonitor
 from emuvim.dcemulator.node import Datacenter, EmulatorCompute
 from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
 
 from emuvim.dcemulator.node import Datacenter, EmulatorCompute
 from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar
 
-class DCNetwork(Dockernet):
+class DCNetwork(Containernet):
     """
     """
-    Wraps the original Mininet/Dockernet class and provides
+    Wraps the original Mininet/Containernet class and provides
     methods to add data centers, switches, etc.
 
     This class is used by topology definition scripts.
     methods to add data centers, switches, etc.
 
     This class is used by topology definition scripts.
@@ -34,7 +34,7 @@ class DCNetwork(Dockernet):
                  dc_emulation_max_mem=512,  # emulation max mem in MB
                  **kwargs):
         """
                  dc_emulation_max_mem=512,  # emulation max mem in MB
                  **kwargs):
         """
-        Create an extended version of a Dockernet network
+        Create an extended version of a Containernet network
         :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
         :param kwargs: path through for Mininet parameters
         :return:
         :param dc_emulation_max_cpu: max. CPU time used by containers in data centers
         :param kwargs: path through for Mininet parameters
         :return:
@@ -42,7 +42,7 @@ class DCNetwork(Dockernet):
         self.dcs = {}
 
         # call original Docker.__init__ and setup default controller
         self.dcs = {}
 
         # call original Docker.__init__ and setup default controller
-        Dockernet.__init__(
+        Containernet.__init__(
             self, switch=OVSKernelSwitch, **kwargs)
 
         # Ryu management
             self, switch=OVSKernelSwitch, **kwargs)
 
         # Ryu management
@@ -60,6 +60,11 @@ class DCNetwork(Dockernet):
         # initialize pool of vlan tags to setup the SDN paths
         self.vlans = range(4096)[::-1]
 
         # initialize pool of vlan tags to setup the SDN paths
         self.vlans = range(4096)[::-1]
 
+        # link to Ryu REST_API
+        ryu_ip = '0.0.0.0'
+        ryu_port = '8080'
+        self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)
+
         # monitoring agent
         if monitor:
             self.monitor_agent = DCNetworkMonitor(self)
         # monitoring agent
         if monitor:
             self.monitor_agent = DCNetworkMonitor(self)
@@ -116,11 +121,11 @@ class DCNetwork(Dockernet):
                 params["params2"]["ip"] = self.getNextIp()
         # ensure that we allow TCLinks between data centers
         # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
                 params["params2"]["ip"] = self.getNextIp()
         # ensure that we allow TCLinks between data centers
         # TODO this is not optimal, we use cls=Link for containers and TCLink for data centers
-        # see Dockernet issue: https://github.com/mpeuster/dockernet/issues/3
+        # see Containernet issue: https://github.com/mpeuster/containernet/issues/3
         if "cls" not in params:
             params["cls"] = TCLink
 
         if "cls" not in params:
             params["cls"] = TCLink
 
-        link = Dockernet.addLink(self, node1, node2, **params)
+        link = Containernet.addLink(self, node1, node2, **params)
 
         # try to give container interfaces a default id
         node1_port_id = node1.ports[link.intf1]
 
         # try to give container interfaces a default id
         node1_port_id = node1.ports[link.intf1]
@@ -138,7 +143,7 @@ class DCNetwork(Dockernet):
 
         # add edge and assigned port number to graph in both directions between node1 and node2
         # port_id: id given in descriptor (if available, otherwise same as port)
 
         # add edge and assigned port number to graph in both directions between node1 and node2
         # port_id: id given in descriptor (if available, otherwise same as port)
-        # port: portnumber assigned by Dockernet
+        # port: portnumber assigned by Containernet
 
         attr_dict = {}
         # possible weight metrics allowed by TClink class:
 
         attr_dict = {}
         # possible weight metrics allowed by TClink class:
@@ -175,14 +180,14 @@ class DCNetwork(Dockernet):
         Wrapper for addDocker method to use custom container class.
         """
         self.DCNetwork_graph.add_node(label)
         Wrapper for addDocker method to use custom container class.
         """
         self.DCNetwork_graph.add_node(label)
-        return Dockernet.addDocker(self, label, cls=EmulatorCompute, **params)
+        return Containernet.addDocker(self, label, cls=EmulatorCompute, **params)
 
     def removeDocker( self, label, **params ):
         """
         Wrapper for removeDocker method to update graph.
         """
         self.DCNetwork_graph.remove_node(label)
 
     def removeDocker( self, label, **params ):
         """
         Wrapper for removeDocker method to update graph.
         """
         self.DCNetwork_graph.remove_node(label)
-        return Dockernet.removeDocker(self, label, **params)
+        return Containernet.removeDocker(self, label, **params)
 
     def addSwitch( self, name, add_to_graph=True, **params ):
         """
 
     def addSwitch( self, name, add_to_graph=True, **params ):
         """
@@ -190,7 +195,7 @@ class DCNetwork(Dockernet):
         """
         if add_to_graph:
             self.DCNetwork_graph.add_node(name)
         """
         if add_to_graph:
             self.DCNetwork_graph.add_node(name)
-        return Dockernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
+        return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
 
     def getAllContainers(self):
         """
 
     def getAllContainers(self):
         """
@@ -205,7 +210,7 @@ class DCNetwork(Dockernet):
         # start
         for dc in self.dcs.itervalues():
             dc.start()
         # start
         for dc in self.dcs.itervalues():
             dc.start()
-        Dockernet.start(self)
+        Containernet.start(self)
 
     def stop(self):
 
 
     def stop(self):
 
@@ -214,7 +219,7 @@ class DCNetwork(Dockernet):
             self.monitor_agent.stop()
 
         # stop emulator net
             self.monitor_agent.stop()
 
         # stop emulator net
-        Dockernet.stop(self)
+        Containernet.stop(self)
 
         # stop Ryu controller
         self.stopRyu()
 
         # stop Ryu controller
         self.stopRyu()
@@ -292,10 +297,11 @@ class DCNetwork(Dockernet):
         switch_inport_nr = src_sw_inport_nr
 
         # choose free vlan if path contains more than 1 switch
         switch_inport_nr = src_sw_inport_nr
 
         # choose free vlan if path contains more than 1 switch
-        if len(path) > 1:
-            vlan = self.vlans.pop()
-        else:
-            vlan = None
+        cmd = kwargs.get('cmd')
+        vlan = None
+        if cmd == 'add-flow':
+            if len(path) > 1:
+                vlan = self.vlans.pop()
 
         for i in range(0,len(path)):
             current_node = self.getNodeByName(current_hop)
 
         for i in range(0,len(path)):
             current_node = self.getNodeByName(current_hop)
@@ -325,15 +331,80 @@ class DCNetwork(Dockernet):
                 kwargs['vlan'] = vlan
                 kwargs['path'] = path
                 kwargs['current_hop'] = current_hop
                 kwargs['vlan'] = vlan
                 kwargs['path'] = path
                 kwargs['current_hop'] = current_hop
-                self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
-                # TODO set entry via Ryu REST api (in case emulator is running remote...)
+                ## set flow entry via ovs-ofctl
+                #self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
+                ## set flow entry via ryu rest api
+                self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
 
             # take first link between switches by default
             if isinstance( next_node, OVSSwitch ):
                 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
                 current_hop = next_hop
 
 
             # take first link between switches by default
             if isinstance( next_node, OVSSwitch ):
                 switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
                 current_hop = next_hop
 
-        return "path added between {0} and {1}".format(vnf_src_name, vnf_dst_name)
+        return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
+
+    def _set_flow_entry_ryu_rest(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
+        match = 'in_port=%s' % switch_inport_nr
+
+        cookie = kwargs.get('cookie')
+        match_input = kwargs.get('match')
+        cmd = kwargs.get('cmd')
+        path = kwargs.get('path')
+        current_hop = kwargs.get('current_hop')
+        vlan = kwargs.get('vlan')
+
+        s = ','
+        if match_input:
+            match = s.join([match, match_input])
+
+        flow = {}
+        flow['dpid'] = int(node.dpid, 16)
+        if cookie:
+            flow['cookie'] = int(cookie)
+
+
+        flow['actions'] = []
+
+        # possible Ryu actions, match fields:
+        # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#add-a-flow-entry
+        if cmd == 'add-flow':
+            prefix = 'stats/flowentry/add'
+            action = {}
+            action['type'] = 'OUTPUT'
+            action['port'] = switch_outport_nr
+            flow['actions'].append(action)
+            if vlan != None:
+                if path.index(current_hop) == 0:  # first node
+                    action = {}
+                    action['type'] = 'PUSH_VLAN'  # Push a new VLAN tag if a input frame is non-VLAN-tagged
+                    action['ethertype'] = 33024   # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
+                    action['type'] = 'SET_FIELD'
+                    action['field'] = 'vlan_vid'
+                    action['value'] = vlan
+                    flow['actions'].append(action)
+                elif path.index(current_hop) == len(path) - 1:  # last node
+                    match += ',dl_vlan=%s' % vlan
+                    action = {}
+                    action['type'] = 'POP_VLAN'
+                    flow['actions'].append(action)
+                else:  # middle nodes
+                    match += ',dl_vlan=%s' % vlan
+            #flow['match'] = self._parse_match(match)
+        elif cmd == 'del-flows':
+            #del(flow['actions'])
+            prefix = 'stats/flowentry/delete'
+            if cookie:
+                flow['cookie_mask'] = cookie
+            #if cookie is None:
+            #    flow['match'] = self._parse_match(match)
+
+            action = {}
+            action['type'] = 'OUTPUT'
+            action['port'] = switch_outport_nr
+            flow['actions'].append(action)
+
+        flow['match'] = self._parse_match(match)
+        self.ryu_REST(prefix, data=flow)
 
     def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
         match = 'in_port=%s' % switch_inport_nr
 
     def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
         match = 'in_port=%s' % switch_inport_nr
@@ -394,3 +465,33 @@ class DCNetwork(Dockernet):
             self.ryu_process.terminate()
             self.ryu_process.kill()
 
             self.ryu_process.terminate()
             self.ryu_process.kill()
 
+    def ryu_REST(self, prefix, dpid=None, data=None):
+        if dpid:
+            url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)
+        else:
+            url = self.ryu_REST_api + '/' + str(prefix)
+        if data:
+            #logging.info('POST: {0}'.format(str(data)))
+            req = urllib2.Request(url, str(data))
+        else:
+            req = urllib2.Request(url)
+
+        ret = urllib2.urlopen(req).read()
+        return ret
+
+    # need to respect that some match fields must be integers
+    # http://ryu.readthedocs.io/en/latest/app/ofctl_rest.html#description-of-match-and-actions
+    def _parse_match(self, match):
+        matches = match.split(',')
+        dict = {}
+        for m in matches:
+            match = m.split('=')
+            if len(match) == 2:
+                try:
+                    m2 = int(match[1], 0)
+                except:
+                    m2 = match[1]
+
+                dict.update({match[0]:m2})
+        return dict
+