ns-3 wifi example examples/linux/ns3/wifi_ping.py
[nepi.git] / src / nepi / resources / ns3 / ns3wrapper.py
index 064b32c..58da68e 100644 (file)
@@ -25,12 +25,15 @@ import time
 import uuid
 
 SINGLETON = "singleton::"
+SIMULATOR_UUID = "singleton::Simulator"
+CONFIG_UUID = "singleton::Config"
+GLOBAL_VALUE_UUID = "singleton::GlobalValue"
+IPV4_GLOBAL_ROUTING_HELPER_UUID = "singleton::Ipv4GlobalRoutingHelper"
 
-def load_ns3_module():
+def load_ns3_libraries():
     import ctypes
     import re
 
-    bindings = os.environ.get("NS3BINDINGS")
     libdir = os.environ.get("NS3LIBRARIES")
 
     # Load the ns-3 modules shared libraries
@@ -39,7 +42,9 @@ def load_ns3_module():
         regex = re.compile("(.*\.so)$")
         libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m]
 
-        libscp = list(libs)
+        initial_size = len(libs)
+        # Try to load the libraries in the right order by trial and error.
+        # Loop until all libraries are loaded.
         while len(libs) > 0:
             for lib in libs:
                 libfile = os.path.join(libdir, lib)
@@ -47,15 +52,22 @@ def load_ns3_module():
                     ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
                     libs.remove(lib)
                 except:
+                    #import traceback
+                    #err = traceback.format_exc()
+                    #print err
                     pass
 
             # if did not load any libraries in the last iteration break
             # to prevent infinit loop
-            if len(libscp) == len(libs):
+            if initial_size == len(libs):
                 raise RuntimeError("Imposible to load shared libraries %s" % str(libs))
-            libscp = list(libs)
+            initial_size = len(libs)
+
+def load_ns3_module():
+    load_ns3_libraries()
 
     # import the python bindings for the ns-3 modules
+    bindings = os.environ.get("NS3BINDINGS")
     if bindings:
         sys.path.append(bindings)
 
@@ -63,11 +75,14 @@ def load_ns3_module():
     import imp
     import ns
 
-    # create a module to add all ns3 classes
+    # create a Python module to add all ns3 classes
     ns3mod = imp.new_module("ns3")
     sys.modules["ns3"] = ns3mod
 
     for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
+        if modname in [ "visualizer" ]:
+            continue
+
         fullmodname = "ns.%s" % modname
         module = __import__(fullmodname, globals(), locals(), ['*'])
 
@@ -86,36 +101,21 @@ def load_ns3_module():
     return ns3mod
 
 class NS3Wrapper(object):
-    def __init__(self, homedir = None):
+    def __init__(self, loglevel = logging.INFO, enable_dump = False):
         super(NS3Wrapper, self).__init__()
         # Thread used to run the simulation
         self._simulation_thread = None
         self._condition = None
 
-        # XXX: Started should be global. There is no support for more than
-        # one simulator per process
+        # True if Simulator::Run was invoked
         self._started = False
 
         # holds reference to all C++ objects and variables in the simulation
         self._objects = dict()
 
-        # create home dir (where all simulation related files will end up)
-        self._homedir = homedir or os.path.join("/", "tmp", "ns3_wrapper" )
-        
-        home = os.path.normpath(self.homedir)
-        if not os.path.exists(home):
-            os.makedirs(home, 0755)
-
         # Logging
-        loglevel = os.environ.get("NS3LOGLEVEL", "debug")
         self._logger = logging.getLogger("ns3wrapper")
-        self._logger.setLevel(getattr(logging, loglevel.upper()))
-        
-        hdlr = logging.FileHandler(os.path.join(self.homedir, "ns3wrapper.log"))
-        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
-        hdlr.setFormatter(formatter)
-        
-        self._logger.addHandler(hdlr)
+        self._logger.setLevel(loglevel)
 
         ## NOTE that the reason to create a handler to the ns3 module,
         # that is re-loaded each time a ns-3 wrapper is instantiated,
@@ -130,6 +130,14 @@ class NS3Wrapper(object):
         # Collection of allowed ns3 classes
         self._allowed_types = None
 
+        # Object to dump instructions to reproduce and debug experiment
+        from ns3wrapper_debug import NS3WrapperDebuger
+        self._debuger = NS3WrapperDebuger(enabled = enable_dump)
+
+    @property
+    def debuger(self):
+        return self._debuger
+
     @property
     def ns3(self):
         if not self._ns3:
@@ -147,7 +155,6 @@ class NS3Wrapper(object):
             tid_count = type_id.GetRegisteredN()
             base = type_id.LookupByName("ns3::Object")
 
-            # Create a .py file using the ns-3 RM template for each ns-3 TypeId
             for i in xrange(tid_count):
                 tid = type_id.GetRegistered(i)
                 
@@ -161,17 +168,13 @@ class NS3Wrapper(object):
         
         return self._allowed_types
 
-    @property
-    def homedir(self):
-        return self._homedir
-
     @property
     def logger(self):
         return self._logger
 
     @property
     def is_running(self):
-        return self._started and self.ns3.Simulator.IsFinished()
+        return self._started and not self.ns3.Simulator.IsFinished()
 
     def make_uuid(self):
         return "uuid%s" % uuid.uuid4()
@@ -180,10 +183,21 @@ class NS3Wrapper(object):
         return self._objects.get(uuid)
 
     def factory(self, type_name, **kwargs):
-        if type_name not in allowed_types:
+        """ This method should be used to construct ns-3 objects
+        that have a TypeId and related introspection information """
+
+        if type_name not in self.allowed_types:
             msg = "Type %s not supported" % (type_name) 
             self.logger.error(msg)
+
+        uuid = self.make_uuid()
+        
+        ### DEBUG
+        self.logger.debug("FACTORY %s( %s )" % (type_name, str(kwargs)))
+        
+        self.debuger.dump_factory(uuid, type_name, kwargs)
+        ########
+
         factory = self.ns3.ObjectFactory()
         factory.SetTypeId(type_name)
 
@@ -193,16 +207,31 @@ class NS3Wrapper(object):
 
         obj = factory.Create()
 
-        uuid = self.make_uuid()
         self._objects[uuid] = obj
 
+        ### DEBUG
+        self.logger.debug("RET FACTORY ( uuid %s ) %s = %s( %s )" % (
+            str(uuid), str(obj), type_name, str(kwargs)))
+        ########
         return uuid
 
     def create(self, clazzname, *args):
+        """ This method should be used to construct ns-3 objects that
+        do not have a TypeId (e.g. Values) """
+
         if not hasattr(self.ns3, 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)
+        ########
+
         clazz = getattr(self.ns3, clazzname)
  
         # arguments starting with 'uuid' identify ns-3 C++
@@ -211,37 +240,81 @@ class NS3Wrapper(object):
        
         obj = clazz(*realargs)
         
-        uuid = self.make_uuid()
         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):
-        if uuid.startswith(SINGLETON):
-            obj = self._singleton(uuid)
+    def invoke(self, uuid, operation, *args, **kwargs):
+        ### DEBUG
+        self.logger.debug("INVOKE %s -> %s( %s, %s ) " % (
+            uuid, operation, str(args), str(kwargs)))
+        ########
+
+        result = None
+        newuuid = None
+
+        if operation == "isRunning":
+            result = self.is_running
+        elif operation == "isAppRunning":
+            result = self._is_app_running(uuid)
+        elif operation == "addStaticRoute":
+            ### DEBUG
+            self.debuger.dump_add_static_route(uuid, args)
+            ########
+
+            result = self._add_static_route(uuid, *args)
         else:
-            obj = self.get_object(uuid)
-    
-        method = getattr(obj, operation)
+            newuuid = self.make_uuid()
 
-        # arguments starting with 'uuid' identify ns-3 C++
-        # objects and must be replaced by the actual object
-        realargs = self.replace_args(args)
+            ### DEBUG
+            self.debuger.dump_invoke(newuuid, uuid, operation, args, kwargs)
+            ########
+
+            if uuid.startswith(SINGLETON):
+                obj = self._singleton(uuid)
+            else:
+                obj = self.get_object(uuid)
+            
+            method = getattr(obj, operation)
 
-        result = method(*realargs)
+            # arguments starting with 'uuid' identify ns-3 C++
+            # objects and must be replaced by the actual object
+            realargs = self.replace_args(args)
+            realkwargs = self.replace_kwargs(kwargs)
 
-        if not result:
-            return None
-        
-        newuuid = self.make_uuid()
-        self._objects[newuuid] = result
+            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 newuuid
+        return result
 
     def _set_attr(self, obj, name, ns3_value):
         obj.SetAttribute(name, ns3_value)
 
     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)
         type_name = obj.GetInstanceTypeId().GetName()
         ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value)
@@ -253,33 +326,61 @@ class NS3Wrapper(object):
         # to set the value by scheduling an event, else
         # we risk to corrupt the state of the
         # simulation.
+        
+        event_executed = [False]
+
         if self.is_running:
             # schedule the event in the Simulator
-            self._schedule_event(self._condition, self._set_attr, 
-                    obj, name, ns3_value)
-        else:
+            self._schedule_event(self._condition, event_executed, 
+                    self._set_attr, obj, name, ns3_value)
+
+        if not event_executed[0]:
             self._set_attr(obj, name, ns3_value)
 
+        ### DEBUG
+        self.logger.debug("RET SET %s = %s -> set(%s, %s)" % (str(value), uuid, name, 
+            str(value)))
+        ########
+
         return value
 
     def _get_attr(self, obj, name, ns3_value):
         obj.GetAttribute(name, ns3_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)
         type_name = obj.GetInstanceTypeId().GetName()
         ns3_value = self._create_attr_ns3_value(type_name, name)
 
+        event_executed = [False]
+
         if self.is_running:
             # schedule the event in the Simulator
-            self._schedule_event(self._condition, self._get_attr, obj,
-                    name, ns3_value)
-        else:
-            get_attr(obj, name, ns3_value)
+            self._schedule_event(self._condition, event_executed,
+                    self._get_attr, obj, name, ns3_value)
 
-        return self._attr_from_ns3_value_to_string(type_name, name, ns3_value)
+        if not event_executed[0]:
+            self._get_attr(obj, name, ns3_value)
+
+        result = self._attr_from_ns3_value_to_string(type_name, name, ns3_value)
+
+        ### DEBUG
+        self.logger.debug("RET GET %s = %s -> get(%s)" % (str(result), uuid, name))
+        ########
+
+        return result
 
     def start(self):
+        ### DEBUG
+        self.debuger.dump_start()
+        ########
+
         # Launch the simulator thread and Start the
         # simulator in that thread
         self._condition = threading.Condition()
@@ -290,21 +391,36 @@ class NS3Wrapper(object):
         self._simulator_thread.start()
         self._started = True
 
+        ### DEBUG
+        self.logger.debug("START")
+        ########
+
     def stop(self, time = None):
+        ### DEBUG
+        self.debuger.dump_stop(time=time)
+        ########
+        
         if time is None:
             self.ns3.Simulator.Stop()
         else:
             self.ns3.Simulator.Stop(self.ns3.Time(time))
 
+        ### DEBUG
+        self.logger.debug("STOP time=%s" % str(time))
+        ########
+
     def shutdown(self):
+        ### DEBUG
+        self.debuger.dump_shutdown()
+        ########
+
         while not self.ns3.Simulator.IsFinished():
             #self.logger.debug("Waiting for simulation to finish")
             time.sleep(0.5)
         
-        # TODO!!!! SHOULD WAIT UNTIL THE THREAD FINISHES
         if self._simulator_thread:
             self._simulator_thread.join()
-        
+       
         self.ns3.Simulator.Destroy()
         
         # Remove all references to ns-3 objects
@@ -313,6 +429,10 @@ class NS3Wrapper(object):
         sys.stdout.flush()
         sys.stderr.flush()
 
+        ### DEBUG
+        self.logger.debug("SHUTDOWN")
+        ########
+
     def _simulator_run(self, condition):
         # Run simulation
         self.ns3.Simulator.Run()
@@ -322,17 +442,16 @@ class NS3Wrapper(object):
         condition.notifyAll()
         condition.release()
 
-    def _schedule_event(self, condition, func, *args):
+    def _schedule_event(self, condition, event_executed, func, *args):
         """ Schedules event on running simulation, and wait until
             event is executed"""
 
-        def execute_event(contextId, condition, has_event_occurred, func, *args):
+        def execute_event(contextId, condition, event_executed, func, *args):
             try:
                 func(*args)
+                event_executed[0] = True
             finally:
-                # flag event occured
-                has_event_occurred[0] = True
-                # notify condition indicating attribute was set
+                # notify condition indicating event was executed
                 condition.acquire()
                 condition.notifyAll()
                 condition.release()
@@ -342,21 +461,16 @@ class NS3Wrapper(object):
 
         # delay 0 means that the event is expected to execute inmediately
         delay = self.ns3.Seconds(0)
+    
+        # Mark event as not executed
+        event_executed[0] = False
 
-        # flag to indicate that the event occured
-        # because bool is an inmutable object in python, in order to create a
-        # bool flag, a list is used as wrapper
-        has_event_occurred = [False]
         condition.acquire()
-
-        simu = self.ns3.Simulator
-
         try:
-            if not simu.IsFinished():
-                simu.ScheduleWithContext(contextId, delay, execute_event,
-                     condition, has_event_occurred, func, *args)
-                while not has_event_occurred[0] and not simu.IsFinished():
-                    condition.wait()
+            self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event, 
+                    condition, event_executed, func, *args)
+            if not self.ns3.Simulator.IsFinished():
+                condition.wait()
         finally:
             condition.release()
 
@@ -432,3 +546,83 @@ class NS3Wrapper(object):
 
         return realargs
 
+    # replace uuids and singleton references for the real objects
+    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()])
+        realkwargs = dict([(k, self._singleton(v) \
+                if str(v).startswith(SINGLETON) else v )\
+                for k, v in realkwargs.iteritems()])
+
+        return realkwargs
+
+    def _is_app_running(self, uuid): 
+        now = self.ns3.Simulator.Now()
+        if now.IsZero():
+            return False
+
+        app = self.get_object(uuid)
+        stop_time_value = self.ns3.TimeValue()
+        app.GetAttribute("StopTime", stop_time_value)
+        stop_time = stop_time_value.Get()
+
+        start_time_value = self.ns3.TimeValue()
+        app.GetAttribute("StartTime", start_time_value)
+        start_time = start_time_value.Get()
+        
+        if now.Compare(start_time) >= 0 and now.Compare(stop_time) < 0:
+            return True
+
+        return False
+
+    def _add_static_route(self, ipv4_uuid, network, prefix, nexthop):
+        ipv4 = self.get_object(ipv4_uuid)
+
+        list_routing = ipv4.GetRoutingProtocol()
+        (static_routing, priority) = list_routing.GetRoutingProtocol(0)
+
+        ifindex = self._find_ifindex(ipv4, nexthop)
+        if ifindex == -1:
+            return False
+        
+        nexthop = self.ns3.Ipv4Address(nexthop)
+
+        if network in ["0.0.0.0", "0", None]:
+            # Default route: 0.0.0.0/0
+            static_routing.SetDefaultRoute(nexthop, ifindex)
+        else:
+            mask = self.ns3.Ipv4Mask("/%s" % prefix) 
+            network = self.ns3.Ipv4Address(network)
+
+            if prefix == 32:
+                # Host route: x.y.z.w/32
+                static_routing.AddHostRouteTo(network, nexthop, ifindex)
+            else:
+                # Network route: x.y.z.w/n
+                static_routing.AddNetworkRouteTo(network, mask, nexthop, 
+                        ifindex) 
+        return True
+
+    def _find_ifindex(self, ipv4, nexthop):
+        ifindex = -1
+
+        nexthop = self.ns3.Ipv4Address(nexthop)
+
+        # For all the interfaces registered with the ipv4 object, find
+        # the one that matches the network of the nexthop
+        nifaces = ipv4.GetNInterfaces()
+        for ifidx in xrange(nifaces):
+            iface = ipv4.GetInterface(ifidx)
+            naddress = iface.GetNAddresses()
+            for addridx in xrange(naddress):
+                ifaddr = iface.GetAddress(addridx)
+                ifmask = ifaddr.GetMask()
+                
+                ifindex = ipv4.GetInterfaceForPrefix(nexthop, ifmask)
+
+                if ifindex == ifidx:
+                    return ifindex
+        return ifindex
+