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]
45 libfile = os.path.join(libdir, lib)
47 ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
52 # if did not load any libraries in the last iteration break
53 # to prevent infinit loop
54 if len(libscp) == len(libs):
55 raise RuntimeError("Imposible to load shared libraries %s" % str(libs))
58 # import the python bindings for the ns-3 modules
60 sys.path.append(bindings)
66 # create a module to add all ns3 classes
67 ns3mod = imp.new_module("ns3")
68 sys.modules["ns3"] = ns3mod
70 for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
71 fullmodname = "ns.%s" % modname
72 module = __import__(fullmodname, globals(), locals(), ['*'])
74 for sattr in dir(module):
75 if sattr.startswith("_"):
78 attr = getattr(module, sattr)
80 # netanim.Config and lte.Config singleton overrides ns3::Config
81 if sattr == "Config" and modname in ['netanim', 'lte']:
82 sattr = "%s.%s" % (modname, sattr)
84 setattr(ns3mod, sattr, attr)
88 class NS3Wrapper(object):
89 def __init__(self, homedir = None, loglevel = logging.INFO):
90 super(NS3Wrapper, self).__init__()
91 # Thread used to run the simulation
92 self._simulation_thread = None
93 self._condition = None
95 # XXX: Started should be global. There is no support for more than
96 # one simulator per process
99 # holds reference to all C++ objects and variables in the simulation
100 self._objects = dict()
102 # create home dir (where all simulation related files will end up)
103 self._homedir = homedir or os.path.join("/", "tmp", "ns3_wrapper" )
105 home = os.path.normpath(self.homedir)
106 if not os.path.exists(home):
107 os.makedirs(home, 0755)
110 self._logger = logging.getLogger("ns3wrapper")
111 self._logger.setLevel(loglevel)
113 hdlr = logging.FileHandler(os.path.join(self.homedir, "ns3wrapper.log"))
114 formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
115 hdlr.setFormatter(formatter)
117 self._logger.addHandler(hdlr)
119 ## NOTE that the reason to create a handler to the ns3 module,
120 # that is re-loaded each time a ns-3 wrapper is instantiated,
121 # is that else each unit test for the ns3wrapper class would need
122 # a separate file. Several ns3wrappers would be created in the
123 # same unit test (single process), leading to inchorences in the
124 # state of ns-3 global objects
126 # Handler to ns3 classes
129 # Collection of allowed ns3 classes
130 self._allowed_types = None
135 # load ns-3 libraries and bindings
136 self._ns3 = load_ns3_module()
141 def allowed_types(self):
142 if not self._allowed_types:
143 self._allowed_types = set()
144 type_id = self.ns3.TypeId()
146 tid_count = type_id.GetRegisteredN()
147 base = type_id.LookupByName("ns3::Object")
149 # Create a .py file using the ns-3 RM template for each ns-3 TypeId
150 for i in xrange(tid_count):
151 tid = type_id.GetRegistered(i)
153 if tid.MustHideFromDocumentation() or \
154 not tid.HasConstructor() or \
155 not tid.IsChildOf(base):
158 type_name = tid.GetName()
159 self._allowed_types.add(type_name)
161 return self._allowed_types
172 def is_running(self):
173 return self._started and self.ns3.Simulator.IsFinished()
176 return "uuid%s" % uuid.uuid4()
178 def get_object(self, uuid):
179 return self._objects.get(uuid)
181 def factory(self, type_name, **kwargs):
182 if type_name not in self.allowed_types:
183 msg = "Type %s not supported" % (type_name)
184 self.logger.error(msg)
186 factory = self.ns3.ObjectFactory()
187 factory.SetTypeId(type_name)
189 for name, value in kwargs.iteritems():
190 ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value)
191 factory.Set(name, ns3_value)
193 obj = factory.Create()
195 uuid = self.make_uuid()
196 self._objects[uuid] = obj
200 def create(self, clazzname, *args):
201 if not hasattr(self.ns3, clazzname):
202 msg = "Type %s not supported" % (clazzname)
203 self.logger.error(msg)
205 clazz = getattr(self.ns3, clazzname)
207 # arguments starting with 'uuid' identify ns-3 C++
208 # objects and must be replaced by the actual object
209 realargs = self.replace_args(args)
211 obj = clazz(*realargs)
213 uuid = self.make_uuid()
214 self._objects[uuid] = obj
218 def invoke(self, uuid, operation, *args):
219 if uuid.startswith(SINGLETON):
220 obj = self._singleton(uuid)
222 obj = self.get_object(uuid)
224 method = getattr(obj, operation)
226 # arguments starting with 'uuid' identify ns-3 C++
227 # objects and must be replaced by the actual object
228 realargs = self.replace_args(args)
230 result = method(*realargs)
235 newuuid = self.make_uuid()
236 self._objects[newuuid] = result
240 def _set_attr(self, obj, name, ns3_value):
241 obj.SetAttribute(name, ns3_value)
243 def set(self, uuid, name, value):
244 obj = self.get_object(uuid)
245 type_name = obj.GetInstanceTypeId().GetName()
246 ns3_value = self._attr_from_string_to_ns3_value(type_name, name, value)
248 # If the Simulation thread is not running,
249 # then there will be no thread-safety problems
250 # in changing the value of an attribute directly.
251 # However, if the simulation is running we need
252 # to set the value by scheduling an event, else
253 # we risk to corrupt the state of the
256 # schedule the event in the Simulator
257 self._schedule_event(self._condition, self._set_attr,
258 obj, name, ns3_value)
260 self._set_attr(obj, name, ns3_value)
264 def _get_attr(self, obj, name, ns3_value):
265 obj.GetAttribute(name, ns3_value)
267 def get(self, uuid, name):
268 obj = self.get_object(uuid)
269 type_name = obj.GetInstanceTypeId().GetName()
270 ns3_value = self._create_attr_ns3_value(type_name, name)
273 # schedule the event in the Simulator
274 self._schedule_event(self._condition, self._get_attr, obj,
277 get_attr(obj, name, ns3_value)
279 return self._attr_from_ns3_value_to_string(type_name, name, ns3_value)
282 # Launch the simulator thread and Start the
283 # simulator in that thread
284 self._condition = threading.Condition()
285 self._simulator_thread = threading.Thread(
286 target = self._simulator_run,
287 args = [self._condition])
288 self._simulator_thread.setDaemon(True)
289 self._simulator_thread.start()
292 def stop(self, time = None):
294 self.ns3.Simulator.Stop()
296 self.ns3.Simulator.Stop(self.ns3.Time(time))
299 while not self.ns3.Simulator.IsFinished():
300 #self.logger.debug("Waiting for simulation to finish")
303 # TODO!!!! SHOULD WAIT UNTIL THE THREAD FINISHES
304 if self._simulator_thread:
305 self._simulator_thread.join()
307 self.ns3.Simulator.Destroy()
309 # Remove all references to ns-3 objects
310 self._objects.clear()
315 def _simulator_run(self, condition):
317 self.ns3.Simulator.Run()
318 # Signal condition to indicate simulation ended and
319 # notify waiting threads
321 condition.notifyAll()
324 def _schedule_event(self, condition, func, *args):
325 """ Schedules event on running simulation, and wait until
328 def execute_event(contextId, condition, has_event_occurred, func, *args):
333 has_event_occurred[0] = True
334 # notify condition indicating attribute was set
336 condition.notifyAll()
339 # contextId is defined as general context
340 contextId = long(0xffffffff)
342 # delay 0 means that the event is expected to execute inmediately
343 delay = self.ns3.Seconds(0)
345 # flag to indicate that the event occured
346 # because bool is an inmutable object in python, in order to create a
347 # bool flag, a list is used as wrapper
348 has_event_occurred = [False]
351 simu = self.ns3.Simulator
354 if not simu.IsFinished():
355 simu.ScheduleWithContext(contextId, delay, execute_event,
356 condition, has_event_occurred, func, *args)
357 while not has_event_occurred[0] and not simu.IsFinished():
362 def _create_attr_ns3_value(self, type_name, name):
363 TypeId = self.ns3.TypeId()
364 tid = TypeId.LookupByName(type_name)
365 info = TypeId.AttributeInformation()
366 if not tid.LookupAttributeByName(name, info):
367 msg = "TypeId %s has no attribute %s" % (type_name, name)
368 self.logger.error(msg)
370 checker = info.checker
371 ns3_value = checker.Create()
374 def _attr_from_ns3_value_to_string(self, type_name, name, ns3_value):
375 TypeId = self.ns3.TypeId()
376 tid = TypeId.LookupByName(type_name)
377 info = TypeId.AttributeInformation()
378 if not tid.LookupAttributeByName(name, info):
379 msg = "TypeId %s has no attribute %s" % (type_name, name)
380 self.logger.error(msg)
382 checker = info.checker
383 value = ns3_value.SerializeToString(checker)
385 type_name = checker.GetValueTypeName()
386 if type_name in ["ns3::UintegerValue", "ns3::IntegerValue"]:
388 if type_name == "ns3::DoubleValue":
390 if type_name == "ns3::BooleanValue":
391 return value == "true"
395 def _attr_from_string_to_ns3_value(self, type_name, name, value):
396 TypeId = self.ns3.TypeId()
397 tid = TypeId.LookupByName(type_name)
398 info = TypeId.AttributeInformation()
399 if not tid.LookupAttributeByName(name, info):
400 msg = "TypeId %s has no attribute %s" % (type_name, name)
401 self.logger.error(msg)
403 str_value = str(value)
404 if isinstance(value, bool):
405 str_value = str_value.lower()
407 checker = info.checker
408 ns3_value = checker.Create()
409 ns3_value.DeserializeFromString(str_value, checker)
412 # singletons are identified as "ns3::ClassName"
413 def _singleton(self, ident):
414 if not ident.startswith(SINGLETON):
417 clazzname = ident[ident.find("::")+2:]
418 if not hasattr(self.ns3, clazzname):
419 msg = "Type %s not supported" % (clazzname)
420 self.logger.error(msg)
422 return getattr(self.ns3, clazzname)
424 # replace uuids and singleton references for the real objects
425 def replace_args(self, args):
426 realargs = [self.get_object(arg) if \
427 str(arg).startswith("uuid") else arg for arg in args]
429 realargs = [self._singleton(arg) if \
430 str(arg).startswith(SINGLETON) else arg for arg in realargs]