add SDN chaining unit test
authorstevenvanrossem <steven.vanrossem@intec.ugent.be>
Wed, 11 May 2016 20:55:15 +0000 (22:55 +0200)
committerstevenvanrossem <steven.vanrossem@intec.ugent.be>
Wed, 11 May 2016 20:55:15 +0000 (22:55 +0200)
src/emuvim/dcemulator/net.py
src/emuvim/examples/monitoring_demo_topology.py
src/emuvim/test/base.py
src/emuvim/test/test_emulator.py

index 248dc34..9ca75f7 100755 (executable)
@@ -30,6 +30,7 @@ class DCNetwork(Dockernet):
     """
 
     def __init__(self, controller=RemoteController, monitor=False,
+                 enable_learning = True,   # in case of RemoteController (Ryu), learning switch behavior can be turned off/on
                  dc_emulation_max_cpu=1.0,  # fraction of overall CPU time for emulation
                  dc_emulation_max_mem=512,  # emulation max mem in MB
                  **kwargs):
@@ -43,13 +44,13 @@ class DCNetwork(Dockernet):
 
         # call original Docker.__init__ and setup default controller
         Dockernet.__init__(
-            self, switch=OVSKernelSwitch, **kwargs)
+            self, switch=OVSKernelSwitch, controller=controller, **kwargs)
 
         # Ryu management
         self.ryu_process = None
         if controller == RemoteController:
             # start Ryu controller
-            self.startRyu()
+            self.startRyu(learning_switch=enable_learning)
 
         # add the specified controller
         self.addController('c0', controller=controller)
@@ -331,10 +332,15 @@ class DCNetwork(Dockernet):
                 kwargs['vlan'] = vlan
                 kwargs['path'] = path
                 kwargs['current_hop'] = current_hop
-                ## 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)
+
+                if self.controller == RemoteController:
+                    ## set flow entry via ryu rest api
+                    self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
+                else:
+                    ## set flow entry via ovs-ofctl
+                    self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
+
+
 
             # take first link between switches by default
             if isinstance( next_node, OVSSwitch ):
@@ -359,6 +365,8 @@ class DCNetwork(Dockernet):
 
         flow = {}
         flow['dpid'] = int(node.dpid, 16)
+        logging.info('node name:{0}'.format(node.name))
+
         if cookie:
             flow['cookie'] = int(cookie)
 
@@ -369,15 +377,13 @@ class DCNetwork(Dockernet):
         # 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
+                    flow['actions'].append(action)
+                    action = {}
                     action['type'] = 'SET_FIELD'
                     action['field'] = 'vlan_vid'
                     action['value'] = vlan
@@ -389,6 +395,11 @@ class DCNetwork(Dockernet):
                     flow['actions'].append(action)
                 else:  # middle nodes
                     match += ',dl_vlan=%s' % vlan
+            # output action must come last
+            action = {}
+            action['type'] = 'OUTPUT'
+            action['port'] = switch_outport_nr
+            flow['actions'].append(action)
             #flow['match'] = self._parse_match(match)
         elif cmd == 'del-flows':
             #del(flow['actions'])
@@ -444,7 +455,7 @@ class DCNetwork(Dockernet):
                                                                                  switch_outport_nr, cmd))
 
     # start Ryu Openflow controller as Remote Controller for the DCNetwork
-    def startRyu(self):
+    def startRyu(self, learning_switch=True):
         # start Ryu controller with rest-API
         python_install_path = site.getsitepackages()[0]
         ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
@@ -455,9 +466,11 @@ class DCNetwork(Dockernet):
         ryu_of_port = '6653'
         ryu_cmd = 'ryu-manager'
         FNULL = open("/tmp/ryu.log", 'w')
-        self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
-        # no learning switch
-        #self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
+        if learning_switch:
+            self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
+        else:
+            # no learning switch
+            self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
         time.sleep(1)
 
     def stopRyu(self):
@@ -466,18 +479,22 @@ class DCNetwork(Dockernet):
             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)
+        try:
+            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
+            ret = urllib2.urlopen(req).read()
+            return ret
+        except:
+            logging.info('error url: {0}'.format(str(url)))
+            if data: logging.info('error POST: {0}'.format(str(data)))
 
     # 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
index 9737609..4dfd5b7 100755 (executable)
@@ -29,7 +29,7 @@ def create_topology1():
     """
     1. Create a data center network object (DCNetwork) with monitoring enabled
     """
-    net = DCNetwork(monitor=True)
+    net = DCNetwork(monitor=True, enable_learning=False)
 
     """
     1b. add a monitoring agent to the DCNetwork
@@ -53,15 +53,17 @@ def create_topology1():
     3. You can add additional SDN switches for data center
        interconnections to the network.
     """
-    #s1 = net.addSwitch("s1")
+    s1 = net.addSwitch("s1")
 
     """
     4. Add links between your data centers and additional switches
        to define you topology.
        These links can use Mininet's features to limit bw, add delay or jitter.
     """
-    net.addLink(dc1, dc2, delay="10ms")
+    #net.addLink(dc1, dc2, delay="10ms")
     #net.addLink(dc1, dc2)
+    net.addLink(dc1, s1)
+    net.addLink(s1, dc2)
     #net.addLink("datacenter1", s1, delay="20ms")
     #net.addLink(s1, dc3)
     #net.addLink(s1, "datacenter4")
index 4bad515..9efb4ab 100644 (file)
@@ -28,7 +28,7 @@ class SimpleTestTopology(unittest.TestCase):
     def createNet(
             self,
             nswitches=0, ndatacenter=0, nhosts=0, ndockers=0,
-            autolinkswitches=False, controller=Controller):
+            autolinkswitches=False, controller=Controller, **kwargs):
         """
         Creates a Mininet instance and automatically adds some
         nodes to it.
@@ -37,7 +37,7 @@ class SimpleTestTopology(unittest.TestCase):
         for our tests. Only use other controllers if you want to test
         specific controller functionality.
         """
-        self.net = DCNetwork(controller=controller)
+        self.net = DCNetwork(controller=controller, **kwargs)
 
         # add some switches
         for i in range(0, nswitches):
index 2038116..0c387bf 100755 (executable)
@@ -10,6 +10,7 @@ import time
 import unittest
 from emuvim.dcemulator.node import EmulatorCompute
 from emuvim.test.base import SimpleTestTopology
+from mininet.node import RemoteController
 
 
 #@unittest.skip("disabled topology tests for development")
@@ -87,6 +88,54 @@ class testEmulatorTopology( SimpleTestTopology ):
         # stop Mininet network
         self.stopNet()
 
+class testEmulatorNetworking( SimpleTestTopology ):
+
+    def testSDNChaining(self):
+        """
+        Create a two data centers and interconnect them with additional
+        switches between them.
+        Uses Ryu SDN controller.
+        Connect the Docker hosts to different datacenters and setup the links between.
+        """
+        # create network
+        self.createNet(
+            nswitches=3, ndatacenter=2, nhosts=0, ndockers=0,
+            autolinkswitches=True,
+            controller=RemoteController,
+            enable_learning=False)
+        # setup links
+        self.net.addLink(self.dc[0], self.s[0])
+        self.net.addLink(self.s[2], self.dc[1])
+        # start Mininet network
+        self.startNet()
+
+        # add compute resources
+        vnf1 = self.dc[0].startCompute("vnf1", network=[{'id':'intf1', 'ip':'10.0.10.1/24'}])
+        vnf2 = self.dc[1].startCompute("vnf2", network=[{'id':'intf2', 'ip':'10.0.10.2/24'}])
+        # check number of running nodes
+        self.assertTrue(len(self.getDockernetContainers()) == 2)
+        self.assertTrue(len(self.net.hosts) == 2)
+        self.assertTrue(len(self.net.switches) == 5)
+        # check status
+        # check get status
+        s1 = self.dc[0].containers.get("vnf1").getStatus()
+        self.assertTrue(s1["name"] == "vnf1")
+        self.assertTrue(s1["state"]["Running"])
+        self.assertTrue(s1["network"][0]['intf_name'] == 'intf1')
+        self.assertTrue(s1["network"][0]['ip'] == '10.0.10.1')
+
+        s2 = self.dc[1].containers.get("vnf2").getStatus()
+        self.assertTrue(s2["name"] == "vnf2")
+        self.assertTrue(s2["state"]["Running"])
+        self.assertTrue(s2["network"][0]['intf_name'] == 'intf2')
+        self.assertTrue(s2["network"][0]['ip'] == '10.0.10.2')
+
+        # setup links
+        self.net.setChain('vnf1', 'vnf2', 'intf1', 'intf2', bidirectional=True, cmd='add-flow')
+        # check connectivity by using ping
+        self.assertTrue(self.net.ping([vnf1, vnf2]) <= 0.0)
+        # stop Mininet network
+        self.stopNet()
 
 #@unittest.skip("disabled compute tests for development")
 class testEmulatorCompute( SimpleTestTopology ):