Merge neco to nepi-3.0
[nepi.git] / src / nepi / resources / ns3 / ns3wrapper.py
1 import logging
2 import os
3 import sys
4 import threading
5 import uuid
6
7 class NS3Wrapper(object):
8     def __init__(self, homedir = None):
9         super(NS3Wrapper, self).__init__()
10         self._ns3 = None
11         self._uuid = self.make_uuid()
12         self._homedir = homedir or os.path.join("/tmp", self._uuid)
13         self._simulation_thread = None
14         self._condition = None
15
16         self._started = False
17         self._stopped = False
18
19         # holds reference to all ns-3 objects in the simulation
20         self._resources = dict()
21
22         # create home dir (where all simulation related files will end up)
23         home = os.path.normpath(self.homedir)
24         if not os.path.exists(home):
25             os.makedirs(home, 0755)
26
27         # Logging
28         loglevel = os.environ.get("NS3LOGLEVEL", "debug")
29         self._logger = logging.getLogger("ns3wrapper.%s" % self.uuid)
30         self._logger.setLevel(getattr(logging, loglevel.upper()))
31         hdlr = logging.FileHandler(os.path.join(self.homedir, "ns3wrapper.log"))
32         formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
33         hdlr.setFormatter(formatter)
34         self._logger.addHandler(hdlr) 
35
36         # Load ns-3 shared libraries and import modules
37         self._load_ns3_module()
38         
39     @property
40     def ns3(self):
41         return self._ns3
42
43     @property
44     def homedir(self):
45         return self._homedir
46
47     @property
48     def uuid(self):
49         return self._uuid
50
51     @property
52     def logger(self):
53         return self._logger
54
55     def make_uuid(self):
56         return "uuid%s" % uuid.uuid4()
57
58     def singleton(self, clazzname):
59         uuid = "uuid%s"%clazzname
60
61         if not uuid in self._resources:
62             if not hasattr(self.ns3, clazzname):
63                 msg = "Type %s not supported" % (typeid) 
64                 self.logger.error(msg)
65
66             clazz = getattr(self.ns3, clazzname)
67             typeid = "ns3::%s" % clazzname
68             self._resources[uuid] = (clazz, typeid)
69
70         return uuid
71
72     def get_trace(self, trace, offset = None, nbytes = None ):
73         pass
74
75     def is_running(self):
76         return self._started and not self._stopped
77
78     def get_resource(self, uuid):
79         (resource, typeid) =  self._resources.get(uuid)
80         return resource
81     
82     def get_typeid(self, uuid):
83         (resource, typeid) =  self._resources.get(uuid)
84         return typeid
85
86     def create(self, clazzname, *args):
87         if not hasattr(self.ns3, clazzname):
88             msg = "Type %s not supported" % (clazzname) 
89             self.logger.error(msg)
90
91         clazz = getattr(self.ns3, clazzname)
92         #typeid = clazz.GetInstanceTypeId().GetName()
93         typeid = "ns3::%s" % clazzname
94
95         realargs = [self.get_resource(arg) if \
96                 str(arg).startswith("uuid") else arg for arg in args]
97       
98         resource = clazz(*realargs)
99         
100         uuid = self.make_uuid()
101         self._resources[uuid] = (resource, typeid)
102         return uuid
103
104     def set(self, uuid, name, value):
105         resource = self.get_resource(uuid)
106
107         if hasattr(resource, name):
108             setattr(resource, name, value)
109         else:
110             self._set_ns3_attr(uuid, name, value)
111
112     def get(self, name, uuid = None):
113         resource = self.get_resource(uuid)
114
115         value = None
116         if hasattr(resource, name):
117             value = getattr(resource, name)
118         else:
119             value = self._get_ns3_attr(uuid, name)
120
121         return value
122
123     def invoke(self, uuid, operation, *args):
124         resource = self.get_resource(uuid)
125         typeid = self.get_typeid(uuid)
126         method = getattr(resource, operation)
127
128         realargs = [self.get_resource(arg) if \
129                 str(arg).startswith("uuid") else arg for arg in args]
130
131         result = method(*realargs)
132
133         if not result:
134             return None
135         
136         uuid = self.make_uuid()
137         self._resources[uuid] = (result, typeid)
138
139         return uuid
140
141     def start(self):
142         self._condition = threading.Condition()
143         self._simulator_thread = threading.Thread(
144                 target = self._simulator_run,
145                 args = [self._condition])
146         self._simulator_thread.setDaemon(True)
147         self._simulator_thread.start()
148         self._started = True
149
150     def stop(self, time = None):
151         if not self.ns3:
152             return
153
154         if time is None:
155             self.ns3.Simulator.Stop()
156         else:
157             self.ns3.Simulator.Stop(self.ns3.Time(time))
158         self._stopped = True
159
160     def shutdown(self):
161         if self.ns3:
162             if not self.ns3.Simulator.IsFinished():
163                 self.stop()
164             
165             # TODO!!!! SHOULD WAIT UNTIL THE THREAD FINISHES
166             if self._simulator_thread:
167                 self._simulator_thread.join()
168             
169             self.ns3.Simulator.Destroy()
170         
171         self._resources.clear()
172         
173         self._ns3 = None
174         sys.stdout.flush()
175         sys.stderr.flush()
176
177     def _simulator_run(self, condition):
178         # Run simulation
179         self.ns3.Simulator.Run()
180         # Signal condition on simulation end to notify waiting threads
181         condition.acquire()
182         condition.notifyAll()
183         condition.release()
184
185     def _schedule_event(self, condition, func, *args):
186         """ Schedules event on running simulation, and wait until
187             event is executed"""
188
189         def execute_event(contextId, condition, has_event_occurred, func, *args):
190             try:
191                 func(*args)
192             finally:
193                 # flag event occured
194                 has_event_occurred[0] = True
195                 # notify condition indicating attribute was set
196                 condition.acquire()
197                 condition.notifyAll()
198                 condition.release()
199
200         # contextId is defined as general context
201         contextId = long(0xffffffff)
202
203         # delay 0 means that the event is expected to execute inmediately
204         delay = self.ns3.Seconds(0)
205
206         # flag to indicate that the event occured
207         # because bool is an inmutable object in python, in order to create a
208         # bool flag, a list is used as wrapper
209         has_event_occurred = [False]
210         condition.acquire()
211         try:
212             if not self.ns3.Simulator.IsFinished():
213                 self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event,
214                      condition, has_event_occurred, func, *args)
215                 while not has_event_occurred[0] and not self.ns3.Simulator.IsFinished():
216                     condition.wait()
217         finally:
218             condition.release()
219
220     def _set_ns3_attr(self, uuid, name, value):
221         resource = self.get_resource(uuid)
222         ns3_value = self._to_ns3_value(uuid, name, value)
223
224         def set_attr(resource, name, ns3_value):
225             resource.SetAttribute(name, ns3_value)
226
227         if self._is_running:
228             # schedule the event in the Simulator
229             self._schedule_event(self._condition, set_attr, resource,
230                     name, ns3_value)
231         else:
232             set_attr(resource, name, ns3_value)
233
234     def _get_ns3_attr(self, uuid, name):
235         resource = self.get_resource(uuid)
236         ns3_value = self._create_ns3_value(uuid, name)
237
238         def get_attr(resource, name, ns3_value):
239             resource.GetAttribute(name, ns3_value)
240
241         if self._is_running:
242             # schedule the event in the Simulator
243             self._schedule_event(self._condition, get_attr, resource,
244                     name, ns3_value)
245         else:
246             get_attr(resource, name, ns3_value)
247
248         return self._from_ns3_value(uuid, name, ns3_value)
249
250     def _create_ns3_value(self, uuid, name):
251         typeid = get_typeid(uuid)
252         TypeId = self.ns3.TypeId()
253         tid = TypeId.LookupByName(typeid)
254         info = TypeId.AttributeInformation()
255         if not tid.LookupAttributeByName(name, info):
256             msg = "TypeId %s has no attribute %s" % (typeid, name) 
257             self.logger.error(msg)
258
259         checker = info.checker
260         ns3_value = checker.Create() 
261         return ns3_value
262
263     def _from_ns3_value(self, uuid, name, ns3_value):
264         typeid = get_typeid(uuid)
265         TypeId = self.ns3.TypeId()
266         tid = TypeId.LookupByName(typeid)
267         info = TypeId.AttributeInformation()
268         if not tid.LookupAttributeByName(name, info):
269             msg = "TypeId %s has no attribute %s" % (typeid, name) 
270             self.logger.error(msg)
271
272         checker = info.checker
273         value = ns3_value.SerializeToString(checker)
274
275         type_name = checker.GetValueTypeName()
276         if type_name in ["ns3::UintegerValue", "ns3::IntegerValue"]:
277             return int(value)
278         if type_name == "ns3::DoubleValue":
279             return float(value)
280         if type_name == "ns3::BooleanValue":
281             return value == "true"
282
283         return value
284
285     def _to_ns3_value(self, uuid, name, value):
286         typeid = get_typeid(uuid)
287         TypeId = self.ns3.TypeId()
288         typeid = TypeId.LookupByName(typeid)
289         info = TypeId.AttributeInformation()
290         if not tid.LookupAttributeByName(name, info):
291             msg = "TypeId %s has no attribute %s" % (typeid, name) 
292             self.logger.error(msg)
293
294         str_value = str(value)
295         if isinstance(value, bool):
296             str_value = str_value.lower()
297
298         checker = info.checker
299         ns3_value = checker.Create()
300         ns3_value.DeserializeFromString(str_value, checker)
301         return ns3_value
302
303     def _load_ns3_module(self):
304         if self.ns3:
305             return 
306
307         import ctypes
308         import imp
309         import re
310         import pkgutil
311
312         bindings = os.environ.get("NS3BINDINGS")
313         libdir = os.environ.get("NS3LIBRARIES")
314
315         # Load the ns-3 modules shared libraries
316         if libdir:
317             files = os.listdir(libdir)
318             regex = re.compile("(.*\.so)$")
319             libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m]
320
321             libscp = list(libs)
322             while len(libs) > 0:
323                 for lib in libs:
324                     libfile = os.path.join(libdir, lib)
325                     try:
326                         ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
327                         libs.remove(lib)
328                     except:
329                         pass
330
331                 # if did not load any libraries in the last iteration break
332                 # to prevent infinit loop
333                 if len(libscp) == len(libs):
334                     raise RuntimeError("Imposible to load shared libraries %s" % str(libs))
335                 libscp = list(libs)
336
337         # import the python bindings for the ns-3 modules
338         if bindings:
339             sys.path.append(bindings)
340
341         # create a module to add all ns3 classes
342         ns3mod = imp.new_module("ns3")
343         sys.modules["ns3"] = ns3mod
344
345         # retrieve all ns3 classes and add them to the ns3 module
346         import ns
347         for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
348             fullmodname = "ns.%s" % modname
349             module = __import__(fullmodname, globals(), locals(), ['*'])
350
351             # netanim.Config singleton overrides ns3::Config
352             if modname in ['netanim']:
353                 continue
354
355             for sattr in dir(module):
356                 if not sattr.startswith("_"):
357                     attr = getattr(module, sattr)
358                     setattr(ns3mod, sattr, attr)
359
360         self._ns3 = ns3mod
361