#
# NEPI, a framework to manage network experiments
# Copyright (C) 2014 INRIA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation;
#
# 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
from nepi.execution.attribute import Attribute, Flags, Types
from nepi.execution.trace import Trace, TraceAttr
from nepi.execution.resource import ResourceManager, clsinit_copy, \
ResourceState
from nepi.resources.linux.application import LinuxApplication
from nepi.util.timefuncs import tnow, tdiffsec
from nepi.resources.netns.netnsemulation import NetNSEmulation
from nepi.resources.linux.netns.netnsclient import LinuxNetNSClient
import os
import time
import threading
@clsinit_copy
class LinuxNetNSEmulation(LinuxApplication, NetNSEmulation):
_rtype = "linux::netns::Emulation"
@classmethod
def _register_attributes(cls):
verbose = Attribute("verbose",
"True to output debugging info for the client-server communication",
type = Types.Bool,
flags = Flags.Design)
enable_dump = Attribute("enableDump",
"Enable dumping the remote executed commands to a script "
"in order to later reproduce and debug the experiment",
type = Types.Bool,
default = False,
flags = Flags.Design)
version = Attribute("version",
"Version of netns to install from nsam repo",
default = "netns-dev",
flags = Flags.Design)
cls._register_attribute(enable_dump)
cls._register_attribute(verbose)
cls._register_attribute(version)
def __init__(self, ec, guid):
LinuxApplication.__init__(self, ec, guid)
NetNSEmulation.__init__(self)
self._client = None
self._home = "netns-emu-%s" % self.guid
self._socket_name = "netns-%s.sock" % os.urandom(4).encode('hex')
@property
def socket_name(self):
return self._socket_name
@property
def remote_socket(self):
return os.path.join(self.run_home, self.socket_name)
def upload_sources(self):
self.node.mkdir(os.path.join(self.node.src_dir, "netnswrapper"))
# upload wrapper python script
wrapper = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
"netnswrapper.py")
self.node.upload(wrapper,
os.path.join(self.node.src_dir, "netnswrapper", "netnswrapper.py"),
overwrite = False)
# upload wrapper debug python script
wrapper_debug = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
"netnswrapper_debug.py")
self.node.upload(wrapper_debug,
os.path.join(self.node.src_dir, "netnswrapper", "netnswrapper_debug.py"),
overwrite = False)
# upload server python script
server = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
"netnsserver.py")
self.node.upload(server,
os.path.join(self.node.src_dir, "netnswrapper", "netnsserver.py"),
overwrite = False)
# Upload user defined sources
self.node.mkdir(os.path.join(self.node.src_dir, "netns"))
src_dir = os.path.join(self.node.src_dir, "netns")
super(LinuxNetNSEmulation, self).upload_sources(src_dir = src_dir)
def upload_extra_sources(self, sources = None, src_dir = None):
return super(LinuxNetNSEmulation, self).upload_sources(
sources = sources,
src_dir = src_dir)
def upload_start_command(self):
command = self.get("command")
env = self.get("env")
# We want to make sure the emulator is running
# before the experiment starts.
# Run the command as a bash script in background,
# in the host ( but wait until the command has
# finished to continue )
env = self.replace_paths(env)
command = self.replace_paths(command)
shfile = os.path.join(self.app_home, "start.sh")
self.node.upload_command(command,
shfile = shfile,
env = env,
overwrite = True)
# Run the wrapper
self._run_in_background()
# Wait until the remote socket is created
self.wait_remote_socket()
def do_deploy(self):
if not self.node or self.node.state < ResourceState.READY:
self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.node.state )
# ccnd needs to wait until node is deployed and running
self.ec.schedule(self.reschedule_delay, self.deploy)
else:
if not self.get("command"):
self.set("command", self._start_command)
if not self.get("depends"):
self.set("depends", self._dependencies)
if self.get("sources"):
sources = self.get("sources")
source = sources.split(" ")[0]
basename = os.path.basename(source)
version = ( basename.strip().replace(".tar.gz", "")
.replace(".tar","")
.replace(".gz","")
.replace(".zip","") )
self.set("version", version)
self.set("sources", source)
if not self.get("build"):
self.set("build", self._build)
if not self.get("env"):
self.set("env", self._environment)
self.do_discover()
self.do_provision()
# Create client
self._client = LinuxNetNSClient(self)
self.set_ready()
def do_start(self):
""" Starts execution execution
"""
self.info("Starting")
if self.state == ResourceState.READY:
self.set_started()
else:
msg = " Failed to execute command '%s'" % command
self.error(msg, out, err)
raise RuntimeError(msg)
def do_stop(self):
""" Stops simulation execution
"""
if self.state == ResourceState.STARTED:
self.set_stopped()
def do_release(self):
self.info("Releasing resource")
tear_down = self.get("tearDown")
if tear_down:
self.node.execute(tear_down)
self.do_stop()
self._client.shutdown()
LinuxApplication.do_stop(self)
super(LinuxApplication, self).do_release()
@property
def _start_command(self):
command = []
#command.append("sudo")
command.append("PYTHONPATH=$PYTHONPATH:${SRC}/netnswrapper/")
command.append("python ${SRC}/netnswrapper/netnsserver.py -S %s" % \
os.path.basename(self.remote_socket) )
if self.get("enableDump"):
command.append("-D")
if self.get("verbose"):
command.append("-v")
command = " ".join(command)
return command
@property
def _dependencies(self):
if self.node.use_rpm:
return (" python python-devel mercurial unzip bridge-utils iproute")
elif self.node.use_deb:
return (" python python-dev mercurial unzip bridge-utils iproute")
return ""
@property
def netns_repo(self):
return "http://nepi.inria.fr/code/netns"
@property
def netns_version(self):
version = self.get("version")
return version or "dev"
@property
def python_unshare_repo(self):
return "http://nepi.inria.fr/code/python-unshare"
@property
def python_unshare_version(self):
return "dev"
@property
def python_passfd_repo(self):
return "http://nepi.inria.fr/code/python-passfd"
@property
def python_passfd_version(self):
return "dev"
@property
def netns_src(self):
location = "${SRC}/netns/%(version)s" \
% {
"version": self.netns_version,
}
return location
@property
def python_unshare_src(self):
location = "${SRC}/python_unshare/%(version)s" \
% {
"version": self.python_unshare_version,
}
return location
@property
def python_passfd_src(self):
location = "${SRC}/python_passfd/%(version)s" \
% {
"version": self.python_passfd_version,
}
return location
def clone_command(self, name, repo, src):
clone_cmd = (
# Test if alredy cloned
" ( "
" ( "
" ( test -d %(src)s ) "
" && echo '%(name)s binaries found, nothing to do'"
" ) "
" ) "
" || "
# clone source code
" ( "
" mkdir -p %(src)s && "
" hg clone %(repo)s %(src)s"
" ) "
) % {
"repo": repo,
"src": src,
"name": name,
}
return clone_cmd
@property
def _build(self):
netns_clone = self.clone_command("netns", self.netns_repo,
self.netns_src)
python_unshare_clone = self.clone_command("python_unshare",
self.python_unshare_repo, self.python_unshare_src)
python_passfd_clone = self.clone_command("python_passfd",
self.python_passfd_repo, self.python_passfd_src)
build_cmd = (
# Netns installation
"( %(netns_clone)s )"
" && "
"( %(python_unshare_clone)s )"
" && "
"( %(python_passfd_clone)s )"
) % {
"netns_clone": netns_clone,
"python_unshare_clone": python_unshare_clone,
"python_passfd_clone": python_passfd_clone,
}
return build_cmd
@property
def _environment(self):
env = []
env.append("PYTHONPATH=$PYTHONPAH:%(netns_src)s/src/:%(python_unshare_src)s/src:%(python_passfd_src)s/src}" % {
"netns_src": self.netns_src,
"python_unshare_src": self.python_unshare_src,
"python_passfd_src": self.python_passfd_src,
})
return " ".join(env)
def replace_paths(self, command):
"""
Replace all special path tags with shell-escaped actual paths.
"""
return ( command
.replace("${USR}", self.node.usr_dir)
.replace("${LIB}", self.node.lib_dir)
.replace("${BIN}", self.node.bin_dir)
.replace("${SRC}", self.node.src_dir)
.replace("${SHARE}", self.node.share_dir)
.replace("${EXP}", self.node.exp_dir)
.replace("${EXP_HOME}", self.node.exp_home)
.replace("${APP_HOME}", self.app_home)
.replace("${RUN_HOME}", self.run_home)
.replace("${NODE_HOME}", self.node.node_home)
.replace("${HOME}", self.node.home_dir)
)
def valid_connection(self, guid):
# TODO: Validate!
return True
def wait_remote_socket(self):
""" Waits until the remote socket is created
"""
command = " [ -e %s ] && echo 'DONE' " % self.remote_socket
for i in range(200):
(out, err), proc = self.node.execute(command, retry = 1,
with_lock = True)
if out.find("DONE") > -1:
break
else:
raise RuntimeError("Remote socket not found at %s" % \
self.remote_socket)