X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=src%2Fnepi%2Fresources%2Fns3%2Fns3wrapper.py;h=9251006668f112b0121bb4c81d46fb50dbfa50c9;hb=58a2b493f8df1072a1faa653c8abb6a3f9ba21fa;hp=4c2063ad7f672f5e25ab4608fe13477eb4b5dbe4;hpb=a56fbc84f38f4110add42bd77b82a437399bc8cd;p=nepi.git diff --git a/src/nepi/resources/ns3/ns3wrapper.py b/src/nepi/resources/ns3/ns3wrapper.py index 4c2063ad..92510066 100644 --- a/src/nepi/resources/ns3/ns3wrapper.py +++ b/src/nepi/resources/ns3/ns3wrapper.py @@ -3,9 +3,8 @@ # 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. +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation; # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -28,12 +27,12 @@ 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 @@ -61,9 +60,13 @@ def load_ns3_module(): # to prevent infinit loop if initial_size == len(libs): raise RuntimeError("Imposible to load shared libraries %s" % str(libs)) - initial_size = 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) @@ -97,7 +100,7 @@ def load_ns3_module(): return ns3mod class NS3Wrapper(object): - def __init__(self, loglevel = logging.INFO): + def __init__(self, loglevel = logging.INFO, enable_dump = False): super(NS3Wrapper, self).__init__() # Thread used to run the simulation self._simulation_thread = None @@ -126,6 +129,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: @@ -143,7 +154,7 @@ class NS3Wrapper(object): tid_count = type_id.GetRegisteredN() base = type_id.LookupByName("ns3::Object") - for i in xrange(tid_count): + for i in range(tid_count): tid = type_id.GetRegistered(i) if tid.MustHideFromDocumentation() or \ @@ -162,7 +173,20 @@ class NS3Wrapper(object): @property def is_running(self): - return self._started and self.ns3.Simulator.IsFinished() + return self.is_started and not self.ns3.Simulator.IsFinished() + + @property + def is_started(self): + if not self._started: + now = self.ns3.Simulator.Now() + if not now.IsZero(): + self._started = True + + return self._started + + @property + def is_finished(self): + return self.ns3.Simulator.IsFinished() def make_uuid(self): return "uuid%s" % uuid.uuid4() @@ -171,29 +195,54 @@ class NS3Wrapper(object): return self._objects.get(uuid) def factory(self, type_name, **kwargs): + """ 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))) + + ### DUMP + self.debuger.dump_factory(uuid, type_name, kwargs) + factory = self.ns3.ObjectFactory() factory.SetTypeId(type_name) - for name, value in kwargs.iteritems(): + for name, value in kwargs.items(): ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value) factory.Set(name, ns3_value) 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))) + + ### DUMP + self.debuger.dump_create(uuid, clazzname, args) + clazz = getattr(self.ns3, clazzname) # arguments starting with 'uuid' identify ns-3 C++ @@ -202,42 +251,104 @@ 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, **kwargs): - if operation == "isAppRunning": - return self._is_app_running(uuid) + ### 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 == "isStarted": + result = self.is_started - if uuid.startswith(SINGLETON): - obj = self._singleton(uuid) + elif operation == "isFinished": + result = self.is_finished + + elif operation == "isAppRunning": + result = self._is_app_running(uuid) + + elif operation == "isAppStarted": + result = self._is_app_started(uuid) + + elif operation == "recvFD": + ### passFD operation binds to a different random socket + ### en every execution, so the socket name that could be + ### dumped to the debug script using dump_invoke is + ### not be valid accross debug executions. + result = self._recv_fd(uuid, *args, **kwargs) + + elif operation == "addStaticRoute": + result = self._add_static_route(uuid, *args) + + ### DUMP - result is static, so will be dumped as plain text + self.debuger.dump_invoke(result, uuid, operation, args, kwargs) + + elif operation == "retrieveObject": + result = self._retrieve_object(uuid, *args, **kwargs) + + ### DUMP - result is static, so will be dumped as plain text + self.debuger.dump_invoke(result, uuid, operation, args, kwargs) + 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) - realkwargs = self.replace_kwargs(kwargs) + ### DUMP - result is a uuid that encoded an dynamically generated + ### object + 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, **realkwargs) + # 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 result is None or \ - isinstance(result, bool): - return result - - newuuid = self.make_uuid() - self._objects[newuuid] = result + result = method(*realargs, **realkwargs) - return newuuid + # 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_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))) + + ### DUMP + 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) @@ -260,12 +371,22 @@ class NS3Wrapper(object): 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)) + + ### DUMP + 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) @@ -280,9 +401,17 @@ class NS3Wrapper(object): if not event_executed[0]: self._get_attr(obj, name, ns3_value) - return self._attr_from_ns3_value_to_string(type_name, 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): + ### DUMP + self.debuger.dump_start() + # Launch the simulator thread and Start the # simulator in that thread self._condition = threading.Condition() @@ -291,15 +420,26 @@ class NS3Wrapper(object): args = [self._condition]) self._simulator_thread.setDaemon(True) self._simulator_thread.start() - self._started = True + + ### DEBUG + self.logger.debug("START") def stop(self, time = None): + ### DUMP + 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): + ### DUMP + self.debuger.dump_shutdown() + while not self.ns3.Simulator.IsFinished(): #self.logger.debug("Waiting for simulation to finish") time.sleep(0.5) @@ -315,6 +455,9 @@ 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() @@ -432,19 +575,22 @@ class NS3Wrapper(object): 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()]) + for k,v in kwargs.items()]) realkwargs = dict([(k, self._singleton(v) \ if str(v).startswith(SINGLETON) else v )\ - for k, v in realkwargs.iteritems()]) + for k, v in realkwargs.items()]) return realkwargs - def _is_app_running(self, uuid): + def _is_app_running(self, uuid): now = self.ns3.Simulator.Now() if now.IsZero(): return False + if self.ns3.Simulator.IsFinished(): + return False + app = self.get_object(uuid) stop_time_value = self.ns3.TimeValue() app.GetAttribute("StopTime", stop_time_value) @@ -454,8 +600,106 @@ class NS3Wrapper(object): 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 + if now.Compare(start_time) >= 0: + if stop_time.IsZero() or now.Compare(stop_time) < 0: + return True return False + + def _is_app_started(self, uuid): + return self._is_app_running(uuid) or self.is_finished + + 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 range(nifaces): + iface = ipv4.GetInterface(ifidx) + naddress = iface.GetNAddresses() + for addridx in range(naddress): + ifaddr = iface.GetAddress(addridx) + ifmask = ifaddr.GetMask() + + ifindex = ipv4.GetInterfaceForPrefix(nexthop, ifmask) + + if ifindex == ifidx: + return ifindex + return ifindex + + def _retrieve_object(self, uuid, typeid, search = False): + obj = self.get_object(uuid) + + type_id = self.ns3.TypeId() + tid = type_id.LookupByName(typeid) + nobj = obj.GetObject(tid) + + newuuid = None + if search: + # search object + for ouuid, oobj in self._objects.items(): + if nobj == oobj: + newuuid = ouuid + break + else: + newuuid = self.make_uuid() + self._objects[newuuid] = nobj + + return newuuid + + def _recv_fd(self, uuid): + """ Waits on a local address to receive a file descriptor + from a local process. The file descriptor is associated + to a FdNetDevice to stablish communication between the + simulation and what ever process writes on that file descriptor + """ + + def recvfd(sock, fdnd): + (fd, msg) = passfd.recvfd(sock) + # Store a reference to the endpoint to keep the socket alive + fdnd.SetFileDescriptor(fd) + + import passfd + import socket + sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + sock.bind("") + address = sock.getsockname() + + fdnd = self.get_object(uuid) + t = threading.Thread(target=recvfd, args=(sock,fdnd)) + t.start() + + return address +