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 as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
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.
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/>.
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
20 from nepi.execution.attribute import Attribute, Flags, Types
21 from nepi.execution.trace import Trace, TraceAttr
22 from nepi.execution.resource import ResourceManager, clsinit_copy, \
23 ResourceState, ResourceFactory, reschedule_delay
24 from nepi.resources.linux.application import LinuxApplication
25 from nepi.util.timefuncs import tnow, tdiffsec
26 from nepi.resources.netns.netnsemulation import NetNSEmulation
27 from nepi.resources.linux.netns.netnsclient import LinuxNetNSClient
34 class LinuxNetNSEmulation(LinuxApplication, NetNSEmulation):
35 _rtype = "LinuxNetNSEmulation"
38 def _register_attributes(cls):
39 verbose = Attribute("verbose",
40 "True to output debugging info for the client-server communication",
44 enable_dump = Attribute("enableDump",
45 "Enable dumping the remote executed commands to a script "
46 "in order to later reproduce and debug the experiment",
51 version = Attribute("version",
52 "Version of netns to install from nsam repo",
53 default = "netns-dev",
56 cls._register_attribute(enable_dump)
57 cls._register_attribute(verbose)
58 cls._register_attribute(version)
60 def __init__(self, ec, guid):
61 LinuxApplication.__init__(self, ec, guid)
62 NetNSEmulation.__init__(self)
65 self._home = "netns-emu-%s" % self.guid
66 self._socket_name = "netns-%s.sock" % os.urandom(4).encode('hex')
69 def socket_name(self):
70 return self._socket_name
73 def remote_socket(self):
74 return os.path.join(self.run_home, self.socket_name)
76 def upload_sources(self):
77 self.node.mkdir(os.path.join(self.node.src_dir, "netnswrapper"))
79 # upload wrapper python script
80 wrapper = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
83 self.node.upload(wrapper,
84 os.path.join(self.node.src_dir, "netnswrapper", "netnswrapper.py"),
87 # upload wrapper debug python script
88 wrapper_debug = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
89 "netnswrapper_debug.py")
91 self.node.upload(wrapper_debug,
92 os.path.join(self.node.src_dir, "netnswrapper", "netnswrapper_debug.py"),
95 # upload server python script
96 server = os.path.join(os.path.dirname(__file__), "..", "..", "netns",
99 self.node.upload(server,
100 os.path.join(self.node.src_dir, "netnswrapper", "netnsserver.py"),
103 # Upload user defined sources
104 self.node.mkdir(os.path.join(self.node.src_dir, "netns"))
105 src_dir = os.path.join(self.node.src_dir, "netns")
107 super(LinuxNetNSEmulation, self).upload_sources(src_dir = src_dir)
109 def upload_extra_sources(self, sources = None, src_dir = None):
110 return super(LinuxNetNSEmulation, self).upload_sources(
114 def upload_start_command(self):
115 command = self.get("command")
116 env = self.get("env")
118 # We want to make sure the emulator is running
119 # before the experiment starts.
120 # Run the command as a bash script in background,
121 # in the host ( but wait until the command has
122 # finished to continue )
123 env = self.replace_paths(env)
124 command = self.replace_paths(command)
126 shfile = os.path.join(self.app_home, "start.sh")
127 self.node.upload_command(command,
133 self._run_in_background()
135 # Wait until the remote socket is created
136 self.wait_remote_socket()
139 if not self.node or self.node.state < ResourceState.READY:
140 self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.node.state )
142 # ccnd needs to wait until node is deployed and running
143 self.ec.schedule(reschedule_delay, self.deploy)
145 if not self.get("command"):
146 self.set("command", self._start_command)
148 if not self.get("depends"):
149 self.set("depends", self._dependencies)
151 if self.get("sources"):
152 sources = self.get("sources")
153 source = sources.split(" ")[0]
154 basename = os.path.basename(source)
155 version = ( basename.strip().replace(".tar.gz", "")
158 .replace(".zip","") )
160 self.set("version", version)
161 self.set("sources", source)
163 if not self.get("build"):
164 self.set("build", self._build)
166 if not self.get("env"):
167 self.set("env", self._environment)
173 self._client = LinuxNetNSClient(self)
177 def do_release(self):
178 self.info("Releasing resource")
180 tear_down = self.get("tearDown")
182 self.node.execute(tear_down)
185 self._client.shutdown()
186 LinuxApplication.do_stop(self)
188 super(LinuxApplication, self).do_release()
191 def _start_command(self):
194 #command.append("sudo")
195 command.append("PYTHONPATH=$PYTHONPATH:${SRC}/netnswrapper/")
196 command.append("python ${SRC}/netnswrapper/netnsserver.py -S %s" % \
197 os.path.basename(self.remote_socket) )
199 if self.get("enableDump"):
202 if self.get("verbose"):
205 command = " ".join(command)
209 def _dependencies(self):
210 if self.node.use_rpm:
211 return ( " python python-devel mercurial unzip bridge-utils iproute")
212 elif self.node.use_deb:
213 return ( " python python-dev mercurial unzip bridge-utils iproute")
217 def netns_repo(self):
218 return "http://nepi.inria.fr/code/netns"
221 def netns_version(self):
222 version = self.get("version")
223 return version or "dev"
226 def python_unshare_repo(self):
227 return "http://nepi.inria.fr/code/python-unshare"
230 def python_unshare_version(self):
234 def python_passfd_repo(self):
235 return "http://nepi.inria.fr/code/python-passfd"
238 def python_passfd_version(self):
243 location = "${SRC}/netns/%(version)s" \
245 "version": self.netns_version,
251 def python_unshare_src(self):
252 location = "${SRC}/python_unshare/%(version)s" \
254 "version": self.python_unshare_version,
260 def python_passfd_src(self):
261 location = "${SRC}/python_passfd/%(version)s" \
263 "version": self.python_passfd_version,
268 def clone_command(self, name, repo, src):
270 # Test if alredy cloned
273 " ( test -d %(src)s ) "
274 " && echo '%(name)s binaries found, nothing to do'"
280 " mkdir -p %(src)s && "
281 " hg clone %(repo)s %(src)s"
293 netns_clone = self.clone_command("netns", self.netns_repo,
295 python_unshare_clone = self.clone_command("python_unshare",
296 self.python_unshare_repo, self.python_unshare_src)
297 python_passfd_clone = self.clone_command("python_passfd",
298 self.python_passfd_repo, self.python_passfd_src)
302 "( %(netns_clone)s )"
304 "( %(python_unshare_clone)s )"
306 "( %(python_passfd_clone)s )"
308 "netns_clone": netns_clone,
309 "python_unshare_clone": python_unshare_clone,
310 "python_passfd_clone": python_passfd_clone,
316 def _environment(self):
318 env.append("PYTHONPATH=$PYTHONPAH:%(netns_src)s/src/:%(python_unshare_src)s/src:%(python_passfd_src)s/src}" % {
319 "netns_src": self.netns_src,
320 "python_unshare_src": self.python_unshare_src,
321 "python_passfd_src": self.python_passfd_src,
326 def replace_paths(self, command):
328 Replace all special path tags with shell-escaped actual paths.
331 .replace("${USR}", self.node.usr_dir)
332 .replace("${LIB}", self.node.lib_dir)
333 .replace("${BIN}", self.node.bin_dir)
334 .replace("${SRC}", self.node.src_dir)
335 .replace("${SHARE}", self.node.share_dir)
336 .replace("${EXP}", self.node.exp_dir)
337 .replace("${EXP_HOME}", self.node.exp_home)
338 .replace("${APP_HOME}", self.app_home)
339 .replace("${RUN_HOME}", self.run_home)
340 .replace("${NODE_HOME}", self.node.node_home)
341 .replace("${HOME}", self.node.home_dir)
344 def valid_connection(self, guid):
348 def wait_remote_socket(self):
349 """ Waits until the remote socket is created
351 command = " [ -e %s ] && echo 'DONE' " % self.remote_socket
353 for i in xrange(200):
354 (out, err), proc = self.node.execute(command, retry = 1,
357 if out.find("DONE") > -1:
360 raise RuntimeError("Remote socket not found at %s" % \