2 # NEPI, a framework to manage network experiments
3 # Copyright (C) 2013 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.resource import clsinit_copy, ResourceState, \
23 from nepi.resources.linux.application import LinuxApplication
24 from nepi.resources.planetlab.node import PlanetlabNode
25 from nepi.util.timefuncs import tnow, tdiffsec
32 # - CREATE GRE - PlanetlabGRE - it only needs to set the gre and remote
33 # properties when configuring the vif_up
35 PYTHON_VSYS_VERSION = "1.0"
38 class PlanetlabTap(LinuxApplication):
39 _rtype = "PlanetlabTap"
40 _help = "Creates a TAP device on a PlanetLab host"
41 _backend = "planetlab"
44 def _register_attributes(cls):
45 ip4 = Attribute("ip4", "IPv4 Address",
48 mac = Attribute("mac", "MAC Address",
51 prefix4 = Attribute("prefix4", "IPv4 network prefix",
55 mtu = Attribute("mtu", "Maximum transmition unit for device",
58 devname = Attribute("deviceName",
59 "Name of the network interface (e.g. eth0, wlan0, etc)",
60 flags = Flags.NoWrite)
62 up = Attribute("up", "Link up",
65 snat = Attribute("snat", "Set SNAT=1",
69 pointopoint = Attribute("pointopoint", "Peer IP address",
72 txqueuelen = Attribute("txqueuelen", "Length of transmission queue",
75 txqueuelen = Attribute("txqueuelen", "Length of transmission queue",
78 gre_key = Attribute("greKey",
79 "GRE key to be used to configure GRE tunnel",
83 gre_remote = Attribute("greRemote",
84 "Public IP of remote endpoint for GRE tunnel",
87 tear_down = Attribute("tearDown",
88 "Bash script to be executed before releasing the resource",
91 cls._register_attribute(ip4)
92 cls._register_attribute(mac)
93 cls._register_attribute(prefix4)
94 cls._register_attribute(mtu)
95 cls._register_attribute(devname)
96 cls._register_attribute(up)
97 cls._register_attribute(snat)
98 cls._register_attribute(pointopoint)
99 cls._register_attribute(txqueuelen)
100 cls._register_attribute(gre_key)
101 cls._register_attribute(gre_remote)
102 cls._register_attribute(tear_down)
104 def __init__(self, ec, guid):
105 super(PlanetlabTap, self).__init__(ec, guid)
106 self._home = "tap-%s" % self.guid
107 self._gre_enabled = False
111 node = self.get_connected(PlanetlabNode.get_rtype())
112 if node: return node[0]
116 def gre_enabled(self):
117 if not self._gre_enabled:
118 from nepi.resources.linux.gretunnel import LinuxGRETunnel
119 gre = self.get_connected(LinuxGRETunnel.get_rtype())
120 if gre: self._gre_enabled = True
122 return self._gre_enabled
124 def upload_sources(self):
127 # vif-creation python script
128 pl_vif_create = os.path.join(os.path.dirname(__file__), "scripts",
131 scripts.append(pl_vif_create)
133 # vif-up python script
134 pl_vif_up = os.path.join(os.path.dirname(__file__), "scripts",
137 scripts.append(pl_vif_up)
139 # vif-down python script
140 pl_vif_down = os.path.join(os.path.dirname(__file__), "scripts",
143 scripts.append(pl_vif_down)
145 # udp-connect python script
146 pl_vif_connect = os.path.join(os.path.dirname(__file__), "scripts",
147 "pl-vif-udp-connect.py")
149 scripts.append(pl_vif_connect)
151 # tunnel creation python script
152 tunchannel = os.path.join(os.path.dirname(__file__), "..", "linux",
153 "scripts", "tunchannel.py")
155 scripts.append(tunchannel)
158 scripts = ";".join(scripts)
160 self.node.upload(scripts,
161 os.path.join(self.node.src_dir),
164 # upload stop.sh script
165 stop_command = self.replace_paths(self._stop_command)
167 self.node.upload(stop_command,
168 os.path.join(self.app_home, "stop.sh"),
170 # Overwrite file every time.
171 # The stop.sh has the path to the socket, which should change
172 # on every experiment run.
175 def upload_start_command(self):
176 # If GRE mode is enabled, TAP creation is delayed until the
177 # tunnel is established
178 if not self.gre_enabled:
179 # Overwrite file every time.
180 # The start.sh has the path to the socket, wich should change
181 # on every experiment run.
182 super(PlanetlabTap, self).upload_start_command(overwrite = True)
184 # We want to make sure the device is up and running
185 # before the deploy finishes, so we execute now the
186 # start script. We run it in background, because the
187 # TAP will live for as long as the process that
188 # created it is running, and wait until the TAP
190 self._run_in_background()
192 # After creating the TAP, the pl-vif-create.py script
193 # will write the name of the TAP to a file. We wait until
194 # we can read the interface name from the file.
195 vif_name = self.wait_vif_name()
196 self.set("deviceName", vif_name)
199 if not self.node or self.node.state < ResourceState.PROVISIONED:
200 self.ec.schedule(reschedule_delay, self.deploy)
202 if not self.get("command"):
203 self.set("command", self._start_command)
205 if not self.get("depends"):
206 self.set("depends", self._dependencies)
208 if not self.get("install"):
209 self.set("install", self._install)
217 if self.state == ResourceState.READY:
218 command = self.get("command")
219 self.info("Starting command '%s'" % command)
223 msg = " Failed to execute command '%s'" % command
224 self.error(msg, out, err)
225 raise RuntimeError, msg
228 command = self.get('command') or ''
230 if self.state == ResourceState.STARTED:
231 self.info("Stopping command '%s'" % command)
233 command = "bash %s" % os.path.join(self.app_home, "stop.sh")
234 (out, err), proc = self.execute_command(command,
238 msg = " Failed to stop command '%s' " % command
239 self.error(msg, out, err)
245 state_check_delay = 0.5
246 if self._state == ResourceState.STARTED and \
247 tdiffsec(tnow(), self._last_state_check) > state_check_delay:
249 if self.get("deviceName"):
250 (out, err), proc = self.node.execute("ifconfig")
252 if out.strip().find(self.get("deviceName")) == -1:
253 # tap is not running is not running (socket not found)
256 self._last_state_check = tnow()
260 def do_release(self):
261 # Node needs to wait until all associated RMs are released
263 from nepi.resources.linux.tunnel import LinuxTunnel
264 rms = self.get_connected(LinuxTunnel.get_rtype())
267 if rm.state < ResourceState.STOPPED:
268 self.ec.schedule(reschedule_delay, self.release)
271 super(PlanetlabTap, self).do_release()
273 def wait_vif_name(self):
274 """ Waits until the vif_name file for the command is generated,
275 and returns the vif_name for the device """
280 (out, err), proc = self.node.check_output(self.run_home, "vif_name")
283 (out, err), proc = self.node.check_errors(self.run_home)
286 raise RuntimeError, err
289 vif_name = out.strip()
295 msg = "Couldn't retrieve vif_name"
296 self.error(msg, out, err)
297 raise RuntimeError, msg
301 def udp_connect_command(self, remote_endpoint, connection_run_home,
302 cipher, cipher_key, bwlimit, txqueuelen):
304 # Set the remote endpoint
305 self.set("pointopoint", remote_endpoint.get("ip4"))
307 remote_ip = socket.gethostbyname(
308 remote_endpoint.node.get("hostname"))
310 local_port_file = os.path.join(connection_run_home,
313 remote_port_file = os.path.join(connection_run_home,
316 ret_file = os.path.join(connection_run_home,
319 # Generate UDP connect command
320 # Use pl-vif-up.py script to configure TAP with peer info
321 vif_up_command = self._vif_up_command
324 command.append(vif_up_command)
326 # Use pl-vid-udp-connect.py to stablish the tunnel between endpoints
327 command.append(") & (")
328 command.append("sudo -S")
329 command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
330 command.append("python ${SRC}/pl-vif-udp-connect.py")
331 command.append("-t %s" % self.vif_type)
332 command.append("-S %s " % self.sock_name)
333 command.append("-l %s " % local_port_file)
334 command.append("-r %s " % remote_port_file)
335 command.append("-H %s " % remote_ip)
336 command.append("-R %s " % ret_file)
338 command.append("-c %s " % cipher)
340 command.append("-k %s " % cipher_key)
342 command.append("-q %s " % txqueuelen)
344 command.append("-b %s " % bwlimit)
348 command = " ".join(command)
349 command = self.replace_paths(command)
353 def gre_connect_command(self, remote_endpoint, connection_run_home):
354 # Set the remote endpoint
355 self.set("pointopoint", remote_endpoint.get("ip4"))
356 self.set("gre_remote", socket.gethostbyname(
357 remote_endpoint.node.get("hostname")))
359 # Generate GRE connect command
361 # Use vif_down command to first kill existing TAP in GRE mode
362 vif_down_command = self._vif_command_command
364 # Use pl-vif-up.py script to configure TAP with peer info
365 vif_up_command = self._vif_up_command
368 command.append(vif_down_command)
369 command.append(") ; (")
370 command.append(vif_up_command)
373 command = " ".join(command)
374 command = self.replace_paths(command)
379 def _start_command(self):
383 command = ["sudo -S python ${SRC}/pl-vif-create.py"]
385 command.append("-t %s" % self.vif_type)
386 command.append("-a %s" % self.get("ip4"))
387 command.append("-n %d" % self.get("prefix4"))
388 command.append("-f %s " % self.vif_name_file)
389 command.append("-S %s " % self.sock_name)
391 if self.get("snat") == True:
394 if self.get("pointopoint"):
395 command.append("-p %s" % self.get("pointopoint"))
397 if self.get("txqueuelen"):
398 command.append("-q %s" % self.get("txqueuelen"))
400 return " ".join(command)
403 def _stop_command(self):
405 command = self._vif_down_command()
407 command = ["sudo -S "]
408 command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
409 command.append("python ${SRC}/pl-vif-down.py")
410 command.append("-S %s " % self.sock_name)
411 command = " ".join(command)
416 def _vif_up_command(self):
418 device_name = "%s" % self.guid
420 device_name = self.get("deviceName")
422 # Use pl-vif-up.py script to configure TAP
423 command = ["sudo -S "]
424 command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
425 command.append("python ${SRC}/pl-vif-up.py")
426 command.append("-N %s" % self.get("deviceName"))
427 command.append("-t %s" % self.vif_type)
428 command.append("-a %s" % self.get("ip4"))
429 command.append("-n %d" % self.get("prefix4"))
431 if self.get("snat") == True:
434 if self.get("pointopoint"):
435 command.append("-p %s" % self.get("pointopoint"))
437 if self.get("txqueuelen"):
438 command.append("-q %s" % self.get("txqueuelen"))
441 command.append("-g %s" % self.gre("greKey"))
442 command.append("-G %s" % self.gre("greRemote"))
444 command.append("-f %s " % self.vif_name_file)
446 return " ".join(command)
449 def _vif_down_command(self):
450 command = ["sudo -S "]
451 command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
452 command.append("python ${SRC}/pl-vif-down.py")
454 command.append("-N %s " % self.get("deviceName"))
456 return " ".join(command)
463 def vif_name_file(self):
464 return os.path.join(self.run_home, "vif_name")
468 return os.path.join(self.run_home, "tap.sock")
471 def _dependencies(self):
472 return "mercurial make gcc"
476 # Install python-vsys and python-passfd
477 install_vsys = ( " ( "
478 " python -c 'import vsys, os; vsys.__version__ == \"%(version)s\" or os._exit(1)' "
483 " hg clone http://nepi.inria.fr/code/python-vsys ; "
486 " sudo -S make install "
488 "version": PYTHON_VSYS_VERSION
491 install_passfd = ( " ( python -c 'import passfd' ) "
495 " hg clone http://nepi.inria.fr/code/python-passfd ; "
496 " cd python-passfd ; "
498 " sudo -S make install "
501 return "%s ; %s" % ( install_vsys, install_passfd )
503 def valid_connection(self, guid):