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>
27 SINGLETON = "singleton::"
29 def load_ns3_module():
33 bindings = os.environ.get("NS3BINDINGS")
34 libdir = os.environ.get("NS3LIBRARIES")
36 # Load the ns-3 modules shared libraries
38 files = os.listdir(libdir)
39 regex = re.compile("(.*\.so)$")
40 libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m]
42 initial_size = len(libs)
43 # Try to load the libraries in the right order by trial and error.
44 # Loop until all libraries are loaded.
47 libfile = os.path.join(libdir, lib)
49 ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
53 #err = traceback.format_exc()
57 # if did not load any libraries in the last iteration break
58 # to prevent infinit loop
59 if initial_size == len(libs):
60 raise RuntimeError("Imposible to load shared libraries %s" % str(libs))
61 initial_size = list(libs)
63 # import the python bindings for the ns-3 modules
65 sys.path.append(bindings)
71 # create a Python module to add all ns3 classes
72 ns3mod = imp.new_module("ns3")
73 sys.modules["ns3"] = ns3mod
75 for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
76 if modname in [ "visualizer" ]:
79 fullmodname = "ns.%s" % modname
80 module = __import__(fullmodname, globals(), locals(), ['*'])
82 for sattr in dir(module):
83 if sattr.startswith("_"):
86 attr = getattr(module, sattr)
88 # netanim.Config and lte.Config singleton overrides ns3::Config
89 if sattr == "Config" and modname in ['netanim', 'lte']:
90 sattr = "%s.%s" % (modname, sattr)
92 setattr(ns3mod, sattr, attr)
96 class NS3Wrapper(object):
97 def __init__(self, loglevel = logging.INFO):
98 super(NS3Wrapper, self).__init__()
99 # Thread used to run the simulation
100 self._simulation_thread = None
101 self._condition = None
103 # True if Simulator::Run was invoked
104 self._started = False
106 # holds reference to all C++ objects and variables in the simulation
107 self._objects = dict()
110 self._logger = logging.getLogger("ns3wrapper")
111 self._logger.setLevel(loglevel)
113 ## NOTE that the reason to create a handler to the ns3 module,
114 # that is re-loaded each time a ns-3 wrapper is instantiated,
115 # is that else each unit test for the ns3wrapper class would need
116 # a separate file. Several ns3wrappers would be created in the
117 # same unit test (single process), leading to inchorences in the
118 # state of ns-3 global objects
120 # Handler to ns3 classes
123 # Collection of allowed ns3 classes
124 self._allowed_types = None
129 # load ns-3 libraries and bindings
130 self._ns3 = load_ns3_module()
135 def allowed_types(self):
136 if not self._allowed_types:
137 self._allowed_types = set()
138 type_id = self.ns3.TypeId()
140 tid_count = type_id.GetRegisteredN()
141 base = type_id.LookupByName("ns3::Object")
143 for i in xrange(tid_count):
144 tid = type_id.GetRegistered(i)
146 if tid.MustHideFromDocumentation() or \
147 not tid.HasConstructor() or \
148 not tid.IsChildOf(base):
151 type_name = tid.GetName()
152 self._allowed_types.add(type_name)
154 return self._allowed_types
161 def is_running(self):
162 return self._started and self.ns3.Simulator.IsFinished()
165 return "uuid%s" % uuid.uuid4()
167 def get_object(self, uuid):
168 return self._objects.get(uuid)
170 def factory(self, type_name, **kwargs):
171 if type_name not in self.allowed_types:
172 msg = "Type %s not supported" % (type_name)
173 self.logger.error(msg)
175 factory = self.ns3.ObjectFactory()
176 factory.SetTypeId(type_name)
178 for name, value in kwargs.iteritems():
179 ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value)
180 factory.Set(name, ns3_value)
182 obj = factory.Create()
184 uuid = self.make_uuid()
185 self._objects[uuid] = obj
189 def create(self, clazzname, *args):
190 if not hasattr(self.ns3, clazzname):
191 msg = "Type %s not supported" % (clazzname)
192 self.logger.error(msg)
194 clazz = getattr(self.ns3, clazzname)
196 # arguments starting with 'uuid' identify ns-3 C++
197 # objects and must be replaced by the actual object
198 realargs = self.replace_args(args)
200 obj = clazz(*realargs)
202 uuid = self.make_uuid()
203 self._objects[uuid] = obj
207 def invoke(self, uuid, operation, *args):
208 if operation == "isAppRunning":
209 return self._is_app_running(uuid)
211 if uuid.startswith(SINGLETON):
212 obj = self._singleton(uuid)
214 obj = self.get_object(uuid)
216 method = getattr(obj, operation)
218 # arguments starting with 'uuid' identify ns-3 C++
219 # objects and must be replaced by the actual object
220 realargs = self.replace_args(args)
222 result = method(*realargs)
227 newuuid = self.make_uuid()
228 self._objects[newuuid] = result
232 def _set_attr(self, obj, name, ns3_value):
233 obj.SetAttribute(name, ns3_value)
235 def set(self, uuid, name, value):
236 obj = self.get_object(uuid)
237 type_name = obj.GetInstanceTypeId().GetName()
238 ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value)
240 # If the Simulation thread is not running,
241 # then there will be no thread-safety problems
242 # in changing the value of an attribute directly.
243 # However, if the simulation is running we need
244 # to set the value by scheduling an event, else
245 # we risk to corrupt the state of the
248 event_executed = [False]
251 # schedule the event in the Simulator
252 self._schedule_event(self._condition, event_executed,
253 self._set_attr, obj, name, ns3_value)
255 if not event_executed[0]:
256 self._set_attr(obj, name, ns3_value)
260 def _get_attr(self, obj, name, ns3_value):
261 obj.GetAttribute(name, ns3_value)
263 def get(self, uuid, name):
264 obj = self.get_object(uuid)
265 type_name = obj.GetInstanceTypeId().GetName()
266 ns3_value = self._create_attr_ns3_value(type_name, name)
268 event_executed = [False]
271 # schedule the event in the Simulator
272 self._schedule_event(self._condition, event_executed,
273 self._get_attr, obj, name, ns3_value)
275 if not event_executed[0]:
276 self._get_attr(obj, name, ns3_value)
278 return self._attr_from_ns3_value_to_string(type_name, name, ns3_value)
281 # Launch the simulator thread and Start the
282 # simulator in that thread
283 self._condition = threading.Condition()
284 self._simulator_thread = threading.Thread(
285 target = self._simulator_run,
286 args = [self._condition])
287 self._simulator_thread.setDaemon(True)
288 self._simulator_thread.start()
291 def stop(self, time = None):
293 self.ns3.Simulator.Stop()
295 self.ns3.Simulator.Stop(self.ns3.Time(time))
298 while not self.ns3.Simulator.IsFinished():
299 #self.logger.debug("Waiting for simulation to finish")
302 if self._simulator_thread:
303 self._simulator_thread.join()
305 self.ns3.Simulator.Destroy()
307 # Remove all references to ns-3 objects
308 self._objects.clear()
313 def _simulator_run(self, condition):
315 self.ns3.Simulator.Run()
316 # Signal condition to indicate simulation ended and
317 # notify waiting threads
319 condition.notifyAll()
322 def _schedule_event(self, condition, event_executed, func, *args):
323 """ Schedules event on running simulation, and wait until
326 def execute_event(contextId, condition, event_executed, func, *args):
329 event_executed[0] = True
331 # notify condition indicating event was executed
333 condition.notifyAll()
336 # contextId is defined as general context
337 contextId = long(0xffffffff)
339 # delay 0 means that the event is expected to execute inmediately
340 delay = self.ns3.Seconds(0)
342 # Mark event as not executed
343 event_executed[0] = False
347 self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event,
348 condition, event_executed, func, *args)
349 if not self.ns3.Simulator.IsFinished():
354 def _create_attr_ns3_value(self, type_name, name):
355 TypeId = self.ns3.TypeId()
356 tid = TypeId.LookupByName(type_name)
357 info = TypeId.AttributeInformation()
358 if not tid.LookupAttributeByName(name, info):
359 msg = "TypeId %s has no attribute %s" % (type_name, name)
360 self.logger.error(msg)
362 checker = info.checker
363 ns3_value = checker.Create()
366 def _attr_from_ns3_value_to_string(self, type_name, name, ns3_value):
367 TypeId = self.ns3.TypeId()
368 tid = TypeId.LookupByName(type_name)
369 info = TypeId.AttributeInformation()
370 if not tid.LookupAttributeByName(name, info):
371 msg = "TypeId %s has no attribute %s" % (type_name, name)
372 self.logger.error(msg)
374 checker = info.checker
375 value = ns3_value.SerializeToString(checker)
377 type_name = checker.GetValueTypeName()
378 if type_name in ["ns3::UintegerValue", "ns3::IntegerValue"]:
380 if type_name == "ns3::DoubleValue":
382 if type_name == "ns3::BooleanValue":
383 return value == "true"
387 def _attr_from_string_to_ns3_value(self, type_name, name, value):
388 TypeId = self.ns3.TypeId()
389 tid = TypeId.LookupByName(type_name)
390 info = TypeId.AttributeInformation()
391 if not tid.LookupAttributeByName(name, info):
392 msg = "TypeId %s has no attribute %s" % (type_name, name)
393 self.logger.error(msg)
395 str_value = str(value)
396 if isinstance(value, bool):
397 str_value = str_value.lower()
399 checker = info.checker
400 ns3_value = checker.Create()
401 ns3_value.DeserializeFromString(str_value, checker)
404 # singletons are identified as "ns3::ClassName"
405 def _singleton(self, ident):
406 if not ident.startswith(SINGLETON):
409 clazzname = ident[ident.find("::")+2:]
410 if not hasattr(self.ns3, clazzname):
411 msg = "Type %s not supported" % (clazzname)
412 self.logger.error(msg)
414 return getattr(self.ns3, clazzname)
416 # replace uuids and singleton references for the real objects
417 def replace_args(self, args):
418 realargs = [self.get_object(arg) if \
419 str(arg).startswith("uuid") else arg for arg in args]
421 realargs = [self._singleton(arg) if \
422 str(arg).startswith(SINGLETON) else arg for arg in realargs]
426 def _is_app_running(self, uuid):
427 now = self.ns3.Simulator.Now()
431 stop_value = self.get(uuid, "StopTime")
432 stop_time = self.ns3.Time(stop_value)
434 start_value = self.get(uuid, "StartTime")
435 start_time = self.ns3.Time(start_value)
437 if now.Compare(start_time) >= 0 and now.Compare(stop_time) <= 0: