Merge pull request #101 from stevenvanrossem/master
authorstevenvanrossem <steven.vanrossem@intec.ugent.be>
Wed, 11 May 2016 23:48:28 +0000 (01:48 +0200)
committerstevenvanrossem <steven.vanrossem@intec.ugent.be>
Wed, 11 May 2016 23:48:28 +0000 (01:48 +0200)
add unittest for SDN chaining + fix bug

28 files changed:
LICENSE [changed mode: 0644->0755]
misc/sonata-demo-docker.son [changed mode: 0644->0755]
setup.cfg [changed mode: 0644->0755]
setup.py [changed mode: 0644->0755]
src/emuvim/__init__.py [changed mode: 0644->0755]
src/emuvim/api/openstack/README.md [changed mode: 0644->0755]
src/emuvim/api/sonata/README.md [changed mode: 0644->0755]
src/emuvim/api/sonata/__init__.py [changed mode: 0644->0755]
src/emuvim/api/zerorpc/__init__.py [changed mode: 0644->0755]
src/emuvim/api/zerorpc/compute.py [changed mode: 0644->0755]
src/emuvim/api/zerorpc/network.py [changed mode: 0644->0755]
src/emuvim/conftest.py [changed mode: 0644->0755]
src/emuvim/dcemulator/net.py
src/emuvim/dcemulator/resourcemodel/__init__.py [changed mode: 0644->0755]
src/emuvim/dcemulator/resourcemodel/upb/__init__.py [changed mode: 0644->0755]
src/emuvim/dcemulator/resourcemodel/upb/simple.py [changed mode: 0644->0755]
src/emuvim/examples/monitoring_demo_topology.py
src/emuvim/examples/resource_model_demo_topology.py [changed mode: 0644->0755]
src/emuvim/examples/sonata_y1_demo_topology_1.py [changed mode: 0644->0755]
src/emuvim/test/__init__.py [changed mode: 0644->0755]
src/emuvim/test/base.py [changed mode: 0644->0755]
src/emuvim/test/integrationtests/__init__.py [changed mode: 0644->0755]
src/emuvim/test/unittests/__init__.py [changed mode: 0644->0755]
src/emuvim/test/unittests/test_emulator.py
src/emuvim/test/unittests/test_resourcemodel.py [changed mode: 0644->0755]
src/emuvim/test/unittests/test_sonata_dummy_gatekeeper.py [changed mode: 0644->0755]
utils/ci/junit-xml/.gitkeep [changed mode: 0644->0755]
utils/docker/Dockerfile [changed mode: 0644->0755]

diff --git a/LICENSE b/LICENSE
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index 7f31a46..3556535 100755 (executable)
@@ -30,6 +30,7 @@ class DCNetwork(Containernet):
     """
 
     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,14 @@ class DCNetwork(Containernet):
 
         # call original Docker.__init__ and setup default controller
         Containernet.__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 +333,15 @@ class DCNetwork(Containernet):
                 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 +366,7 @@ class DCNetwork(Containernet):
 
         flow = {}
         flow['dpid'] = int(node.dpid, 16)
+
         if cookie:
             flow['cookie'] = int(cookie)
 
@@ -369,15 +377,13 @@ class DCNetwork(Containernet):
         # 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,14 +395,19 @@ class DCNetwork(Containernet):
                     flow['actions'].append(action)
                 else:  # middle nodes
                     match += ',dl_vlan=%s' % vlan
-            #flow['match'] = self._parse_match(match)
+            # output action must come last
+            action = {}
+            action['type'] = 'OUTPUT'
+            action['port'] = switch_outport_nr
+            flow['actions'].append(action)
+
         elif cmd == 'del-flows':
-            #del(flow['actions'])
             prefix = 'stats/flowentry/delete'
+
+            # if cookie is given, only delete flows by cookie
+            # do not specify other match -> also other cookies can be matched
             if cookie:
-                flow['cookie_mask'] = cookie
-            #if cookie is None:
-            #    flow['match'] = self._parse_match(match)
+                flow['cookie_mask'] = int('0xffffffffffffffff', 16)  # need full mask to match complete cookie
 
             action = {}
             action['type'] = 'OUTPUT'
@@ -444,7 +455,7 @@ class DCNetwork(Containernet):
                                                                                  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(Containernet):
         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,23 @@ class DCNetwork(Containernet):
             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)
+        if data: logging.info('log POST: {0}'.format(str(data)))
+        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
old mode 100644 (file)
new mode 100755 (executable)
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")
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index 5a83547..0adc917
@@ -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,10 +37,12 @@ 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):
+        # start from s1 because ovs does not like to have dpid = 0
+        # and switch name-number is being used by mininet to set the dpid
+        for i in range(1, nswitches+1):
             self.s.append(self.net.addSwitch('s%d' % i))
         # if specified, chain all switches
         if autolinkswitches:
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index 243f050..80ac1d1 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,112 @@ class testEmulatorTopology( SimpleTestTopology ):
         # stop Mininet network
         self.stopNet()
 
+class testEmulatorNetworking( SimpleTestTopology ):
+
+    def testSDNChainingSingleService(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.getContainernetContainers()) == 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()
+
+    def testSDNChainingMultiService(self):
+        """
+        Create a two data centers and interconnect them with additional
+        switches between them.
+        Uses Ryu SDN controller.
+        Setup 2 services and setup isolated paths between them
+        Delete only the first service, and check that other one still works
+        """
+        # 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()
+
+        ## First Service
+        # 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'}])
+        # setup links
+        self.net.setChain('vnf1', 'vnf2', 'intf1', 'intf2', bidirectional=True, cmd='add-flow', cookie=1)
+        # check connectivity by using ping
+        self.assertTrue(self.net.ping([vnf1, vnf2]) <= 0.0)
+
+        ## Second Service
+        # add compute resources
+        vnf11 = self.dc[0].startCompute("vnf11", network=[{'id': 'intf1', 'ip': '10.0.20.1/24'}])
+        vnf22 = self.dc[1].startCompute("vnf22", network=[{'id': 'intf2', 'ip': '10.0.20.2/24'}])
+
+        # check number of running nodes
+        self.assertTrue(len(self.getContainernetContainers()) == 4)
+        self.assertTrue(len(self.net.hosts) == 4)
+        self.assertTrue(len(self.net.switches) == 5)
+
+        # setup links
+        self.net.setChain('vnf11', 'vnf22', 'intf1', 'intf2', bidirectional=True, cmd='add-flow', cookie=2)
+        # check connectivity by using ping
+        self.assertTrue(self.net.ping([vnf11, vnf22]) <= 0.0)
+        # check first service cannot ping second service
+        self.assertTrue(self.net.ping([vnf1, vnf22]) > 0.0)
+        self.assertTrue(self.net.ping([vnf2, vnf11]) > 0.0)
+
+        # delete the first service chain
+        self.net.setChain('vnf1', 'vnf2', 'intf1', 'intf2', bidirectional=True, cmd='del-flows', cookie=1)
+        # check connectivity of first service is down
+        self.assertTrue(self.net.ping([vnf1, vnf2]) > 0.0)
+        #time.sleep(100)
+        # check connectivity of second service is still up
+        self.assertTrue(self.net.ping([vnf11, vnf22]) <= 0.0)
+
+        # stop Mininet network
+        self.stopNet()
 
 #@unittest.skip("disabled compute tests for development")
 class testEmulatorCompute( SimpleTestTopology ):
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)