2 # NEPI, a framework to manage network experiments
3 # Copyright (C) 2013 INRIA
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
28 # 1. ns-3 classes should be identified as ns3::clazzname?
31 SINGLETON = "singleton::"
33 def load_ns3_module():
39 bindings = os.environ.get("NS3BINDINGS")
40 libdir = os.environ.get("NS3LIBRARIES")
42 # Load the ns-3 modules shared libraries
44 files = os.listdir(libdir)
45 regex = re.compile("(.*\.so)$")
46 libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m]
51 libfile = os.path.join(libdir, lib)
53 ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
58 # if did not load any libraries in the last iteration break
59 # to prevent infinit loop
60 if len(libscp) == len(libs):
61 raise RuntimeError("Imposible to load shared libraries %s" % str(libs))
64 # import the python bindings for the ns-3 modules
66 sys.path.append(bindings)
68 # create a module to add all ns3 classes
69 ns3mod = imp.new_module("ns3")
70 sys.modules["ns3"] = ns3mod
72 # retrieve all ns3 classes and add them to the ns3 module
75 for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
76 fullmodname = "ns.%s" % modname
77 module = __import__(fullmodname, globals(), locals(), ['*'])
79 for sattr in dir(module):
80 if sattr.startswith("_"):
83 attr = getattr(module, sattr)
85 # netanim.Config and lte.Config singleton overrides ns3::Config
86 if sattr == "Config" and modname in ['netanim', 'lte']:
87 sattr = "%s.%s" % (modname, sattr)
89 setattr(ns3mod, sattr, attr)
93 class NS3Wrapper(object):
94 def __init__(self, homedir = None):
95 super(NS3Wrapper, self).__init__()
96 # Thread used to run the simulation
97 self._simulation_thread = None
98 self._condition = None
100 self._started = False
102 # holds reference to all C++ objects and variables in the simulation
103 self._objects = dict()
105 # holds the class identifiers of uuid to be able to retrieve
106 # the corresponding ns3 TypeId to set/get attributes.
107 # This is necessary because the method GetInstanceTypeId is not
108 # exposed through the Python bindings
111 # create home dir (where all simulation related files will end up)
112 self._homedir = homedir or os.path.join("/", "tmp", "ns3_wrapper" )
114 home = os.path.normpath(self.homedir)
115 if not os.path.exists(home):
116 os.makedirs(home, 0755)
119 loglevel = os.environ.get("NS3LOGLEVEL", "debug")
120 self._logger = logging.getLogger("ns3wrapper")
121 self._logger.setLevel(getattr(logging, loglevel.upper()))
123 hdlr = logging.FileHandler(os.path.join(self.homedir, "ns3wrapper.log"))
124 formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
125 hdlr.setFormatter(formatter)
127 self._logger.addHandler(hdlr)
129 # Python module to refernce all ns-3 classes and types
135 self._ns3 = load_ns3_module()
148 def is_running(self):
149 return self._started and self._ns3 and not self.ns3.Simulator.IsFinished()
152 return "uuid%s" % uuid.uuid4()
154 def get_object(self, uuid):
155 return self._objects.get(uuid)
157 def get_typeid(self, uuid):
158 return self._tids.get(uuid)
160 def create(self, clazzname, *args):
161 if not hasattr(self.ns3, clazzname):
162 msg = "Type %s not supported" % (clazzname)
163 self.logger.error(msg)
165 clazz = getattr(self.ns3, clazzname)
167 # arguments starting with 'uuid' identify ns-3 C++
168 # objects and must be replaced by the actual object
169 realargs = self.replace_args(args)
171 obj = clazz(*realargs)
173 uuid = self.make_uuid()
174 self._objects[uuid] = obj
176 #typeid = clazz.GetInstanceTypeId().GetName()
177 typeid = "ns3::%s" % clazzname
178 self._tids[uuid] = typeid
182 def invoke(self, uuid, operation, *args):
183 if uuid.startswith(SINGLETON):
184 obj = self._singleton(uuid)
186 obj = self.get_object(uuid)
188 method = getattr(obj, operation)
190 # arguments starting with 'uuid' identify ns-3 C++
191 # objects and must be replaced by the actual object
192 realargs = self.replace_args(args)
194 result = method(*realargs)
199 newuuid = self.make_uuid()
200 self._objects[newuuid] = result
204 def set(self, uuid, name, value):
205 obj = self.get_object(uuid)
206 ns3_value = self._to_ns3_value(uuid, name, value)
208 def set_attr(obj, name, ns3_value):
209 obj.SetAttribute(name, ns3_value)
211 # If the Simulation thread is not running,
212 # then there will be no thread-safety problems
213 # in changing the value of an attribute directly.
214 # However, if the simulation is running we need
215 # to set the value by scheduling an event, else
216 # we risk to corrupt the state of the
219 # schedule the event in the Simulator
220 self._schedule_event(self._condition, set_attr, obj,
223 set_attr(obj, name, ns3_value)
227 def get(self, uuid, name):
228 obj = self.get_object(uuid)
229 ns3_value = self._create_ns3_value(uuid, name)
231 def get_attr(obj, name, ns3_value):
232 obj.GetAttribute(name, ns3_value)
235 # schedule the event in the Simulator
236 self._schedule_event(self._condition, get_attr, obj,
239 get_attr(obj, name, ns3_value)
241 return self._from_ns3_value(uuid, name, ns3_value)
244 # Launch the simulator thread and Start the
245 # simulator in that thread
246 self._condition = threading.Condition()
247 self._simulator_thread = threading.Thread(
248 target = self._simulator_run,
249 args = [self._condition])
250 self._simulator_thread.setDaemon(True)
251 self._simulator_thread.start()
254 def stop(self, time = None):
259 self.ns3.Simulator.Stop()
261 self.ns3.Simulator.Stop(self.ns3.Time(time))
265 while not self.ns3.Simulator.IsFinished():
266 #self.logger.debug("Waiting for simulation to finish")
269 # TODO!!!! SHOULD WAIT UNTIL THE THREAD FINISHES
270 if self._simulator_thread:
271 self._simulator_thread.join()
273 self.ns3.Simulator.Destroy()
275 # Remove all references to ns-3 objects
276 self._objects.clear()
282 def _simulator_run(self, condition):
284 self.ns3.Simulator.Run()
285 # Signal condition to indicate simulation ended and
286 # notify waiting threads
288 condition.notifyAll()
291 def _schedule_event(self, condition, func, *args):
292 """ Schedules event on running simulation, and wait until
295 def execute_event(contextId, condition, has_event_occurred, func, *args):
300 has_event_occurred[0] = True
301 # notify condition indicating attribute was set
303 condition.notifyAll()
306 # contextId is defined as general context
307 contextId = long(0xffffffff)
309 # delay 0 means that the event is expected to execute inmediately
310 delay = self.ns3.Seconds(0)
312 # flag to indicate that the event occured
313 # because bool is an inmutable object in python, in order to create a
314 # bool flag, a list is used as wrapper
315 has_event_occurred = [False]
318 if not self.ns3.Simulator.IsFinished():
319 self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event,
320 condition, has_event_occurred, func, *args)
321 while not has_event_occurred[0] and not self.ns3.Simulator.IsFinished():
326 def _create_ns3_value(self, uuid, name):
327 typeid = self.get_typeid(uuid)
328 TypeId = self.ns3.TypeId()
329 tid = TypeId.LookupByName(typeid)
330 info = TypeId.AttributeInformation()
331 if not tid.LookupAttributeByName(name, info):
332 msg = "TypeId %s has no attribute %s" % (typeid, name)
333 self.logger.error(msg)
335 checker = info.checker
336 ns3_value = checker.Create()
339 def _from_ns3_value(self, uuid, name, ns3_value):
340 typeid = self.get_typeid(uuid)
341 TypeId = self.ns3.TypeId()
342 tid = TypeId.LookupByName(typeid)
343 info = TypeId.AttributeInformation()
344 if not tid.LookupAttributeByName(name, info):
345 msg = "TypeId %s has no attribute %s" % (typeid, name)
346 self.logger.error(msg)
348 checker = info.checker
349 value = ns3_value.SerializeToString(checker)
351 type_name = checker.GetValueTypeName()
352 if type_name in ["ns3::UintegerValue", "ns3::IntegerValue"]:
354 if type_name == "ns3::DoubleValue":
356 if type_name == "ns3::BooleanValue":
357 return value == "true"
361 def _to_ns3_value(self, uuid, name, value):
362 typeid = self.get_typeid(uuid)
363 TypeId = self.ns3.TypeId()
364 tid = TypeId.LookupByName(typeid)
365 info = TypeId.AttributeInformation()
366 if not tid.LookupAttributeByName(name, info):
367 msg = "TypeId %s has no attribute %s" % (typeid, name)
368 self.logger.error(msg)
370 str_value = str(value)
371 if isinstance(value, bool):
372 str_value = str_value.lower()
374 checker = info.checker
375 ns3_value = checker.Create()
376 ns3_value.DeserializeFromString(str_value, checker)
379 # singletons are identified as "ns3::ClassName"
380 def _singleton(self, ident):
381 if not ident.startswith(SINGLETON):
384 clazzname = ident[ident.find("::")+2:]
385 if not hasattr(self.ns3, clazzname):
386 msg = "Type %s not supported" % (clazzname)
387 self.logger.error(msg)
389 return getattr(self.ns3, clazzname)
391 # replace uuids and singleton references for the real objects
392 def replace_args(self, args):
393 realargs = [self.get_object(arg) if \
394 str(arg).startswith("uuid") else arg for arg in args]
396 realargs = [self._singleton(arg) if \
397 str(arg).startswith(SINGLETON) else arg for arg in realargs]