#
# NEPI, a framework to manage network experiments
# Copyright (C) 2013 INRIA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# Author: Alina Quereilhac
import logging
import os
import sys
import threading
import uuid
class NS3Wrapper(object):
def __init__(self, homedir = None):
super(NS3Wrapper, self).__init__()
# Thread used to run the simulation
self._simulation_thread = None
self._condition = None
self._started = False
self._stopped = False
# holds reference to all C++ objects and variables in the simulation
self._objects = dict()
# holds the class identifiers of uuid to be able to retrieve
# the corresponding ns3 TypeId to set/get attributes.
# This is necessary because the method GetInstanceTypeId is not
# exposed through the Python bindings
self._tids = dict()
# Generate unique identifier for the simulation wrapper
self._uuid = self.make_uuid()
# create home dir (where all simulation related files will end up)
self._homedir = homedir or os.path.join("/tmp", self.uuid)
home = os.path.normpath(self.homedir)
if not os.path.exists(home):
os.makedirs(home, 0755)
# Logging
loglevel = os.environ.get("NS3LOGLEVEL", "debug")
self._logger = logging.getLogger("ns3wrapper.%s" % self.uuid)
self._logger.setLevel(getattr(logging, loglevel.upper()))
hdlr = logging.FileHandler(os.path.join(self.homedir, "ns3wrapper.log"))
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
self._logger.addHandler(hdlr)
# Python module to refernce all ns-3 classes and types
self._ns3 = None
# Load ns-3 shared libraries and import modules
self._load_ns3_module()
# Add module as anoter object, so we can reference it later
self._objects[self.uuid] = self.ns3
@property
def ns3(self):
return self._ns3
@property
def homedir(self):
return self._homedir
@property
def uuid(self):
return self._uuid
@property
def logger(self):
return self._logger
def make_uuid(self):
return "uuid%s" % uuid.uuid4()
def is_running(self):
return self._started and not self._stopped
def get_object(self, uuid):
return self._objects.get(uuid)
def get_typeid(self, uuid):
return self._tids.get(uuid)
def singleton(self, clazzname):
uuid = "uuid%s"%clazzname
if not uuid in self._objects:
if not hasattr(self.ns3, clazzname):
msg = "Type %s not supported" % (typeid)
self.logger.error(msg)
clazz = getattr(self.ns3, clazzname)
self._objects[uuid] = clazz
typeid = "ns3::%s" % clazzname
self._tids[uuid] = typeid
return uuid
def create(self, clazzname, *args):
if not hasattr(self.ns3, clazzname):
msg = "Type %s not supported" % (clazzname)
self.logger.error(msg)
realargs = [self.get_object(arg) if \
str(arg).startswith("uuid") else arg for arg in args]
clazz = getattr(self.ns3, clazzname)
obj = clazz(*realargs)
uuid = self.make_uuid()
self._objects[uuid] = obj
#typeid = clazz.GetInstanceTypeId().GetName()
typeid = "ns3::%s" % clazzname
self._tids[uuid] = typeid
return uuid
def invoke(self, uuid, operation, *args):
obj = self.get_object(uuid)
method = getattr(obj, operation)
# arguments starting with 'uuid' identifie stored
# objects and must be replaced by the actual object
realargs = [self.get_object(arg) if \
str(arg).startswith("uuid") else arg for arg in args]
result = method(*realargs)
if not result:
return None
newuuid = self.make_uuid()
self._objects[newuuid] = result
return newuuid
def set(self, uuid, name, value):
obj = self.get_object(uuid)
ns3_value = self._to_ns3_value(uuid, name, value)
def set_attr(obj, name, ns3_value):
obj.SetAttribute(name, ns3_value)
# If the Simulation thread is not running,
# then there will be no thread-safety problems
# in changing the value of an attribute directly.
# However, if the simulation is running we need
# to set the value by scheduling an event, else
# we risk to corrupt the state of the
# simulation.
if self._is_running:
# schedule the event in the Simulator
self._schedule_event(self._condition, set_attr, obj,
name, ns3_value)
else:
set_attr(obj, name, ns3_value)
def get(self, uuid, name):
obj = self.get_object(uuid)
ns3_value = self._create_ns3_value(uuid, name)
def get_attr(obj, name, ns3_value):
obj.GetAttribute(name, ns3_value)
if self._is_running:
# schedule the event in the Simulator
self._schedule_event(self._condition, get_attr, obj,
name, ns3_value)
else:
get_attr(obj, name, ns3_value)
return self._from_ns3_value(uuid, name, ns3_value)
def start(self):
# Launch the simulator thread and Start the
# simulator in that thread
self._condition = threading.Condition()
self._simulator_thread = threading.Thread(
target = self._simulator_run,
args = [self._condition])
self._simulator_thread.setDaemon(True)
self._simulator_thread.start()
self._started = True
def stop(self, time = None):
if not self.ns3:
return
if time is None:
self.ns3.Simulator.Stop()
else:
self.ns3.Simulator.Stop(self.ns3.Time(time))
self._stopped = True
def shutdown(self):
if self.ns3:
if not self.ns3.Simulator.IsFinished():
self.stop()
# TODO!!!! SHOULD WAIT UNTIL THE THREAD FINISHES
if self._simulator_thread:
self._simulator_thread.join()
self.ns3.Simulator.Destroy()
# Remove all references to ns-3 objects
self._objects.clear()
self._ns3 = None
sys.stdout.flush()
sys.stderr.flush()
def _simulator_run(self, condition):
# Run simulation
self.ns3.Simulator.Run()
# Signal condition to indicate simulation ended and
# notify waiting threads
condition.acquire()
condition.notifyAll()
condition.release()
def _schedule_event(self, condition, func, *args):
""" Schedules event on running simulation, and wait until
event is executed"""
def execute_event(contextId, condition, has_event_occurred, func, *args):
try:
func(*args)
finally:
# flag event occured
has_event_occurred[0] = True
# notify condition indicating attribute was set
condition.acquire()
condition.notifyAll()
condition.release()
# contextId is defined as general context
contextId = long(0xffffffff)
# delay 0 means that the event is expected to execute inmediately
delay = self.ns3.Seconds(0)
# flag to indicate that the event occured
# because bool is an inmutable object in python, in order to create a
# bool flag, a list is used as wrapper
has_event_occurred = [False]
condition.acquire()
try:
if not self.ns3.Simulator.IsFinished():
self.ns3.Simulator.ScheduleWithContext(contextId, delay, execute_event,
condition, has_event_occurred, func, *args)
while not has_event_occurred[0] and not self.ns3.Simulator.IsFinished():
condition.wait()
finally:
condition.release()
def _create_ns3_value(self, uuid, name):
typeid = self.get_typeid(uuid)
TypeId = self.ns3.TypeId()
tid = TypeId.LookupByName(typeid)
info = TypeId.AttributeInformation()
if not tid.LookupAttributeByName(name, info):
msg = "TypeId %s has no attribute %s" % (typeid, name)
self.logger.error(msg)
checker = info.checker
ns3_value = checker.Create()
return ns3_value
def _from_ns3_value(self, uuid, name, ns3_value):
typeid = self.get_typeid(uuid)
TypeId = self.ns3.TypeId()
tid = TypeId.LookupByName(typeid)
info = TypeId.AttributeInformation()
if not tid.LookupAttributeByName(name, info):
msg = "TypeId %s has no attribute %s" % (typeid, name)
self.logger.error(msg)
checker = info.checker
value = ns3_value.SerializeToString(checker)
type_name = checker.GetValueTypeName()
if type_name in ["ns3::UintegerValue", "ns3::IntegerValue"]:
return int(value)
if type_name == "ns3::DoubleValue":
return float(value)
if type_name == "ns3::BooleanValue":
return value == "true"
return value
def _to_ns3_value(self, uuid, name, value):
typeid = self.get_typeid(uuid)
TypeId = self.ns3.TypeId()
tid = TypeId.LookupByName(typeid)
info = TypeId.AttributeInformation()
if not tid.LookupAttributeByName(name, info):
msg = "TypeId %s has no attribute %s" % (typeid, name)
self.logger.error(msg)
str_value = str(value)
if isinstance(value, bool):
str_value = str_value.lower()
checker = info.checker
ns3_value = checker.Create()
ns3_value.DeserializeFromString(str_value, checker)
return ns3_value
def _load_ns3_module(self):
if self.ns3:
return
import ctypes
import imp
import re
import pkgutil
bindings = os.environ.get("NS3BINDINGS")
libdir = os.environ.get("NS3LIBRARIES")
# Load the ns-3 modules shared libraries
if libdir:
files = os.listdir(libdir)
regex = re.compile("(.*\.so)$")
libs = [m.group(1) for filename in files for m in [regex.search(filename)] if m]
libscp = list(libs)
while len(libs) > 0:
for lib in libs:
libfile = os.path.join(libdir, lib)
try:
ctypes.CDLL(libfile, ctypes.RTLD_GLOBAL)
libs.remove(lib)
except:
pass
# if did not load any libraries in the last iteration break
# to prevent infinit loop
if len(libscp) == len(libs):
raise RuntimeError("Imposible to load shared libraries %s" % str(libs))
libscp = list(libs)
# import the python bindings for the ns-3 modules
if bindings:
sys.path.append(bindings)
# create a module to add all ns3 classes
ns3mod = imp.new_module("ns3")
sys.modules["ns3"] = ns3mod
# retrieve all ns3 classes and add them to the ns3 module
import ns
for importer, modname, ispkg in pkgutil.iter_modules(ns.__path__):
fullmodname = "ns.%s" % modname
module = __import__(fullmodname, globals(), locals(), ['*'])
for sattr in dir(module):
if sattr.startswith("_"):
continue
attr = getattr(module, sattr)
# netanim.Config and lte.Config singleton overrides ns3::Config
if sattr == "Config" and modname in ['netanim', 'lte']:
sattr = "%s.%s" % (modname, sattr)
setattr(ns3mod, sattr, attr)
self._ns3 = ns3mod