X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=src%2Fnepi%2Fresources%2Fns3%2Fns3wrapper.py;h=58da68ed45be2570d141cf075c915158a83ac334;hb=64fdda7bf665776fb043b355e989502e6c367a0c;hp=f6c063bdc1d0d3882bb7d776b405ef30cebddfe3;hpb=22d54534e6b8cf228522e618076f643d65e246d5;p=nepi.git diff --git a/src/nepi/resources/ns3/ns3wrapper.py b/src/nepi/resources/ns3/ns3wrapper.py index f6c063bd..58da68ed 100644 --- a/src/nepi/resources/ns3/ns3wrapper.py +++ b/src/nepi/resources/ns3/ns3wrapper.py @@ -24,19 +24,16 @@ import threading import time import uuid -# TODO: -# 1. ns-3 classes should be identified as ns3::clazzname? -# - 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 imp import re - import pkgutil - bindings = os.environ.get("NS3BINDINGS") libdir = os.environ.get("NS3LIBRARIES") # Load the ns-3 modules shared libraries @@ -45,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) @@ -53,26 +52,37 @@ 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) - # create a module to add all ns3 classes + import pkgutil + import imp + import ns + + # create a Python module to add all ns3 classes ns3mod = imp.new_module("ns3") sys.modules["ns3"] = ns3mod - # retrieve all ns3 classes and add them to the ns3 module - import ns - for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__): + if modname in [ "visualizer" ]: + continue + fullmodname = "ns.%s" % modname module = __import__(fullmodname, globals(), locals(), ['*']) @@ -91,54 +101,72 @@ 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 + # True if Simulator::Run was invoked self._started = False # holds reference to all C++ objects and variables in the simulation self._objects = dict() - # holds the class identifiers of uuid to be able to retrieve - # the corresponding ns3 TypeId to set/get attributes. - # This is necessary because the method GetInstanceTypeId is not - # exposed through the Python bindings - self._tids = 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) - - # Python module to refernce all ns-3 classes and types + 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, + # is that else each unit test for the ns3wrapper class would need + # a separate file. Several ns3wrappers would be created in the + # same unit test (single process), leading to inchorences in the + # state of ns-3 global objects + # + # Handler to ns3 classes self._ns3 = None - + + # 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: + # load ns-3 libraries and bindings self._ns3 = load_ns3_module() return self._ns3 @property - def homedir(self): - return self._homedir + def allowed_types(self): + if not self._allowed_types: + self._allowed_types = set() + type_id = self.ns3.TypeId() + + tid_count = type_id.GetRegisteredN() + base = type_id.LookupByName("ns3::Object") + + for i in xrange(tid_count): + tid = type_id.GetRegistered(i) + + if tid.MustHideFromDocumentation() or \ + not tid.HasConstructor() or \ + not tid.IsChildOf(base): + continue + + type_name = tid.GetName() + self._allowed_types.add(type_name) + + return self._allowed_types @property def logger(self): @@ -146,7 +174,7 @@ class NS3Wrapper(object): @property def is_running(self): - return self._started and self._ns3 and not self.ns3.Simulator.IsFinished() + return self._started and not self.ns3.Simulator.IsFinished() def make_uuid(self): return "uuid%s" % uuid.uuid4() @@ -154,14 +182,56 @@ class NS3Wrapper(object): def get_object(self, uuid): return self._objects.get(uuid) - def get_typeid(self, uuid): - return self._tids.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))) + + self.debuger.dump_factory(uuid, type_name, kwargs) + ######## + + factory = self.ns3.ObjectFactory() + factory.SetTypeId(type_name) + + for name, value in kwargs.iteritems(): + ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value) + factory.Set(name, ns3_value) + + obj = factory.Create() + + 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++ @@ -170,43 +240,84 @@ class NS3Wrapper(object): obj = clazz(*realargs) - uuid = self.make_uuid() self._objects[uuid] = obj - #typeid = clazz.GetInstanceTypeId().GetName() - typeid = "ns3::%s" % clazzname - self._tids[uuid] = typeid + ### 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) + ######## - result = method(*realargs) + if uuid.startswith(SINGLETON): + obj = self._singleton(uuid) + else: + obj = self.get_object(uuid) + + method = getattr(obj, operation) - if not result: - return None - - newuuid = self.make_uuid() - self._objects[newuuid] = result + # 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) + + 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 - return 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): - obj = self.get_object(uuid) - ns3_value = self._to_ns3_value(uuid, name, value) + ### DEBUG + self.logger.debug("SET %s %s %s" % (uuid, name, str(value))) + + self.debuger.dump_set(uuid, name, value) + ######## - def set_attr(obj, name, ns3_value): - obj.SetAttribute(name, ns3_value) + obj = self.get_object(uuid) + type_name = obj.GetInstanceTypeId().GetName() + ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value) # If the Simulation thread is not running, # then there will be no thread-safety problems @@ -215,32 +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, set_attr, obj, - name, ns3_value) - else: - set_attr(obj, name, ns3_value) + 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) - ns3_value = self._create_ns3_value(uuid, name) + type_name = obj.GetInstanceTypeId().GetName() + ns3_value = self._create_attr_ns3_value(type_name, name) - def get_attr(obj, name, ns3_value): - obj.GetAttribute(name, ns3_value) + event_executed = [False] if self.is_running: # schedule the event in the Simulator - self._schedule_event(self._condition, 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) + + 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) - return self._from_ns3_value(uuid, 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() @@ -251,34 +391,48 @@ class NS3Wrapper(object): self._simulator_thread.start() self._started = True - def stop(self, time = None): - if not self.ns3: - return + ### 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): - if self.ns3: - 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() + ### DEBUG + self.debuger.dump_shutdown() + ######## + + while not self.ns3.Simulator.IsFinished(): + #self.logger.debug("Waiting for simulation to finish") + time.sleep(0.5) + + if self._simulator_thread: + self._simulator_thread.join() + + self.ns3.Simulator.Destroy() # Remove all references to ns-3 objects self._objects.clear() - self._ns3 = None sys.stdout.flush() sys.stderr.flush() + ### DEBUG + self.logger.debug("SHUTDOWN") + ######## + def _simulator_run(self, condition): # Run simulation self.ns3.Simulator.Run() @@ -288,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() @@ -308,41 +461,37 @@ 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() try: + self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event, + condition, event_executed, func, *args) if not self.ns3.Simulator.IsFinished(): - self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event, - condition, has_event_occurred, func, *args) - while not has_event_occurred[0] and not self.ns3.Simulator.IsFinished(): - condition.wait() + condition.wait() finally: condition.release() - def _create_ns3_value(self, uuid, name): - typeid = self.get_typeid(uuid) + def _create_attr_ns3_value(self, type_name, name): TypeId = self.ns3.TypeId() - tid = TypeId.LookupByName(typeid) + tid = TypeId.LookupByName(type_name) info = TypeId.AttributeInformation() if not tid.LookupAttributeByName(name, info): - msg = "TypeId %s has no attribute %s" % (typeid, name) + msg = "TypeId %s has no attribute %s" % (type_name, name) self.logger.error(msg) checker = info.checker ns3_value = checker.Create() return ns3_value - def _from_ns3_value(self, uuid, name, ns3_value): - typeid = self.get_typeid(uuid) + def _attr_from_ns3_value_to_string(self, type_name, name, ns3_value): TypeId = self.ns3.TypeId() - tid = TypeId.LookupByName(typeid) + tid = TypeId.LookupByName(type_name) info = TypeId.AttributeInformation() if not tid.LookupAttributeByName(name, info): - msg = "TypeId %s has no attribute %s" % (typeid, name) + msg = "TypeId %s has no attribute %s" % (type_name, name) self.logger.error(msg) checker = info.checker @@ -358,13 +507,12 @@ class NS3Wrapper(object): return value - def _to_ns3_value(self, uuid, name, value): - typeid = self.get_typeid(uuid) + def _attr_from_string_to_ns3_value(self, type_name, name, value): TypeId = self.ns3.TypeId() - tid = TypeId.LookupByName(typeid) + tid = TypeId.LookupByName(type_name) info = TypeId.AttributeInformation() if not tid.LookupAttributeByName(name, info): - msg = "TypeId %s has no attribute %s" % (typeid, name) + msg = "TypeId %s has no attribute %s" % (type_name, name) self.logger.error(msg) str_value = str(value) @@ -398,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 +