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::"
28 SIMULATOR_UUID = "singleton::Simulator"
29 CONFIG_UUID = "singleton::Config"
30 GLOBAL_VALUE_UUID = "singleton::GlobalValue"
32 def load_ns3_module():
36 bindings = os.environ.get("NS3BINDINGS")
37 libdir = os.environ.get("NS3LIBRARIES")
39 # Load the ns-3 modules shared libraries
41 files = os.listdir(libdir)
42 regex = re.compile("(.*\.so)$")
43 libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m]
45 initial_size = len(libs)
46 # Try to load the libraries in the right order by trial and error.
47 # Loop until all libraries are loaded.
50 libfile = os.path.join(libdir, lib)
52 ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
56 #err = traceback.format_exc()
60 # if did not load any libraries in the last iteration break
61 # to prevent infinit loop
62 if initial_size == len(libs):
63 raise RuntimeError("Imposible to load shared libraries %s" % str(libs))
64 initial_size = list(libs)
66 # import the python bindings for the ns-3 modules
68 sys.path.append(bindings)
74 # create a Python module to add all ns3 classes
75 ns3mod = imp.new_module("ns3")
76 sys.modules["ns3"] = ns3mod
78 for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
79 if modname in [ "visualizer" ]:
82 fullmodname = "ns.%s" % modname
83 module = __import__(fullmodname, globals(), locals(), ['*'])
85 for sattr in dir(module):
86 if sattr.startswith("_"):
89 attr = getattr(module, sattr)
91 # netanim.Config and lte.Config singleton overrides ns3::Config
92 if sattr == "Config" and modname in ['netanim', 'lte']:
93 sattr = "%s.%s" % (modname, sattr)
95 setattr(ns3mod, sattr, attr)
99 class NS3Wrapper(object):
100 def __init__(self, loglevel = logging.INFO):
101 super(NS3Wrapper, self).__init__()
102 # Thread used to run the simulation
103 self._simulation_thread = None
104 self._condition = None
106 # True if Simulator::Run was invoked
107 self._started = False
109 # holds reference to all C++ objects and variables in the simulation
110 self._objects = dict()
113 self._logger = logging.getLogger("ns3wrapper")
114 self._logger.setLevel(loglevel)
116 ## NOTE that the reason to create a handler to the ns3 module,
117 # that is re-loaded each time a ns-3 wrapper is instantiated,
118 # is that else each unit test for the ns3wrapper class would need
119 # a separate file. Several ns3wrappers would be created in the
120 # same unit test (single process), leading to inchorences in the
121 # state of ns-3 global objects
123 # Handler to ns3 classes
126 # Collection of allowed ns3 classes
127 self._allowed_types = None
132 # load ns-3 libraries and bindings
133 self._ns3 = load_ns3_module()
138 def allowed_types(self):
139 if not self._allowed_types:
140 self._allowed_types = set()
141 type_id = self.ns3.TypeId()
143 tid_count = type_id.GetRegisteredN()
144 base = type_id.LookupByName("ns3::Object")
146 for i in xrange(tid_count):
147 tid = type_id.GetRegistered(i)
149 if tid.MustHideFromDocumentation() or \
150 not tid.HasConstructor() or \
151 not tid.IsChildOf(base):
154 type_name = tid.GetName()
155 self._allowed_types.add(type_name)
157 return self._allowed_types
164 def is_running(self):
165 return self._started and self.ns3.Simulator.IsFinished()
168 return "uuid%s" % uuid.uuid4()
170 def get_object(self, uuid):
171 return self._objects.get(uuid)
173 def factory(self, type_name, **kwargs):
174 if type_name not in self.allowed_types:
175 msg = "Type %s not supported" % (type_name)
176 self.logger.error(msg)
178 factory = self.ns3.ObjectFactory()
179 factory.SetTypeId(type_name)
181 for name, value in kwargs.iteritems():
182 ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value)
183 factory.Set(name, ns3_value)
185 obj = factory.Create()
187 uuid = self.make_uuid()
188 self._objects[uuid] = obj
192 def create(self, clazzname, *args):
193 if not hasattr(self.ns3, clazzname):
194 msg = "Type %s not supported" % (clazzname)
195 self.logger.error(msg)
197 clazz = getattr(self.ns3, clazzname)
199 # arguments starting with 'uuid' identify ns-3 C++
200 # objects and must be replaced by the actual object
201 realargs = self.replace_args(args)
203 obj = clazz(*realargs)
205 uuid = self.make_uuid()
206 self._objects[uuid] = obj
210 def invoke(self, uuid, operation, *args, **kwargs):
211 if operation == "isAppRunning":
212 return self._is_app_running(uuid)
214 if uuid.startswith(SINGLETON):
215 obj = self._singleton(uuid)
217 obj = self.get_object(uuid)
219 method = getattr(obj, operation)
221 # arguments starting with 'uuid' identify ns-3 C++
222 # objects and must be replaced by the actual object
223 realargs = self.replace_args(args)
224 realkwargs = self.replace_kwargs(kwargs)
226 result = method(*realargs, **realkwargs)
228 if result is None or \
229 isinstance(result, bool):
232 newuuid = self.make_uuid()
233 self._objects[newuuid] = result
237 def _set_attr(self, obj, name, ns3_value):
238 obj.SetAttribute(name, ns3_value)
240 def set(self, uuid, name, value):
241 obj = self.get_object(uuid)
242 type_name = obj.GetInstanceTypeId().GetName()
243 ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value)
245 # If the Simulation thread is not running,
246 # then there will be no thread-safety problems
247 # in changing the value of an attribute directly.
248 # However, if the simulation is running we need
249 # to set the value by scheduling an event, else
250 # we risk to corrupt the state of the
253 event_executed = [False]
256 # schedule the event in the Simulator
257 self._schedule_event(self._condition, event_executed,
258 self._set_attr, obj, name, ns3_value)
260 if not event_executed[0]:
261 self._set_attr(obj, name, ns3_value)
265 def _get_attr(self, obj, name, ns3_value):
266 obj.GetAttribute(name, ns3_value)
268 def get(self, uuid, name):
269 obj = self.get_object(uuid)
270 type_name = obj.GetInstanceTypeId().GetName()
271 ns3_value = self._create_attr_ns3_value(type_name, name)
273 event_executed = [False]
276 # schedule the event in the Simulator
277 self._schedule_event(self._condition, event_executed,
278 self._get_attr, obj, name, ns3_value)
280 if not event_executed[0]:
281 self._get_attr(obj, name, ns3_value)
283 return self._attr_from_ns3_value_to_string(type_name, name, ns3_value)
286 # Launch the simulator thread and Start the
287 # simulator in that thread
288 self._condition = threading.Condition()
289 self._simulator_thread = threading.Thread(
290 target = self._simulator_run,
291 args = [self._condition])
292 self._simulator_thread.setDaemon(True)
293 self._simulator_thread.start()
296 def stop(self, time = None):
298 self.ns3.Simulator.Stop()
300 self.ns3.Simulator.Stop(self.ns3.Time(time))
303 while not self.ns3.Simulator.IsFinished():
304 #self.logger.debug("Waiting for simulation to finish")
307 if self._simulator_thread:
308 self._simulator_thread.join()
310 self.ns3.Simulator.Destroy()
312 # Remove all references to ns-3 objects
313 self._objects.clear()
318 def _simulator_run(self, condition):
320 self.ns3.Simulator.Run()
321 # Signal condition to indicate simulation ended and
322 # notify waiting threads
324 condition.notifyAll()
327 def _schedule_event(self, condition, event_executed, func, *args):
328 """ Schedules event on running simulation, and wait until
331 def execute_event(contextId, condition, event_executed, func, *args):
334 event_executed[0] = True
336 # notify condition indicating event was executed
338 condition.notifyAll()
341 # contextId is defined as general context
342 contextId = long(0xffffffff)
344 # delay 0 means that the event is expected to execute inmediately
345 delay = self.ns3.Seconds(0)
347 # Mark event as not executed
348 event_executed[0] = False
352 self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event,
353 condition, event_executed, func, *args)
354 if not self.ns3.Simulator.IsFinished():
359 def _create_attr_ns3_value(self, type_name, name):
360 TypeId = self.ns3.TypeId()
361 tid = TypeId.LookupByName(type_name)
362 info = TypeId.AttributeInformation()
363 if not tid.LookupAttributeByName(name, info):
364 msg = "TypeId %s has no attribute %s" % (type_name, name)
365 self.logger.error(msg)
367 checker = info.checker
368 ns3_value = checker.Create()
371 def _attr_from_ns3_value_to_string(self, type_name, name, ns3_value):
372 TypeId = self.ns3.TypeId()
373 tid = TypeId.LookupByName(type_name)
374 info = TypeId.AttributeInformation()
375 if not tid.LookupAttributeByName(name, info):
376 msg = "TypeId %s has no attribute %s" % (type_name, name)
377 self.logger.error(msg)
379 checker = info.checker
380 value = ns3_value.SerializeToString(checker)
382 type_name = checker.GetValueTypeName()
383 if type_name in ["ns3::UintegerValue", "ns3::IntegerValue"]:
385 if type_name == "ns3::DoubleValue":
387 if type_name == "ns3::BooleanValue":
388 return value == "true"
392 def _attr_from_string_to_ns3_value(self, type_name, name, value):
393 TypeId = self.ns3.TypeId()
394 tid = TypeId.LookupByName(type_name)
395 info = TypeId.AttributeInformation()
396 if not tid.LookupAttributeByName(name, info):
397 msg = "TypeId %s has no attribute %s" % (type_name, name)
398 self.logger.error(msg)
400 str_value = str(value)
401 if isinstance(value, bool):
402 str_value = str_value.lower()
404 checker = info.checker
405 ns3_value = checker.Create()
406 ns3_value.DeserializeFromString(str_value, checker)
409 # singletons are identified as "ns3::ClassName"
410 def _singleton(self, ident):
411 if not ident.startswith(SINGLETON):
414 clazzname = ident[ident.find("::")+2:]
415 if not hasattr(self.ns3, clazzname):
416 msg = "Type %s not supported" % (clazzname)
417 self.logger.error(msg)
419 return getattr(self.ns3, clazzname)
421 # replace uuids and singleton references for the real objects
422 def replace_args(self, args):
423 realargs = [self.get_object(arg) if \
424 str(arg).startswith("uuid") else arg for arg in args]
426 realargs = [self._singleton(arg) if \
427 str(arg).startswith(SINGLETON) else arg for arg in realargs]
431 # replace uuids and singleton references for the real objects
432 def replace_kwargs(self, kwargs):
433 realkwargs = dict([(k, self.get_object(v) \
434 if str(v).startswith("uuid") else v) \
435 for k,v in kwargs.iteritems()])
437 realkwargs = dict([(k, self._singleton(v) \
438 if str(v).startswith(SINGLETON) else v )\
439 for k, v in realkwargs.iteritems()])
443 def _is_app_running(self, uuid):
444 now = self.ns3.Simulator.Now()
448 stop_value = self.get(uuid, "StopTime")
449 stop_time = self.ns3.Time(stop_value)
451 start_value = self.get(uuid, "StartTime")
452 start_time = self.ns3.Time(start_value)
454 self.logger.debug("NOW %s" % now.GetSeconds())
455 self.logger.debug("START TIME %s" % start_value)
456 self.logger.debug("STOP TIME %s" % stop_value)
458 if now.Compare(start_time) >= 0 and now.Compare(stop_time) < 0: