SDN chaining now via ryu rest api
authorstevenvanrossem <steven.vanrossem@intec.ugent.be>
Tue, 10 May 2016 14:37:57 +0000 (16:37 +0200)
committerstevenvanrossem <steven.vanrossem@intec.ugent.be>
Tue, 10 May 2016 14:37:57 +0000 (16:37 +0200)
src/emuvim/api/zerorpc/network.py
src/emuvim/cli/network.py
src/emuvim/dcemulator/monitoring.py
src/emuvim/dcemulator/net.py

index b5873ff..37b34f9 100644 (file)
@@ -97,7 +97,8 @@ class DCNetworkApi(object):
                 cmd='del-flows',
                 weight=kwargs.get('weight'),
                 match=kwargs.get('match'),
-                bidirectional=kwargs.get('bidirectional'))
+                bidirectional=kwargs.get('bidirectional'),
+                cookie=kwargs.get('cookie'))
             return c
         except Exception as ex:
             logging.exception("RPC error.")
index 911c5ef..53007cd 100755 (executable)
@@ -56,6 +56,7 @@ class ZeroRpcClient(object):
             vnf_dst_interface=self._parse_vnf_interface(args.get("destination")),\r
             weight=args.get("weight"),\r
             match=args.get("match"),\r
+            bidirectional=args.get("bidirectional"),\r
             cookie=args.get("cookie"))\r
 \r
         r = self.c.network_action_stop(\r
index ab77c39..762c947 100755 (executable)
@@ -23,10 +23,7 @@ class to read openflow stats from the Ryu controller of the DCNetwork
 class DCNetworkMonitor():\r
     def __init__(self, net):\r
         self.net = net\r
-        # link to Ryu REST_API\r
-        ryu_ip = '0.0.0.0'\r
-        ryu_port = '8080'\r
-        self.ryu_REST_api = 'http://{0}:{1}'.format(ryu_ip, ryu_port)\r
+\r
         prometheus_ip = '0.0.0.0'\r
         prometheus_port = '9090'\r
         self.prometheus_REST_api = 'http://{0}:{1}'.format(prometheus_ip, prometheus_port)\r
@@ -297,7 +294,7 @@ class DCNetworkMonitor():
 \r
 \r
                 # query Ryu\r
-                ret = self.REST_cmd('stats/flow', flow_dict['switch_dpid'], data=data)\r
+                ret = self.net.ryu_REST('stats/flow', dpid=flow_dict['switch_dpid'], data=data)\r
                 flow_stat_dict = ast.literal_eval(ret)\r
 \r
                 logging.info('received flow stat:{0} '.format(flow_stat_dict))\r
@@ -318,7 +315,7 @@ class DCNetworkMonitor():
             for dpid in dpid_set:\r
 \r
                 # query Ryu\r
-                ret = self.REST_cmd('stats/port', dpid)\r
+                ret = self.net.ryu_REST('stats/port', dpid=dpid)\r
                 port_stat_dict = ast.literal_eval(ret)\r
 \r
                 metric_list = [metric_dict for metric_dict in self.network_metrics\r
@@ -408,17 +405,6 @@ class DCNetworkMonitor():
         #logging.exception('metric {0} not found on {1}:{2}'.format(metric_key, vnf_name, vnf_interface))\r
         #return 'metric {0} not found on {1}:{2}'.format(metric_key, vnf_name, vnf_interface)\r
 \r
-    def REST_cmd(self, prefix, dpid, data=None):\r
-        url = self.ryu_REST_api + '/' + str(prefix) + '/' + str(dpid)\r
-        if data:\r
-            logging.info('POST: {0}'.format(str(data)))\r
-            req = urllib2.Request(url, str(data))\r
-        else:\r
-            req = urllib2.Request(url)\r
-\r
-        ret = urllib2.urlopen(req).read()\r
-        return ret\r
-\r
     def query_Prometheus(self, query):\r
         '''\r
         escaped_chars='{}[]'\r
index f7fafdf..248dc34 100755 (executable)
@@ -9,8 +9,8 @@ import time
 from subprocess import Popen
 import os
 import re
-
-
+import urllib2
+from functools import partial
 
 from mininet.net import Dockernet
 from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
@@ -60,6 +60,11 @@ class DCNetwork(Dockernet):
         # 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)
@@ -292,10 +297,11 @@ class DCNetwork(Dockernet):
         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)
@@ -325,15 +331,80 @@ class DCNetwork(Dockernet):
                 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
 
-        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
@@ -394,3 +465,33 @@ class DCNetwork(Dockernet):
             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
+