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 class NS3Wrapper(object):
34 def __init__(self, homedir = None):
35 super(NS3Wrapper, self).__init__()
36 # Thread used to run the simulation
37 self._simulation_thread = None
38 self._condition = None
42 # holds reference to all C++ objects and variables in the simulation
43 self._objects = dict()
45 # holds the class identifiers of uuid to be able to retrieve
46 # the corresponding ns3 TypeId to set/get attributes.
47 # This is necessary because the method GetInstanceTypeId is not
48 # exposed through the Python bindings
51 # create home dir (where all simulation related files will end up)
52 self._homedir = homedir or os.path.join("/", "tmp", "ns3_wrapper" )
54 home = os.path.normpath(self.homedir)
55 if not os.path.exists(home):
56 os.makedirs(home, 0755)
59 loglevel = os.environ.get("NS3LOGLEVEL", "debug")
60 self._logger = logging.getLogger("ns3wrapper")
61 self._logger.setLevel(getattr(logging, loglevel.upper()))
63 hdlr = logging.FileHandler(os.path.join(self.homedir, "ns3wrapper.log"))
64 formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
65 hdlr.setFormatter(formatter)
67 self._logger.addHandler(hdlr)
69 # Python module to refernce all ns-3 classes and types
72 # Load ns-3 shared libraries and import modules
73 self._load_ns3_module()
89 return self._started and self._ns3 and not self.ns3.Simulator.IsFinished()
92 return "uuid%s" % uuid.uuid4()
94 def get_object(self, uuid):
95 return self._objects.get(uuid)
97 def get_typeid(self, uuid):
98 return self._tids.get(uuid)
100 def create(self, clazzname, *args):
101 if not hasattr(self.ns3, clazzname):
102 msg = "Type %s not supported" % (clazzname)
103 self.logger.error(msg)
105 clazz = getattr(self.ns3, clazzname)
107 # arguments starting with 'uuid' identify ns-3 C++
108 # objects and must be replaced by the actual object
109 realargs = self.replace_args(args)
111 obj = clazz(*realargs)
113 uuid = self.make_uuid()
114 self._objects[uuid] = obj
116 #typeid = clazz.GetInstanceTypeId().GetName()
117 typeid = "ns3::%s" % clazzname
118 self._tids[uuid] = typeid
122 def invoke(self, uuid, operation, *args):
123 if uuid.startswith(SINGLETON):
124 obj = self._singleton(uuid)
126 obj = self.get_object(uuid)
128 method = getattr(obj, operation)
130 # arguments starting with 'uuid' identify ns-3 C++
131 # objects and must be replaced by the actual object
132 realargs = self.replace_args(args)
134 result = method(*realargs)
139 newuuid = self.make_uuid()
140 self._objects[newuuid] = result
144 def set(self, uuid, name, value):
145 obj = self.get_object(uuid)
146 ns3_value = self._to_ns3_value(uuid, name, value)
148 def set_attr(obj, name, ns3_value):
149 obj.SetAttribute(name, ns3_value)
151 # If the Simulation thread is not running,
152 # then there will be no thread-safety problems
153 # in changing the value of an attribute directly.
154 # However, if the simulation is running we need
155 # to set the value by scheduling an event, else
156 # we risk to corrupt the state of the
159 # schedule the event in the Simulator
160 self._schedule_event(self._condition, set_attr, obj,
163 set_attr(obj, name, ns3_value)
167 def get(self, uuid, name):
168 obj = self.get_object(uuid)
169 ns3_value = self._create_ns3_value(uuid, name)
171 def get_attr(obj, name, ns3_value):
172 obj.GetAttribute(name, ns3_value)
175 # schedule the event in the Simulator
176 self._schedule_event(self._condition, get_attr, obj,
179 get_attr(obj, name, ns3_value)
181 return self._from_ns3_value(uuid, name, ns3_value)
184 # Launch the simulator thread and Start the
185 # simulator in that thread
186 self._condition = threading.Condition()
187 self._simulator_thread = threading.Thread(
188 target = self._simulator_run,
189 args = [self._condition])
190 self._simulator_thread.setDaemon(True)
191 self._simulator_thread.start()
194 def stop(self, time = None):
199 self.ns3.Simulator.Stop()
201 self.ns3.Simulator.Stop(self.ns3.Time(time))
205 while not self.ns3.Simulator.IsFinished():
206 #self.logger.debug("Waiting for simulation to finish")
209 # TODO!!!! SHOULD WAIT UNTIL THE THREAD FINISHES
210 if self._simulator_thread:
211 self._simulator_thread.join()
213 self.ns3.Simulator.Destroy()
215 # Remove all references to ns-3 objects
216 self._objects.clear()
222 def _simulator_run(self, condition):
224 self.ns3.Simulator.Run()
225 # Signal condition to indicate simulation ended and
226 # notify waiting threads
228 condition.notifyAll()
231 def _schedule_event(self, condition, func, *args):
232 """ Schedules event on running simulation, and wait until
235 def execute_event(contextId, condition, has_event_occurred, func, *args):
240 has_event_occurred[0] = True
241 # notify condition indicating attribute was set
243 condition.notifyAll()
246 # contextId is defined as general context
247 contextId = long(0xffffffff)
249 # delay 0 means that the event is expected to execute inmediately
250 delay = self.ns3.Seconds(0)
252 # flag to indicate that the event occured
253 # because bool is an inmutable object in python, in order to create a
254 # bool flag, a list is used as wrapper
255 has_event_occurred = [False]
258 if not self.ns3.Simulator.IsFinished():
259 self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event,
260 condition, has_event_occurred, func, *args)
261 while not has_event_occurred[0] and not self.ns3.Simulator.IsFinished():
266 def _create_ns3_value(self, uuid, name):
267 typeid = self.get_typeid(uuid)
268 TypeId = self.ns3.TypeId()
269 tid = TypeId.LookupByName(typeid)
270 info = TypeId.AttributeInformation()
271 if not tid.LookupAttributeByName(name, info):
272 msg = "TypeId %s has no attribute %s" % (typeid, name)
273 self.logger.error(msg)
275 checker = info.checker
276 ns3_value = checker.Create()
279 def _from_ns3_value(self, uuid, name, ns3_value):
280 typeid = self.get_typeid(uuid)
281 TypeId = self.ns3.TypeId()
282 tid = TypeId.LookupByName(typeid)
283 info = TypeId.AttributeInformation()
284 if not tid.LookupAttributeByName(name, info):
285 msg = "TypeId %s has no attribute %s" % (typeid, name)
286 self.logger.error(msg)
288 checker = info.checker
289 value = ns3_value.SerializeToString(checker)
291 type_name = checker.GetValueTypeName()
292 if type_name in ["ns3::UintegerValue", "ns3::IntegerValue"]:
294 if type_name == "ns3::DoubleValue":
296 if type_name == "ns3::BooleanValue":
297 return value == "true"
301 def _to_ns3_value(self, uuid, name, value):
302 typeid = self.get_typeid(uuid)
303 TypeId = self.ns3.TypeId()
304 tid = TypeId.LookupByName(typeid)
305 info = TypeId.AttributeInformation()
306 if not tid.LookupAttributeByName(name, info):
307 msg = "TypeId %s has no attribute %s" % (typeid, name)
308 self.logger.error(msg)
310 str_value = str(value)
311 if isinstance(value, bool):
312 str_value = str_value.lower()
314 checker = info.checker
315 ns3_value = checker.Create()
316 ns3_value.DeserializeFromString(str_value, checker)
319 # singletons are identified as "ns3::ClassName"
320 def _singleton(self, ident):
321 if not ident.startswith(SINGLETON):
324 clazzname = ident[ident.find("::")+2:]
325 if not hasattr(self.ns3, clazzname):
326 msg = "Type %s not supported" % (clazzname)
327 self.logger.error(msg)
329 return getattr(self.ns3, clazzname)
331 # replace uuids and singleton references for the real objects
332 def replace_args(self, args):
333 realargs = [self.get_object(arg) if \
334 str(arg).startswith("uuid") else arg for arg in args]
336 realargs = [self._singleton(arg) if \
337 str(arg).startswith(SINGLETON) else arg for arg in realargs]
341 def _load_ns3_module(self):
350 bindings = os.environ.get("NS3BINDINGS")
351 libdir = os.environ.get("NS3LIBRARIES")
353 # Load the ns-3 modules shared libraries
355 files = os.listdir(libdir)
356 regex = re.compile("(.*\.so)$")
357 libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m]
362 libfile = os.path.join(libdir, lib)
364 ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
369 # if did not load any libraries in the last iteration break
370 # to prevent infinit loop
371 if len(libscp) == len(libs):
372 raise RuntimeError("Imposible to load shared libraries %s" % str(libs))
375 # import the python bindings for the ns-3 modules
377 sys.path.append(bindings)
379 # create a module to add all ns3 classes
380 ns3mod = imp.new_module("ns3")
381 sys.modules["ns3"] = ns3mod
383 # retrieve all ns3 classes and add them to the ns3 module
386 for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
387 fullmodname = "ns.%s" % modname
388 module = __import__(fullmodname, globals(), locals(), ['*'])
390 for sattr in dir(module):
391 if sattr.startswith("_"):
394 attr = getattr(module, sattr)
396 # netanim.Config and lte.Config singleton overrides ns3::Config
397 if sattr == "Config" and modname in ['netanim', 'lte']:
398 sattr = "%s.%s" % (modname, sattr)
400 setattr(ns3mod, sattr, attr)