reinstate import of print_function in py3 so as to shorten the distance between both...
[nepi.git] / src / nepi / resources / planetlab / tap.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2013 INRIA
4 #
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;
8 #
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.
13 #
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/>.
16 #
17 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
18
19 from nepi.execution.attribute import Attribute, Flags, Types
20 from nepi.execution.resource import clsinit_copy, ResourceState 
21 from nepi.resources.linux.tap import LinuxTap
22 from nepi.resources.planetlab.node import PlanetlabNode
23 from nepi.util.timefuncs import tnow, tdiffsec
24
25 import os
26 import time
27
28 PYTHON_VSYS_VERSION = "1.0"
29
30 @clsinit_copy
31 class PlanetlabTap(LinuxTap):
32     _rtype = "planetlab::Tap"
33     _help = "Creates a TAP device on a PlanetLab host"
34     _platform = "planetlab"
35
36     @classmethod
37     def _register_attributes(cls):
38         snat = Attribute("snat", "Set SNAT=1", 
39                 type = Types.Bool,
40                 flags = Flags.Design)
41         
42         cls._register_attribute(snat)
43
44     def __init__(self, ec, guid):
45         super(PlanetlabTap, self).__init__(ec, guid)
46         self._home = "tap-%s" % self.guid
47         self._gre_enabled = False
48
49     @property
50     def node(self):
51         node = self.get_connected(PlanetlabNode.get_rtype())
52         if node: return node[0]
53         raise RuntimeError("TAP/TUN devices must be connected to Node")
54
55     def upload_sources(self):
56         scripts = []
57
58         # vif-creation python script
59         pl_vif_create = os.path.join(os.path.dirname(__file__), "scripts",
60                 "pl-vif-create.py")
61
62         scripts.append(pl_vif_create)
63         
64         # vif-up python script
65         pl_vif_up = os.path.join(os.path.dirname(__file__), "scripts",
66                 "pl-vif-up.py")
67         
68         scripts.append(pl_vif_up)
69
70         # vif-down python script
71         pl_vif_down = os.path.join(os.path.dirname(__file__), "scripts",
72                 "pl-vif-down.py")
73         
74         scripts.append(pl_vif_down)
75
76         # udp-connect python script
77         udp_connect = os.path.join(os.path.dirname(__file__), 
78                 "..",
79                 "linux",
80                 "scripts",
81                 "linux-udp-connect.py")
82         
83         scripts.append(udp_connect)
84
85         # tunnel creation python script
86         tunchannel = os.path.join(os.path.dirname(__file__), 
87                 "..", 
88                 "linux",
89                 "scripts", 
90                 "tunchannel.py")
91
92         scripts.append(tunchannel)
93
94         # Upload scripts
95         scripts = ";".join(scripts)
96
97         self.node.upload(scripts,
98                 os.path.join(self.node.src_dir),
99                 overwrite = False)
100
101         # upload stop.sh script
102         stop_command = self.replace_paths(self._stop_command)
103
104         self.node.upload_command(stop_command,
105                 shfile = os.path.join(self.app_home, "stop.sh"),
106                 # Overwrite file every time. 
107                 # The stop.sh has the path to the socket, which should change
108                 # on every experiment run.
109                 overwrite = True)
110
111     def upload_start_command(self):
112         super(PlanetlabTap, self).upload_start_command()
113
114         # Planetlab TAPs always add a PI header
115         self.set("pi", True)
116
117         if not self.gre_enabled:
118             # After creating the TAP, the pl-vif-create.py script
119             # will write the name of the TAP to a file. We wait until
120             # we can read the interface name from the file.
121             vif_name = self.wait_vif_name()
122             self.set("deviceName", vif_name) 
123
124     def wait_vif_name(self, exec_run_home = None):
125         """ Waits until the vif_name file for the command is generated, 
126             and returns the vif_name for the device """
127         vif_name = None
128         delay = 0.5
129
130         # The vif_name file will be created in the tap-home, while the
131         # current execution home might be elsewhere to check for errors
132         # (e.g. could be a tunnel-home)
133         if not exec_run_home:
134             exec_run_home = self.run_home
135
136         for i in range(20):
137             (out, err), proc = self.node.check_output(self.run_home, "vif_name")
138
139             if proc.poll() > 0:
140                 (out, err), proc = self.node.check_errors(exec_run_home)
141                 
142                 if err.strip():
143                     raise RuntimeError(err)
144
145             if out:
146                 vif_name = out.strip()
147                 break
148             else:
149                 time.sleep(delay)
150                 delay = delay * 1.5
151         else:
152             msg = "Couldn't retrieve vif_name"
153             self.error(msg, out, err)
154             raise RuntimeError(msg)
155
156         return vif_name
157
158     def gre_connect(self, remote_endpoint, connection_app_home,
159             connection_run_home):
160         super(PlanetlabTap, self).gre_connect(remote_endpoint, 
161                 connection_app_home, connection_run_home)
162          # After creating the TAP, the pl-vif-create.py script
163         # will write the name of the TAP to a file. We wait until
164         # we can read the interface name from the file.
165         vif_name = self.wait_vif_name(exec_run_home = connection_run_home)
166         self.set("deviceName", vif_name) 
167
168         return True
169
170     def _gre_connect_command(self, remote_endpoint, connection_app_home, 
171             connection_run_home): 
172         # Set the remote endpoint, (private) IP of the device
173         self.set("pointopoint", remote_endpoint.get("ip"))
174         # Public IP of the node
175         self.set("greRemote", remote_endpoint.node.get("ip"))
176
177         # Generate GRE connect command
178
179         # Use vif_down command to first kill existing TAP in GRE mode
180         vif_down_command = self._vif_down_command
181
182         # Use pl-vif-up.py script to configure TAP with peer info
183         vif_up_command = self._vif_up_command
184         
185         command = ["("]
186         command.append(vif_down_command)
187         command.append(") ; (")
188         command.append(vif_up_command)
189         command.append(")")
190
191         command = " ".join(command)
192         command = self.replace_paths(command)
193
194         return command
195
196     @property
197     def _start_command(self):
198         if self.gre_enabled:
199             command = []
200         else:
201             command = ["sudo -S python ${SRC}/pl-vif-create.py"]
202             
203             command.append("-t %s" % self.vif_type)
204             command.append("-a %s" % self.get("ip"))
205             command.append("-n %s" % self.get("prefix"))
206             command.append("-f %s " % self.vif_name_file)
207             command.append("-S %s " % self.sock_name)
208
209             if self.get("snat") == True:
210                 command.append("-s")
211
212             if self.get("pointopoint"):
213                 command.append("-p %s" % self.get("pointopoint"))
214             
215             if self.get("txqueuelen"):
216                 command.append("-q %s" % self.get("txqueuelen"))
217
218         return " ".join(command)
219
220     @property
221     def _stop_command(self):
222         if self.gre_enabled:
223             command = self._vif_down_command
224         else:
225             command = ["sudo -S "]
226             command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
227             command.append("python ${SRC}/pl-vif-down.py")
228             command.append("-S %s " % self.sock_name)
229             command = " ".join(command)
230
231         return command
232
233     @property
234     def _vif_up_command(self):
235         if self.gre_enabled:
236             device_name = "%s" % self.guid
237         else:
238             device_name = self.get("deviceName")
239
240         # Use pl-vif-up.py script to configure TAP
241         command = ["sudo -S "]
242         command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
243         command.append("python ${SRC}/pl-vif-up.py")
244         command.append("-u %s" % self.node.get("username"))
245         command.append("-N %s" % device_name)
246         command.append("-t %s" % self.vif_type)
247         command.append("-a %s" % self.get("ip"))
248         command.append("-n %s" % self.get("prefix"))
249
250         if self.get("snat") == True:
251             command.append("-s")
252
253         if self.get("pointopoint"):
254             command.append("-p %s" % self.get("pointopoint"))
255         
256         if self.get("txqueuelen"):
257             command.append("-q %s" % self.get("txqueuelen"))
258
259         if self.gre_enabled:
260             command.append("-g %s" % self.get("greKey"))
261             command.append("-G %s" % self.get("greRemote"))
262         
263         command.append("-f %s " % self.vif_name_file)
264
265         return " ".join(command)
266
267     @property
268     def _vif_down_command(self):
269         if self.gre_enabled:
270             device_name = "%s" % self.guid
271         else:
272             device_name = self.get("deviceName")
273
274         command = ["sudo -S "]
275         command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
276         command.append("python ${SRC}/pl-vif-down.py")
277         command.append("-N %s " % device_name)
278         
279         if self.gre_enabled:
280             command.append("-u %s" % self.node.get("username"))
281             command.append("-t %s" % self.vif_type)
282             command.append("-D")
283
284         return " ".join(command)
285
286     @property
287     def vif_name_file(self):
288         return os.path.join(self.run_home, "vif_name")
289
290     @property
291     def _install(self):
292         # Install python-vsys and python-passfd
293         install_vsys = ( " ( "
294                     "   python -c 'import vsys, os;  vsys.__version__ == \"%(version)s\" or os._exit(1)' "
295                     " ) "
296                     " || "
297                     " ( "
298                     "   cd ${SRC} ; "
299                     "   hg clone http://nepi.inria.fr/code/python-vsys ; "
300                     "   cd python-vsys ; "
301                     "   make all ; "
302                     "   sudo -S make install "
303                     " )" ) % ({
304                         "version": PYTHON_VSYS_VERSION
305                         })
306
307         install_passfd = ( " ( python -c 'import passfd' ) "
308                     " || "
309                     " ( "
310                     "   cd ${SRC} ; "
311                     "   hg clone http://nepi.inria.fr/code/python-passfd ; "
312                     "   cd python-passfd ; "
313                     "   make all ; "
314                     "   sudo -S make install "
315                     " )" )
316
317         return "%s ; %s" % ( install_vsys, install_passfd )
318
319     def valid_connection(self, guid):
320         # TODO: Validate!
321         return True
322