Remote ns-3 with ping working
[nepi.git] / src / nepi / resources / ns3 / ns3wrapper.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2013 INRIA
4 #
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.
9 #
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.
14 #
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/>.
17 #
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19
20 import logging
21 import os
22 import sys
23 import threading
24 import time
25 import uuid
26
27 SINGLETON = "singleton::"
28
29 def load_ns3_module():
30     import ctypes
31     import re
32
33     bindings = os.environ.get("NS3BINDINGS")
34     libdir = os.environ.get("NS3LIBRARIES")
35
36     # Load the ns-3 modules shared libraries
37     if libdir:
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]
41
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.
45         while len(libs) > 0:
46             for lib in libs:
47                 libfile = os.path.join(libdir, lib)
48                 try:
49                     ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
50                     libs.remove(lib)
51                 except:
52                     #import traceback
53                     #err = traceback.format_exc()
54                     #print err
55                     pass
56
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)
62
63     # import the python bindings for the ns-3 modules
64     if bindings:
65         sys.path.append(bindings)
66
67     import pkgutil
68     import imp
69     import ns
70
71     # create a Python module to add all ns3 classes
72     ns3mod = imp.new_module("ns3")
73     sys.modules["ns3"] = ns3mod
74
75     for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
76         if modname in [ "visualizer" ]:
77             continue
78
79         fullmodname = "ns.%s" % modname
80         module = __import__(fullmodname, globals(), locals(), ['*'])
81
82         for sattr in dir(module):
83             if sattr.startswith("_"):
84                 continue
85
86             attr = getattr(module, sattr)
87
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)
91
92             setattr(ns3mod, sattr, attr)
93
94     return ns3mod
95
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
102
103         # True if Simulator::Run was invoked
104         self._started = False
105
106         # holds reference to all C++ objects and variables in the simulation
107         self._objects = dict()
108
109         # Logging
110         self._logger = logging.getLogger("ns3wrapper")
111         self._logger.setLevel(loglevel)
112
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
119         #
120         # Handler to ns3 classes
121         self._ns3 = None
122
123         # Collection of allowed ns3 classes
124         self._allowed_types = None
125
126     @property
127     def ns3(self):
128         if not self._ns3:
129             # load ns-3 libraries and bindings
130             self._ns3 = load_ns3_module()
131
132         return self._ns3
133
134     @property
135     def allowed_types(self):
136         if not self._allowed_types:
137             self._allowed_types = set()
138             type_id = self.ns3.TypeId()
139             
140             tid_count = type_id.GetRegisteredN()
141             base = type_id.LookupByName("ns3::Object")
142
143             for i in xrange(tid_count):
144                 tid = type_id.GetRegistered(i)
145                 
146                 if tid.MustHideFromDocumentation() or \
147                         not tid.HasConstructor() or \
148                         not tid.IsChildOf(base): 
149                     continue
150
151                 type_name = tid.GetName()
152                 self._allowed_types.add(type_name)
153         
154         return self._allowed_types
155
156     @property
157     def logger(self):
158         return self._logger
159
160     @property
161     def is_running(self):
162         return self._started and self.ns3.Simulator.IsFinished()
163
164     def make_uuid(self):
165         return "uuid%s" % uuid.uuid4()
166
167     def get_object(self, uuid):
168         return self._objects.get(uuid)
169
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)
174  
175         factory = self.ns3.ObjectFactory()
176         factory.SetTypeId(type_name)
177
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)
181
182         obj = factory.Create()
183
184         uuid = self.make_uuid()
185         self._objects[uuid] = obj
186
187         return uuid
188
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)
193      
194         clazz = getattr(self.ns3, clazzname)
195  
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)
199        
200         obj = clazz(*realargs)
201         
202         uuid = self.make_uuid()
203         self._objects[uuid] = obj
204
205         return uuid
206
207     def invoke(self, uuid, operation, *args):
208         if operation == "isAppRunning":
209             return self._is_app_running(uuid)
210
211         if uuid.startswith(SINGLETON):
212             obj = self._singleton(uuid)
213         else:
214             obj = self.get_object(uuid)
215         
216         method = getattr(obj, operation)
217
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)
221
222         result = method(*realargs)
223
224         if not result:
225             return result
226        
227         newuuid = self.make_uuid()
228         self._objects[newuuid] = result
229
230         return newuuid
231
232     def _set_attr(self, obj, name, ns3_value):
233         obj.SetAttribute(name, ns3_value)
234
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)
239
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
246         # simulation.
247         
248         event_executed = [False]
249
250         if self.is_running:
251             # schedule the event in the Simulator
252             self._schedule_event(self._condition, event_executed, 
253                     self._set_attr, obj, name, ns3_value)
254
255         if not event_executed[0]:
256             self._set_attr(obj, name, ns3_value)
257
258         return value
259
260     def _get_attr(self, obj, name, ns3_value):
261         obj.GetAttribute(name, ns3_value)
262
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)
267
268         event_executed = [False]
269
270         if self.is_running:
271             # schedule the event in the Simulator
272             self._schedule_event(self._condition, event_executed,
273                     self._get_attr, obj, name, ns3_value)
274
275         if not event_executed[0]:
276             self._get_attr(obj, name, ns3_value)
277
278         return self._attr_from_ns3_value_to_string(type_name, name, ns3_value)
279
280     def start(self):
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()
289         self._started = True
290
291     def stop(self, time = None):
292         if time is None:
293             self.ns3.Simulator.Stop()
294         else:
295             self.ns3.Simulator.Stop(self.ns3.Time(time))
296
297     def shutdown(self):
298         while not self.ns3.Simulator.IsFinished():
299             #self.logger.debug("Waiting for simulation to finish")
300             time.sleep(0.5)
301         
302         if self._simulator_thread:
303             self._simulator_thread.join()
304         
305         self.ns3.Simulator.Destroy()
306         
307         # Remove all references to ns-3 objects
308         self._objects.clear()
309         
310         sys.stdout.flush()
311         sys.stderr.flush()
312
313     def _simulator_run(self, condition):
314         # Run simulation
315         self.ns3.Simulator.Run()
316         # Signal condition to indicate simulation ended and
317         # notify waiting threads
318         condition.acquire()
319         condition.notifyAll()
320         condition.release()
321
322     def _schedule_event(self, condition, event_executed, func, *args):
323         """ Schedules event on running simulation, and wait until
324             event is executed"""
325
326         def execute_event(contextId, condition, event_executed, func, *args):
327             try:
328                 func(*args)
329                 event_executed[0] = True
330             finally:
331                 # notify condition indicating event was executed
332                 condition.acquire()
333                 condition.notifyAll()
334                 condition.release()
335
336         # contextId is defined as general context
337         contextId = long(0xffffffff)
338
339         # delay 0 means that the event is expected to execute inmediately
340         delay = self.ns3.Seconds(0)
341     
342         # Mark event as not executed
343         event_executed[0] = False
344
345         condition.acquire()
346         try:
347             self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event, 
348                     condition, event_executed, func, *args)
349             if not self.ns3.Simulator.IsFinished():
350                 condition.wait()
351         finally:
352             condition.release()
353
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)
361
362         checker = info.checker
363         ns3_value = checker.Create() 
364         return ns3_value
365
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)
373
374         checker = info.checker
375         value = ns3_value.SerializeToString(checker)
376
377         type_name = checker.GetValueTypeName()
378         if type_name in ["ns3::UintegerValue", "ns3::IntegerValue"]:
379             return int(value)
380         if type_name == "ns3::DoubleValue":
381             return float(value)
382         if type_name == "ns3::BooleanValue":
383             return value == "true"
384
385         return value
386
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)
394
395         str_value = str(value)
396         if isinstance(value, bool):
397             str_value = str_value.lower()
398
399         checker = info.checker
400         ns3_value = checker.Create()
401         ns3_value.DeserializeFromString(str_value, checker)
402         return ns3_value
403
404     # singletons are identified as "ns3::ClassName"
405     def _singleton(self, ident):
406         if not ident.startswith(SINGLETON):
407             return None
408
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)
413
414         return getattr(self.ns3, clazzname)
415
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]
420  
421         realargs = [self._singleton(arg) if \
422                 str(arg).startswith(SINGLETON) else arg for arg in realargs]
423
424         return realargs
425
426     def _is_app_running(self, uuid): 
427         now = self.ns3.Simulator.Now()
428         if now.IsZero():
429             return False
430
431         stop_value = self.get(uuid, "StopTime")
432         stop_time = self.ns3.Time(stop_value)
433         
434         start_value = self.get(uuid, "StartTime")
435         start_time = self.ns3.Time(start_value)
436         
437         if now.Compare(start_time) >= 0 and now.Compare(stop_time) <= 0:
438             return True
439
440         return False
441