2 # NEPI, a framework to manage network experiments
3 # Copyright (C) 2014 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 version 2 as
7 # published by the Free Software Foundation;
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19 from nepi.execution.attribute import Attribute, Flags, Types
20 from nepi.execution.trace import Trace, TraceAttr
21 from nepi.execution.resource import ResourceManager, clsinit_copy, \
23 from nepi.resources.linux.application import LinuxApplication
24 from nepi.util.timefuncs import tnow, tdiffsec
25 from nepi.resources.netns.netnsemulation import NetNSEmulation
26 from nepi.resources.linux.netns.netnsclient import LinuxNetNSClient
33 class LinuxNetNSEmulation(LinuxApplication, NetNSEmulation):
34 _rtype = "linux::netns::Emulation"
37 def _register_attributes(cls):
38 verbose = Attribute("verbose",
39 "True to output debugging info for the client-server communication",
43 enable_dump = Attribute("enableDump",
44 "Enable dumping the remote executed commands to a script "
45 "in order to later reproduce and debug the experiment",
50 version = Attribute("version",
51 "Version of netns to install from nsam repo",
52 default = "netns-dev",
55 cls._register_attribute(enable_dump)
56 cls._register_attribute(verbose)
57 cls._register_attribute(version)
59 def __init__(self, ec, guid):
60 LinuxApplication.__init__(self, ec, guid)
61 NetNSEmulation.__init__(self)
64 self._home = "netns-emu-%s" % self.guid
65 self._socket_name = "netns-%s.sock" % os.urandom(4).encode('hex')
68 def socket_name(self):
69 return self._socket_name
72 def remote_socket(self):
73 return os.path.join(self.run_home, self.socket_name)
75 def upload_sources(self):
76 self.node.mkdir(os.path.join(self.node.src_dir, "netnswrapper"))
78 # upload wrapper python script
79 wrapper = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
82 self.node.upload(wrapper,
83 os.path.join(self.node.src_dir, "netnswrapper", "netnswrapper.py"),
86 # upload wrapper debug python script
87 wrapper_debug = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
88 "netnswrapper_debug.py")
90 self.node.upload(wrapper_debug,
91 os.path.join(self.node.src_dir, "netnswrapper", "netnswrapper_debug.py"),
94 # upload server python script
95 server = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
98 self.node.upload(server,
99 os.path.join(self.node.src_dir, "netnswrapper", "netnsserver.py"),
102 # Upload user defined sources
103 self.node.mkdir(os.path.join(self.node.src_dir, "netns"))
104 src_dir = os.path.join(self.node.src_dir, "netns")
106 super(LinuxNetNSEmulation, self).upload_sources(src_dir = src_dir)
108 def upload_extra_sources(self, sources = None, src_dir = None):
109 return super(LinuxNetNSEmulation, self).upload_sources(
113 def upload_start_command(self):
114 command = self.get("command")
115 env = self.get("env")
117 # We want to make sure the emulator is running
118 # before the experiment starts.
119 # Run the command as a bash script in background,
120 # in the host ( but wait until the command has
121 # finished to continue )
122 env = self.replace_paths(env)
123 command = self.replace_paths(command)
125 shfile = os.path.join(self.app_home, "start.sh")
126 self.node.upload_command(command,
132 self._run_in_background()
134 # Wait until the remote socket is created
135 self.wait_remote_socket()
138 if not self.node or self.node.state < ResourceState.READY:
139 self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.node.state )
141 # ccnd needs to wait until node is deployed and running
142 self.ec.schedule(self.reschedule_delay, self.deploy)
144 if not self.get("command"):
145 self.set("command", self._start_command)
147 if not self.get("depends"):
148 self.set("depends", self._dependencies)
150 if self.get("sources"):
151 sources = self.get("sources")
152 source = sources.split(" ")[0]
153 basename = os.path.basename(source)
154 version = ( basename.strip().replace(".tar.gz", "")
157 .replace(".zip","") )
159 self.set("version", version)
160 self.set("sources", source)
162 if not self.get("build"):
163 self.set("build", self._build)
165 if not self.get("env"):
166 self.set("env", self._environment)
172 self._client = LinuxNetNSClient(self)
177 """ Starts execution execution
180 self.info("Starting")
182 if self.state == ResourceState.READY:
185 msg = " Failed to execute command '%s'" % command
186 self.error(msg, out, err)
187 raise RuntimeError(msg)
190 """ Stops simulation execution
193 if self.state == ResourceState.STARTED:
196 def do_release(self):
197 self.info("Releasing resource")
199 tear_down = self.get("tearDown")
201 self.node.execute(tear_down)
204 self._client.shutdown()
205 LinuxApplication.do_stop(self)
207 super(LinuxApplication, self).do_release()
210 def _start_command(self):
213 #command.append("sudo")
214 command.append("PYTHONPATH=$PYTHONPATH:${SRC}/netnswrapper/")
215 command.append("python ${SRC}/netnswrapper/netnsserver.py -S %s" % \
216 os.path.basename(self.remote_socket) )
218 if self.get("enableDump"):
221 if self.get("verbose"):
224 command = " ".join(command)
228 def _dependencies(self):
229 if self.node.use_rpm:
230 return (" python python-devel mercurial unzip bridge-utils iproute")
231 elif self.node.use_deb:
232 return (" python python-dev mercurial unzip bridge-utils iproute")
236 def netns_repo(self):
237 return "http://nepi.inria.fr/code/netns"
240 def netns_version(self):
241 version = self.get("version")
242 return version or "dev"
245 def python_unshare_repo(self):
246 return "http://nepi.inria.fr/code/python-unshare"
249 def python_unshare_version(self):
253 def python_passfd_repo(self):
254 return "http://nepi.inria.fr/code/python-passfd"
257 def python_passfd_version(self):
262 location = "${SRC}/netns/%(version)s" \
264 "version": self.netns_version,
270 def python_unshare_src(self):
271 location = "${SRC}/python_unshare/%(version)s" \
273 "version": self.python_unshare_version,
279 def python_passfd_src(self):
280 location = "${SRC}/python_passfd/%(version)s" \
282 "version": self.python_passfd_version,
287 def clone_command(self, name, repo, src):
289 # Test if alredy cloned
292 " ( test -d %(src)s ) "
293 " && echo '%(name)s binaries found, nothing to do'"
299 " mkdir -p %(src)s && "
300 " hg clone %(repo)s %(src)s"
312 netns_clone = self.clone_command("netns", self.netns_repo,
314 python_unshare_clone = self.clone_command("python_unshare",
315 self.python_unshare_repo, self.python_unshare_src)
316 python_passfd_clone = self.clone_command("python_passfd",
317 self.python_passfd_repo, self.python_passfd_src)
321 "( %(netns_clone)s )"
323 "( %(python_unshare_clone)s )"
325 "( %(python_passfd_clone)s )"
327 "netns_clone": netns_clone,
328 "python_unshare_clone": python_unshare_clone,
329 "python_passfd_clone": python_passfd_clone,
335 def _environment(self):
337 env.append("PYTHONPATH=$PYTHONPAH:%(netns_src)s/src/:%(python_unshare_src)s/src:%(python_passfd_src)s/src}" % {
338 "netns_src": self.netns_src,
339 "python_unshare_src": self.python_unshare_src,
340 "python_passfd_src": self.python_passfd_src,
345 def replace_paths(self, command):
347 Replace all special path tags with shell-escaped actual paths.
350 .replace("${USR}", self.node.usr_dir)
351 .replace("${LIB}", self.node.lib_dir)
352 .replace("${BIN}", self.node.bin_dir)
353 .replace("${SRC}", self.node.src_dir)
354 .replace("${SHARE}", self.node.share_dir)
355 .replace("${EXP}", self.node.exp_dir)
356 .replace("${EXP_HOME}", self.node.exp_home)
357 .replace("${APP_HOME}", self.app_home)
358 .replace("${RUN_HOME}", self.run_home)
359 .replace("${NODE_HOME}", self.node.node_home)
360 .replace("${HOME}", self.node.home_dir)
363 def valid_connection(self, guid):
367 def wait_remote_socket(self):
368 """ Waits until the remote socket is created
370 command = " [ -e %s ] && echo 'DONE' " % self.remote_socket
373 (out, err), proc = self.node.execute(command, retry = 1,
376 if out.find("DONE") > -1:
379 raise RuntimeError("Remote socket not found at %s" % \