Remove the ovs example under planetlab. Let only the ones under openvswicth folder
[nepi.git] / src / nepi / resources / planetlab / openvswitch / tunnel.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 as published by
7 #    the Free Software Foundation, either version 3 of the License, or
8 #    (at your option) any later version.
9 #
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.
14 #
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/>.
17 #
18 # Authors: Alina Quereilhac <alina.quereilhac@inria.fr>
19 #         Alexandros Kouvakas <alexandros.kouvakas@inria.fr>
20 #         Julien Tribino <julien.tribino@inria.fr>
21
22
23 from nepi.execution.attribute import Attribute, Flags, Types
24 from nepi.execution.resource import ResourceManager, ResourceFactory, clsinit_copy, \
25         ResourceState
26 from nepi.resources.linux.application import LinuxApplication
27 from nepi.resources.planetlab.node import PlanetlabNode            
28 from nepi.resources.planetlab.openvswitch.ovs import OVSSwitch   
29 from nepi.util.timefuncs import tnow, tdiffsec    
30 from nepi.resources.planetlab.vroute import PlanetlabVroute
31 from nepi.resources.planetlab.tap import PlanetlabTap
32
33 import os
34 import time
35 import socket
36
37 reschedule_delay = "0.5s"
38
39 @clsinit_copy                 
40 class OVSTunnel(LinuxApplication):
41     """
42     .. class:: Class Args :
43       
44         :param ec: The Experiment controller
45         :type ec: ExperimentController
46         :param guid: guid of the RM
47         :type guid: int
48         :param creds: Credentials to communicate with the rm 
49         :type creds: dict
50
51     """
52     
53     _rtype = "OVSTunnel"
54     _authorized_connections = ["OVSPort", "PlanetlabTap"]    
55
56     @classmethod
57     def _register_attributes(cls):
58         """ Register the attributes of OVSTunnel RM 
59
60         """
61         network = Attribute("network", "IPv4 Network Address",
62                flags = Flags.Design)
63
64         cipher = Attribute("cipher",
65                "Cipher to encript communication. "
66                 "One of PLAIN, AES, Blowfish, DES, DES3. ",
67                 default = None,
68                 allowed = ["PLAIN", "AES", "Blowfish", "DES", "DES3"],
69                 type = Types.Enumerate, 
70                 flags = Flags.Design)
71
72         cipher_key = Attribute("cipherKey",
73                 "Specify a symmetric encryption key with which to protect "
74                 "packets across the tunnel. python-crypto must be installed "
75                 "on the system." ,
76                 flags = Flags.Design)
77
78         txqueuelen = Attribute("txQueueLen",
79                 "Specifies the interface's transmission queue length. "
80                 "Defaults to 1000. ", 
81                 type = Types.Integer, 
82                 flags = Flags.Design)
83
84         bwlimit = Attribute("bwLimit",
85                 "Specifies the interface's emulated bandwidth in bytes "
86                 "per second.",
87                 type = Types.Integer, 
88                 flags = Flags.Design)
89
90         cls._register_attribute(network)
91         cls._register_attribute(cipher)
92         cls._register_attribute(cipher_key)
93         cls._register_attribute(txqueuelen)
94         cls._register_attribute(bwlimit)
95
96     def __init__(self, ec, guid):
97         """
98         :param ec: The Experiment controller
99         :type ec: ExperimentController
100         :param guid: guid of the RM
101         :type guid: int
102     
103         """
104         super(OVSTunnel, self).__init__(ec, guid)
105         self._home = "tunnel-%s" % self.guid
106         self.port_info_tunl = []
107         self._pid = None
108         self._ppid = None
109         self._vroute = None
110         self._node_endpoint1 = None
111         self._node_endpoint2 = None
112
113     def log_message(self, msg):
114         return " guid %d - Tunnel - %s " % (self.guid, msg)
115
116     def app_home(self, node):
117         return os.path.join(node.exp_home, self._home)
118
119     def run_home(self, node):
120         return os.path.join(self.app_home(node), self.ec.run_id)
121
122     @property
123     def tap(self):
124         """ Return the Tap RM if it exists """
125         rclass = ResourceFactory.get_resource_type(PlanetlabTap.get_rtype())
126         for guid in self.connections:
127             rm = self.ec.get_resource(guid)
128             if isinstance(rm, rclass):
129                 return rm
130
131     @property
132     def ovsswitch(self):
133         """ Return the 1st switch """
134         for guid in self.connections:
135             rm_port = self.ec.get_resource(guid)
136             if hasattr(rm_port, "create_port"):
137                 rm_list = rm_port.get_connected(OVSSwitch.get_rtype())
138                 if rm_list:
139                     return rm_list[0]
140
141     @property         
142     def check_switch_host_link(self):
143         """ Check if the links are between switches
144             or switch-host. Return False for the latter.
145         """
146         if self.tap :
147             return True
148         return False
149
150
151     def endpoints(self):
152         """ Return the list with the two connected elements.
153         Either Switch-Switch or Switch-Host
154         """
155         connected = [1, 1]
156         position = 0
157         for guid in self.connections:
158             rm = self.ec.get_resource(guid)
159             if hasattr(rm, "create_port"):
160                 connected[position] = rm
161                 position += 1
162             elif hasattr(rm, "udp_connect"):
163                 connected[1] = rm
164         return connected
165
166     def get_node(self, endpoint):
167         """ Get the nodes of the endpoint
168         """
169         rm = []
170         if hasattr(endpoint, "create_port"):
171             rm_list = endpoint.get_connected(OVSSwitch.get_rtype())
172             if rm_list:
173                 rm = rm_list[0].get_connected(PlanetlabNode.get_rtype())
174         else:
175             rm = endpoint.get_connected(PlanetlabNode.get_rtype())
176
177         if rm :
178             return rm[0]
179
180     @property
181     def endpoint1(self):
182         """ Return the first endpoint : Always a Switch
183         """
184         endpoint = self.endpoints()
185         return endpoint[0]
186
187     @property
188     def endpoint2(self):
189         """ Return the second endpoint : Either a Switch or a TAP
190         """
191         endpoint = self.endpoints()
192         return endpoint[1]
193
194     def get_port_info(self, endpoint1, endpoint2):
195         #TODO : Need to change it. Really bad to have method that return different type of things !!!!!
196         """ Retrieve the port_info list for each port
197         
198         """
199         if self.check_switch_host_link :
200             host0, ip0, pname0, virt_ip0, pnumber0 = endpoint1.port_info
201             return pnumber0
202
203         host0, ip0, pname0, virt_ip0, pnumber0 = endpoint1.port_info
204         host1, ip1, pname1, virt_ip1, pnumber1 = endpoint2.port_info
205
206         return pname0, ip1, pnumber1
207     
208     def wait_local_port(self, node_endpoint):
209         """ Waits until the if_name file for the command is generated, 
210             and returns the if_name for the device """
211
212         local_port = None
213         delay = 1.0
214
215         #TODO : Need to change it with reschedule to avoid the problem 
216         #        of the order of connection
217         for i in xrange(10):
218             (out, err), proc = node_endpoint.check_output(self.run_home(node_endpoint), 'local_port')
219             if out:
220                 local_port = int(out)
221                 break
222             else:
223                 time.sleep(delay)
224                 delay = delay * 1.5
225         else:
226             msg = "Couldn't retrieve local_port"
227             self.error(msg, out, err)
228             raise RuntimeError, msg
229
230         return local_port
231
232     def connection(self, local_endpoint, rm_endpoint):
233         """ Create the connect command for each case : 
234               - Host - Switch,  
235               - Switch - Switch,  
236               - Switch - Host
237         """
238         local_node = self.get_node(local_endpoint)
239         local_node.mkdir(self.run_home(local_node))
240
241         rm_node = self.get_node(rm_endpoint)
242         rm_node.mkdir(self.run_home(rm_node))
243
244         # Host to switch
245         if self.check_switch_host_link and local_endpoint == self.endpoint2 :
246         # Collect info from rem_endpoint
247             remote_ip = socket.gethostbyname(rm_node.get("hostname"))
248
249         # Collect info from endpoint
250             connection_run_home = self.run_home(local_node)
251             connection_app_home = self.app_home(local_node)
252             cipher = self.get("cipher")
253             cipher_key = self.get("cipherKey")
254             bwlimit = self.get("bwLimit")
255             txqueuelen = self.get("txQueueLen")
256
257            
258         # Upload the remote port in a file
259             rem_port = str(self.get_port_info(rm_endpoint,local_endpoint))
260             rem_port_file = os.path.join(self.run_home(local_node), "remote_port")
261             local_node.upload(rem_port, rem_port_file,
262                  text = True,
263                  overwrite = False)
264        
265             self._pid, self._ppid = local_endpoint.udp_connect(
266                  rm_node, connection_run_home, connection_app_home,
267                  cipher, cipher_key, bwlimit, txqueuelen) 
268
269
270 #            connect_command = local_endpoint.udp_connect_command(
271 #                 remote_ip, local_port_file, rem_port_file,
272 #                 ret_file, cipher, cipher_key, bwlimit, txqueuelen) 
273
274 #            self.connection_command(connect_command, local_node, rm_node)
275
276 #        # Wait for pid file to be generated
277 #            self._pid, self._ppid = local_node.wait_pid(self.run_home(local_node))
278
279             if not self._pid or not self._ppid:
280                 (out, err), proc = local_node.check_errors(self.run_home(local_node))
281                 # Out is what was written in the stderr file
282                 if err:
283                     msg = " Failed to start connection of the OVS Tunnel "
284                     self.error(msg, out, err)
285                     raise RuntimeError, msg
286             return
287
288         # Switch to Host
289         if self.check_switch_host_link and local_endpoint == self.endpoint1:
290             local_port_name = local_endpoint.get('port_name')
291             remote_port_num = self.wait_local_port(rm_node)
292             remote_ip = socket.gethostbyname(rm_node.get("hostname"))
293   
294         # Switch to Switch
295         if not self.check_switch_host_link :
296             local_port_name, remote_ip, remote_port_num = self.get_port_info(local_endpoint, rm_endpoint)
297
298         connect_command = local_endpoint.switch_connect_command(
299                     local_port_name, remote_ip, remote_port_num)
300
301         self.connection_command(connect_command, local_node, rm_node)       
302
303     def connection_command(self, command, node_endpoint, rm_node_endpoint):
304         """ Execute the connection command on the node and check if the processus is
305             correctly running on the node.
306         """
307         shfile = os.path.join(self.app_home(node_endpoint), "sw_connect.sh")
308         node_endpoint.upload(command,
309                 shfile,
310                 text = True,
311                 overwrite = False)
312
313         # Invoke connect script
314         out = err= ''       
315         cmd = "bash %s" % shfile
316         (out, err), proc = node_endpoint.run(cmd, self.run_home(node_endpoint),
317                 sudo  = True,
318                 stdout = "sw_stdout",
319                 stderr = "sw_stderr")
320         
321         # Check if execution errors occured
322
323         if proc.poll():
324             msg = "Failed to connect endpoints"
325             self.error(msg, out, err)
326             raise RuntimeError, msg
327
328         # For debugging
329         msg = "Connection on port configured"
330         self.debug(msg)
331
332     def do_provision(self):
333         """ Provision the tunnel
334         """
335         
336         #TODO : The order of the connection is important for now ! 
337         # Need to change the code of wait local port
338         self.connection(self.endpoint2, self.endpoint1)
339         self.connection(self.endpoint1, self.endpoint2)
340
341     def configure_route(self):
342         """ Configure the route for the tap device
343
344             .. note : In case of a conection between a switch and a host, a route
345                       was missing on the node with the Tap Device. This method create
346                       the missing route. 
347         """
348
349         if  self.check_switch_host_link:
350             self._vroute = self.ec.register_resource("PlanetlabVroute")
351             self.ec.set(self._vroute, "action", "add")
352             self.ec.set(self._vroute, "network", self.get("network"))
353
354             self.ec.register_connection(self._vroute, self.tap.guid)
355             self.ec.deploy(guids=[self._vroute], group = self.deployment_group)
356
357     def do_deploy(self):
358         """ Deploy the tunnel after the endpoint get ready
359         """
360         if (not self.endpoint1 or self.endpoint1.state < ResourceState.READY) or \
361             (not self.endpoint2 or self.endpoint2.state < ResourceState.READY):
362             self.ec.schedule(reschedule_delay, self.deploy)
363             return
364
365         self.do_discover()
366         self.do_provision()
367         self.configure_route()
368
369         # Cannot call the deploy of the linux application 
370         #         because of a log error.
371         # Need to investigate if it is right that the tunnel 
372         #    inherits from the linux application
373         #  super(OVSTunnel, self).do_deploy()
374         self.set_ready()
375  
376     def do_release(self):
377         """ Release the tunnel by releasing the Tap Device if exists
378         """
379         if self.check_switch_host_link:
380             # TODO: Make more generic Release method of PLTAP
381             tap_node = self.get_node(self.endpoint2)
382             if self._pid and self._ppid:
383                 (out, err), proc = tap_node.kill(self._pid,
384                         self._ppid, sudo = True)
385
386                 if err or proc.poll():
387                     msg = " Failed to delete TAP device"
388                     self.error(msg, out, err)
389
390         super(OVSTunnel, self).do_release()
391