Serious refactoring of TUN/TAP and tunnel code. Linux udp/gre tunnels not yet functional
[nepi.git] / src / nepi / resources / planetlab / openvswitch / ovsport.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 from nepi.execution.attribute import Attribute, Flags, Types
23 from nepi.execution.resource import ResourceManager, clsinit_copy, \
24         ResourceState
25 from nepi.resources.planetlab.openvswitch.ovs import PlanetlabOVSSwitch        
26 from nepi.resources.planetlab.node import PlanetlabNode        
27 from nepi.resources.linux.application import LinuxApplication
28
29 import os
30
31 @clsinit_copy                 
32 class PlanetlabOVSPort(LinuxApplication):
33     """
34     .. class:: Class Args :
35       
36         :param ec: The Experiment controller
37         :type ec: ExperimentController
38         :param guid: guid of the RM
39         :type guid: int
40
41     """
42     
43     _rtype = "planetlab::OVSPort"
44     _help = "Runs an OpenVSwitch on a PlanetLab host"
45     _platform = "planetlab"
46
47     _authorized_connections = ["planetlab::OVSSwitch", "linux::UdpTunnel", "linux::Tunnel"]      
48
49     @classmethod
50     def _register_attributes(cls):
51         """ Register the attributes of OVSPort RM 
52
53         """
54         port_name = Attribute("port_name", "Name of the port",
55             flags = Flags.Design)                       
56         ip = Attribute("ip", "IP of the endpoint. This is the attribute " 
57                                 "you should use to establish a tunnel or a remote "
58                                 "connection between endpoint",
59             flags = Flags.Design)
60         network = Attribute("network", "Network used by the port",
61             flags = Flags.Design)       
62
63         cls._register_attribute(port_name)
64         cls._register_attribute(ip)
65         cls._register_attribute(network)
66
67     def __init__(self, ec, guid):
68         """
69         :param ec: The Experiment controller
70         :type ec: ExperimentController
71         :param guid: guid of the RM
72         :type guid: int
73     
74         """
75         super(PlanetlabOVSPort, self).__init__(ec, guid)
76         self._home = "ovsport-%s" % self.guid
77         self._port_number = None
78
79     @property
80     def node(self):
81         """ Node that run the switch and the ports
82         """
83         return self.ovsswitch.node
84
85     @property
86     def ovsswitch(self):
87         """ Switch where the port is created
88         """
89         ovsswitch = self.get_connected(PlanetlabOVSSwitch.get_rtype())
90         if ovsswitch: return ovsswitch[0]
91         return None
92         
93     @property
94     def port_number(self):
95         return self._port_number
96
97     def valid_connection(self, guid):
98         """ Check if the connection is available.
99
100         :param guid: Guid of the current RM
101         :type guid: int
102         :rtype:  Boolean
103
104         """
105         rm = self.ec.get_resource(guid)
106         if rm.get_rtype() not in self._authorized_connections:
107             return False
108
109         return True
110
111     def create_port(self):
112         """ Create the desired port
113         """
114         msg = "Creating the port %s" % self.get('port_name')
115         self.debug(msg)
116
117         if not self.get('port_name'):
118             msg = "The port name is not assigned"
119             self.error(msg)
120             raise AttributeError, msg
121
122         if not self.ovsswitch:
123             msg = "The OVSwitch RM is not running"
124             self.error(msg)
125             raise AttributeError, msg
126
127         command = "sliver-ovs create-port %s %s" % (
128                 self.ovsswitch.get('bridge_name'),
129                 self.get('port_name'))   
130         
131         shfile = os.path.join(self.app_home, "create_port.sh")
132         try:
133             self.node.run_and_wait(command, self.run_home,
134                     shfile=shfile,
135                     sudo = True,
136                     stderr="port_stdout", 
137                     stdout="port_stderr",
138                     pidfile="port_pidfile",
139                     ecodefile="port_exitcode")
140         except RuntimeError:
141             msg = "Could not create ovs-port"            
142             self.debug(msg)
143             raise RuntimeError, msg
144
145         self.info("Created port %s on switch %s" % (
146             self.get('port_name'),
147             self.ovsswitch.get('bridge_name')))     
148             
149     def initiate_udp_connection(self, remote_endpoint, connection_app_home, 
150             connection_run_home, cipher, cipher_key, bwlimit, txqueuelen):
151         """ Get the local_endpoint of the port
152         """
153         msg = "Discovering the port number for %s" % self.get('port_name')
154         self.info(msg)
155
156         command = "sliver-ovs get-local-endpoint %s" % self.get('port_name')
157
158         shfile = os.path.join(connection_app_home, "get_port.sh")
159         (out, err), proc = self.node.run_and_wait(command, connection_run_home,
160                 shfile=shfile,
161                 sudo=True, 
162                 overwrite = True,
163                 pidfile="get_port_pidfile",
164                 ecodefile="get_port_exitcode", 
165                 stdout="get_port_stdout",    
166                 stderr="get_port_stderr")
167
168         if err != "":
169             msg = "Error retrieving the local endpoint of the port"
170             self.error(msg)
171             raise RuntimeError, msg
172
173         if out:
174             self._port_number = out.strip()
175
176         self.info("The number of the %s is %s" % (self.get('port_name'), 
177            self.port_number))
178
179         # Must set a routing rule in the ovs client nodes so they know
180         # that the LAN can be found through the switch
181         if remote_endpoint.is_rm_instance("planetlab::Tap"):
182             self._vroute = self.ec.register_resource("planetlab::Vroute")
183             self.ec.set(self._vroute, "action", "add")
184             self.ec.set(self._vroute, "prefix", remote_endpoint.get("prefix"))
185             self.ec.set(self._vroute, "nexthop", remote_endpoint.get("pointopoint"))
186             self.ec.set(self._vroute, "network", self.get("network"))
187
188             self.ec.register_connection(self._vroute, remote_endpoint.guid)
189             self.ec.deploy(guids=[self._vroute], group = self.deployment_group)
190
191             # For debugging
192             msg = "Route for the tap configured"
193             self.debug(msg)
194
195         return self.port_number
196
197     def establish_udp_connection(self, remote_endpoint,
198             connection_app_home,
199             connection_run_home, 
200             port):
201         remote_ip = remote_endpoint.node.get("ip")
202         command = self._establish_connection_command(port, remote_ip)
203
204         shfile = os.path.join(connection_app_home, "connect_port.sh")
205         (out, err), proc = self.node.run_and_wait(command, connection_run_home,
206                 shfile=shfile,
207                 sudo=True, 
208                 overwrite = True,
209                 pidfile="connect_port_pidfile",
210                 ecodefile="connect_port_exitcode", 
211                 stdout="connect_port_stdout",    
212                 stderr="connect_port_stderr")
213
214         # For debugging
215         msg = "Connection on port configured"
216         self.debug(msg)
217
218     def _establish_connection_command(self, port, remote_ip):
219         """ Script to create the connection from a switch to a 
220              remote endpoint
221         """
222         local_port_name = self.get('port_name')
223
224         command = ["sliver-ovs"]
225         command.append("set-remote-endpoint")
226         command.append(local_port_name)
227         command.append(remote_ip)
228         command.append(port)
229         command = " ".join(command)
230         command = self.replace_paths(command)
231         return command
232        
233     def verify_connection(self, remote_endpoint, connection_app_home, 
234                 connection_run_home):
235         self.ovsswitch.ovs_status()
236
237     def terminate_connection(self, endpoint, connection_app_home, 
238                 connection_run_home):
239         return True
240
241     def check_status(self):
242         return self.node.status(self._pid, self._ppid)
243
244     def do_provision(self):
245         self.node.mkdir(self.run_home)
246
247         self.create_port()
248         end_ip = self.ovsswitch.get('virtual_ip_pref').split('/')
249         self.set("ip", end_ip[0])
250
251         #Check the status of the OVS Switch
252         self.ovsswitch.ovs_status()
253     
254         self.set_provisioned()
255                 
256     def do_deploy(self):
257         """ Deploy the OVS port after the OVS Switch
258         """
259         if not self.ovsswitch or self.ovsswitch.state < ResourceState.READY:       
260             self.debug("---- RESCHEDULING DEPLOY ---- OVSwitch state %s " % self.ovsswitch.state )  
261             self.ec.schedule(self.reschedule_delay, self.deploy)
262         else:
263             self.do_discover()
264             self.do_provision()
265
266             self.set_ready()
267
268     def do_release(self):
269         """ Delete the port on the OVSwitch. It needs to wait for the tunnel
270         to be released.
271         """
272         from nepi.resources.linux.udptunnel import LinuxUdpTunnel
273         rm = self.get_connected(LinuxUdpTunnel.get_rtype())
274
275         if rm and rm[0].state < ResourceState.STOPPED:
276             self.ec.schedule(self.reschedule_delay, self.release)
277             return 
278             
279         msg = "Deleting the port %s" % self.get('port_name')
280         self.info(msg)
281
282         command = "sliver-ovs del_port %s" % self.get('port_name')
283
284         shfile = os.path.join(self.app_home, "stop.sh")
285         self.node.run_and_wait(command, self.run_home,
286                 shfile=shfile,
287                 sudo=True, 
288                 pidfile="stop_pidfile",
289                 ecodefile="stop_exitcode", 
290                 stdout="stop_stdout", 
291                 stderr="stop_stderr")
292
293         super(PlanetlabOVSPort, self).do_release()
294