Adding netns wrapper
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Thu, 10 Jul 2014 12:39:26 +0000 (14:39 +0200)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Thu, 10 Jul 2014 12:39:26 +0000 (14:39 +0200)
src/nepi/resources/netns/netnswrapper.py [new file with mode: 0644]
src/nepi/resources/netns/netnswrapper_debug.py [new file with mode: 0644]
src/nepi/resources/ns3/ns3wrapper_debug.py
test/lib/test_utils.py
test/resources/netns/netnswrapper.py [new file with mode: 0755]

diff --git a/src/nepi/resources/netns/netnswrapper.py b/src/nepi/resources/netns/netnswrapper.py
new file mode 100644 (file)
index 0000000..f2e1e7a
--- /dev/null
@@ -0,0 +1,186 @@
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2013 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+import logging
+import netns
+import time
+import os
+import sys
+import uuid
+
+class NetNSWrapper(object):
+    def __init__(self, loglevel = logging.INFO, enable_dump = False):
+        super(NetNSWrapper, self).__init__()
+        # holds reference to all C++ objects and variables in the simulation
+        self._objects = dict()
+
+        # Logging
+        self._logger = logging.getLogger("netnswrapper")
+        self._logger.setLevel(loglevel)
+
+        # Object to dump instructions to reproduce and debug experiment
+        from netnswrapper_debug import NetNSWrapperDebuger
+        self._debuger = NetNSWrapperDebuger(enabled = enable_dump)
+
+    @property
+    def debuger(self):
+        return self._debuger
+
+    @property
+    def logger(self):
+        return self._logger
+
+    def make_uuid(self):
+        return "uuid%s" % uuid.uuid4()
+
+    def get_object(self, uuid):
+        return self._objects.get(uuid)
+    def create(self, clazzname, *args):
+        """ This method should be used to construct netns objects """
+
+        if clazzname not in ['open'] and not hasattr(netns, clazzname):
+            msg = "Type %s not supported" % (clazzname) 
+            self.logger.error(msg)
+
+        uuid = self.make_uuid()
+        
+        ### DEBUG
+        self.logger.debug("CREATE %s( %s )" % (clazzname, str(args)))
+    
+        self.debuger.dump_create(uuid, clazzname, args)
+        ########
+
+        if clazzname == "open":
+            path = args[0] 
+            mode = args[1] 
+            obj = open(path, mode)
+        else:
+            clazz = getattr(netns, clazzname)
+     
+            # arguments starting with 'uuid' identify ns-3 C++
+            # objects and must be replaced by the actual object
+            realargs = self.replace_args(args)
+           
+            obj = clazz(*realargs)
+            
+        self._objects[uuid] = obj
+
+        ### DEBUG
+        self.logger.debug("RET CREATE ( uuid %s ) %s = %s( %s )" % (str(uuid), 
+            str(obj), clazzname, str(args)))
+        ########
+
+        return uuid
+
+    def invoke(self, uuid, operation, *args, **kwargs):
+        newuuid = self.make_uuid()
+        
+        ### DEBUG
+        self.logger.debug("INVOKE %s -> %s( %s, %s ) " % (
+            uuid, operation, str(args), str(kwargs)))
+            
+        self.debuger.dump_invoke(newuuid, uuid, operation, args, kwargs)
+        ########
+
+        obj = self.get_object(uuid)
+        
+        method = getattr(obj, operation)
+
+        # arguments starting with 'uuid' identify netns
+        # objects and must be replaced by the actual object
+        realargs = self.replace_args(args)
+        realkwargs = self.replace_kwargs(kwargs)
+
+        result = method(*realargs, **realkwargs)
+
+        # If the result is an object (not a base value),
+        # then keep track of the object a return the object
+        # reference (newuuid)
+        if not (result is None or type(result) in [
+                bool, float, long, str, int]):
+            self._objects[newuuid] = result
+            result = newuuid
+
+        ### DEBUG
+        self.logger.debug("RET INVOKE %s%s = %s -> %s(%s, %s) " % (
+            "(uuid %s) " % str(newuuid) if newuuid else "", str(result), uuid, 
+            operation, str(args), str(kwargs)))
+        ########
+
+        return result
+
+    def set(self, uuid, name, value):
+        ### DEBUG
+        self.logger.debug("SET %s %s %s" % (uuid, name, str(value)))
+    
+        self.debuger.dump_set(uuid, name, value)
+        ########
+
+        obj = self.get_object(uuid)
+        setattr(obj, name, value)
+
+        ### DEBUG
+        self.logger.debug("RET SET %s = %s -> set(%s, %s)" % (str(value), uuid, name, 
+            str(value)))
+        ########
+
+        return value
+
+    def get(self, uuid, name):
+        ### DEBUG
+        self.logger.debug("GET %s %s" % (uuid, name))
+        
+        self.debuger.dump_get(uuid, name)
+        ########
+
+        obj = self.get_object(uuid)
+        result = getattr(obj, name)
+
+        ### DEBUG
+        self.logger.debug("RET GET %s = %s -> get(%s)" % (str(result), uuid, name))
+        ########
+
+        return result
+
+    def shutdown(self):
+        ### DEBUG
+        self.debuger.dump_shutdown()
+        ########
+
+        sys.stdout.flush()
+        sys.stderr.flush()
+
+        ### DEBUG
+        self.logger.debug("SHUTDOWN")
+        ########
+
+    def replace_args(self, args):
+        realargs = [self.get_object(arg) if \
+                str(arg).startswith("uuid") else arg for arg in args]
+        return realargs
+
+    def replace_kwargs(self, kwargs):
+        realkwargs = dict([(k, self.get_object(v) \
+                if str(v).startswith("uuid") else v) \
+                for k,v in kwargs.iteritems()])
+        return realkwargs
+
diff --git a/src/nepi/resources/netns/netnswrapper_debug.py b/src/nepi/resources/netns/netnswrapper_debug.py
new file mode 100644 (file)
index 0000000..b273563
--- /dev/null
@@ -0,0 +1,191 @@
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2013 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+
+############ METHODS DEBUG NETNSWRAPPER EXECUTION
+##
+## The netnswrapper works in ditributed mode, receiving instructions from
+## a remote client. This makes it very hard to debug scripting errors 
+## in the client side. To simplify error debugging, when set to debug mode,
+## the ns3wrapper dumps every executed line to a script that can be then
+## executed locally to reproduce and debug the experiment.
+##
+###########################################################################
+
+import logging
+
+class NetNSWrapperDebuger(object):
+    def __init__(self, enabled):
+        super(NetNSWrapperDebuger, self).__init__()
+        self._enabled = enabled
+        self._script_path = "debug.py"
+
+        self.dump_header()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @property
+    def script_path(self):
+        return self._script_path
+
+    def dump_to_script(self, command):
+        f = open(self.script_path, "a")
+        f.write("%s" % command)
+        f.close()
+
+    def dump_header(self):
+        if not self.enabled:
+            return
+
+        header = """
+from netnswrapper import NetNSWrapper
+
+wrapper = NS3Wrapper()
+
+"""
+        self.dump_to_script(header)
+
+    def dump_factory(self, uuid, type_name, kwargs):
+        if not self.enabled:
+            return
+
+        command = ("kwargs = %(kwargs)s\n"
+                "%(uuid)s = wrapper.factory(%(type_name)s, **kwargs)\n\n" 
+                ) % dict({
+                 "uuid": self.format_value(uuid),
+                 "type_name": self.format_value(type_name),
+                 "kwargs": self.format_kwargs(kwargs)
+                })
+
+        self.dump_to_script(command)
+
+    def dump_create(self, uuid, clazzname, args):
+        if not self.enabled:
+            return
+
+        command = ("args = %(args)s\n"
+                "%(uuid)s = wrapper.create(%(clazzname)s, *args)\n\n" 
+                ) % dict({
+                 "uuid": self.format_value(uuid),
+                 "clazzname": self.format_value(clazzname),
+                 "args": self.format_args(args),
+                })
+
+        self.dump_to_script(command)
+
+    def dump_invoke(self, newuuid, uuid, operation, args, kwargs):
+        if not self.enabled:
+            return
+
+        command = ("args = %(args)s\n"
+                   "kwargs = %(kwargs)s\n"
+                   "%(newuuid)s = wrapper.invoke(%(uuid)s, %(operation)s, *args, **kwargs)\n\n" 
+                ) % dict({
+                 "newuuid": self.format_value(newuuid) if newuuid else "nothing",
+                 "uuid": self.format_value(uuid),
+                 "operation": self.format_value(operation),
+                 "args": self.format_args(args),
+                 "kwargs": self.format_kwargs(kwargs),
+                })
+
+        self.dump_to_script(command)
+
+    def dump_set(self, uuid, name, value):
+        if not self.enabled:
+            return
+
+        command = ("wrapper.set(%(uuid)s, %(name)s, %(value)s)\n\n" 
+                ) % dict({
+                 "uuid": self.format_value(uuid),
+                 "name": self.format_value(name),
+                 "value": self.format_value(value),
+                })
+
+        self.dump_to_script(command)
+
+    def dump_get(self, uuid, name):
+        if not self.enabled:
+            return
+
+        command = ("wrapper.get(%(uuid)s, %(name)s)\n\n" 
+                ) % dict({
+                 "uuid": self.format_value(uuid),
+                 "name": self.format_value(name),
+                })
+        
+        self.dump_to_script(command)
+
+    def dump_start(self):
+        if not self.enabled:
+            return
+
+        command = "wrapper.start()\n\n"
+        self.dump_to_script(command)
+
+    def dump_stop(self, time = None):
+        if not self.enabled:
+            return
+
+        command = ("wrapper.stop(time=%(time)s)\n\n" 
+                ) % dict({
+                 "time": self.format_value(time) if time else "None",
+                })
+
+        self.dump_to_script(command)
+
+    def dump_shutdown(self):
+        if not self.enabled:
+            return
+
+        command = "wrapper.shutdown()\n\n"
+        self.dump_to_script(command)
+
+    def dump_add_static_route(self, uuid, args):
+        if not self.enabled:
+            return
+
+        command = ("args = %(args)s\n"
+                   "wrapper._add_static_route(%(uuid)s, *args)\n\n" 
+                ) % dict({
+                 "uuid": self.format_value(uuid),
+                 "args": self.format_args(args),
+                })
+
+        self.dump_to_script(command)
+
+    def format_value(self, value):
+        if isinstance(value, str) and value.startswith("uuid"):
+            return value.replace("-", "")
+
+        import pprint 
+        return pprint.pformat(value)
+
+    def format_args(self, args):
+        fargs = map(self.format_value, args)
+        return "[%s]" % ",".join(fargs)
+
+    def format_kwargs(self, kwargs):
+        fkwargs = map(lambda (k,w): 
+               "%s: %s" % (self.format_value(k), self.format_value(w)), 
+            kwargs.iteritems())
+        
+        return  "dict({%s})" % ",".join(fkwargs)
+        
index 51eae83..99a6e5b 100644 (file)
@@ -20,8 +20,8 @@
 
 ############ METHODS DEBUG NS3WRAPPER EXECUTION
 ##
-## The ns3wrapper works in an interactive mode, receiving instructions in
-## a distributed manner. This makes it very hard to debug scripting errors 
+## The ns3wrapper works in ditributed mode, receiving instructions from
+## a remote client. This makes it very hard to debug scripting errors 
 ## in the client side. To simplify error debugging, when set to debug mode,
 ## the ns3wrapper dumps every executed line to a script that can be then
 ## executed locally to reproduce and debug the experiment.
index 9422114..958cfc0 100644 (file)
@@ -149,3 +149,18 @@ def skipIfNotSfi(func):
         return func(*args, **kwargs)
 
     return wrapped
+
+def skipIf(cond, text):
+    def wrapped(func, text):
+        name = func.__name__
+
+        def banner(*args, **kwargs):
+            sys.stderr.write("*** WARNING: Skipping test %s: `%s'\n" %
+                    (name, text))
+            return None
+        return banner
+
+    return (lambda func: wrapped(func, text)) if cond else lambda func: func
+
+
+
diff --git a/test/resources/netns/netnswrapper.py b/test/resources/netns/netnswrapper.py
new file mode 100755 (executable)
index 0000000..db3bf7e
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2013 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+
+# Test based on netns test/test_core.py file test_run_ping_routing test
+#
+
+from nepi.resources.netns.netnswrapper import NetNSWrapper
+
+from test_utils import skipIf
+
+import os
+import subprocess
+import sys
+import time
+import unittest
+
+class NetNSWrapperTest(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    @skipIf(os.getuid() != 0, "Test requires root privileges")
+    def test_run_ping_routing(self):
+        wrapper = NetNSWrapper()
+
+        ### create 3 nodes
+        #n1 = netns.Node()
+        #n2 = netns.Node()
+        #n3 = netns.Node()
+        n1 = wrapper.create("Node")
+        n2 = wrapper.create("Node")
+        n3 = wrapper.create("Node")
+
+        ### add interfaces to nodes
+        #i1 = n1.add_if()
+        #i2a = n2.add_if()
+        #i2b = n2.add_if()
+        #i3 = n3.add_if()
+        i1 = wrapper.invoke(n1, "add_if")
+        i2a = wrapper.invoke(n2, "add_if")
+        i2b = wrapper.invoke(n2, "add_if")
+        i3 = wrapper.invoke(n3, "add_if")
+
+        ### set interfaces up
+        # i1.up = i2a.up = i2b.up = i3.up = True
+        wrapper.set(i1, "up", True)
+        wrapper.set(i2a, "up", True)
+        wrapper.set(i2b, "up", True)
+        wrapper.set(i3, "up", True)
+
+        ### create 2 switches
+        #l1 = netns.Switch()
+        #l2 = netns.Switch()
+        l1 = wrapper.create("Switch")
+        l2 = wrapper.create("Switch")
+
+        ### connect interfaces to switches
+        #l1.connect(i1)
+        #l1.connect(i2a)
+        #l2.connect(i2b)
+        #l2.connect(i3)
+        wrapper.invoke(l1, "connect", i1)
+        wrapper.invoke(l1, "connect", i2a)
+        wrapper.invoke(l2, "connect", i2b)
+        wrapper.invoke(l2, "connect", i3)
+
+        ### set switched up
+        # l1.up = l2.up = True
+        wrapper.set(l1, "up", True)
+        wrapper.set(l2, "up", True)
+
+        ## add ip addresses to interfaces
+        #i1.add_v4_address('10.0.0.1', 24)
+        #i2a.add_v4_address('10.0.0.2', 24)
+        #i2b.add_v4_address('10.0.1.1', 24)
+        #i3.add_v4_address('10.0.1.2', 24)
+        wrapper.invoke(i1, "add_v4_address", "10.0.0.1", 24)
+        wrapper.invoke(i2a, "add_v4_address", "10.0.0.2", 24)
+        wrapper.invoke(i2b, "add_v4_address", "10.0.1.1", 24)
+        wrapper.invoke(i3, "add_v4_address", "10.0.1.2", 24)
+
+        ## add routes to nodes
+        #n1.add_route(prefix = '10.0.1.0', prefix_len = 24,
+        #        nexthop = '10.0.0.2')
+        #n3.add_route(prefix = '10.0.0.0', prefix_len = 24,
+        #        nexthop = '10.0.1.1')
+        wrapper.invoke(n1, "add_route", prefix = "10.0.1.0", prefix_len = 24,
+                nexthop = "10.0.0.2")
+        wrapper.invoke(n3, "add_route", prefix = "10.0.0.0", prefix_len = 24,
+                nexthop = "10.0.1.1")
+
+        ## launch pings
+        #a1 = n1.Popen(['ping', '-qc1', '10.0.1.2'], stdout = null)
+        #a2 = n3.Popen(['ping', '-qc1', '10.0.0.1'], stdout = null)
+        path1 = "/tmp/netns_file1"
+        path2 = "/tmp/netns_file2"
+        file1 = wrapper.create("open", path1, "w")
+        file2 = wrapper.create("open", path2, "w")
+        a1 = wrapper.invoke(n1, "Popen", ["ping", "-qc1", "10.0.1.2"], stdout = file1)
+        a2 = wrapper.invoke(n3, "Popen", ["ping", "-qc1", "10.0.0.1"], stdout = file2)
+
+        ## get ping status
+        p1 = None
+        p2 = None
+        while p1 is None or p2 is None:
+            p1 = wrapper.invoke(a1, "poll")
+            p2 = wrapper.invoke(a2, "poll")
+
+        stdout1 = open(path1, "r")
+        stdout2 = open(path2, "r")
+
+        print stdout1.read(), stdout2.read()
+
+if __name__ == '__main__':
+    unittest.main()
+