--- /dev/null
+#
+# 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
+
--- /dev/null
+#
+# 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)
+
############ 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.
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
+
+
+
--- /dev/null
+#!/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()
+