8275249f186a17c2a1d211e6944a29a3116c7486
[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 OVSSwitch        
26 from nepi.resources.planetlab.node import PlanetlabNode        
27 from nepi.resources.linux.application import LinuxApplication
28
29 import os
30
31 reschedule_delay = "0.5s"
32
33 @clsinit_copy                 
34 class OVSPort(LinuxApplication):
35     """
36     .. class:: Class Args :
37       
38         :param ec: The Experiment controller
39         :type ec: ExperimentController
40         :param guid: guid of the RM
41         :type guid: int
42
43     """
44     
45     _rtype = "OVSPort"
46     _help = "Runs an OpenVSwitch on a PlanetLab host"
47     _backend = "planetlab"
48
49     _authorized_connections = ["OVSSwitch", "LinuxUdpTunnel", "LinuxTunnel"]      
50
51     @classmethod
52     def _register_attributes(cls):
53         """ Register the attributes of OVSPort RM 
54
55         """
56         port_name = Attribute("port_name", "Name of the port",
57             flags = Flags.Design)                       
58         endpoint_ip = Attribute("endpoint_ip", "IP of the endpoint. This is the attribute " 
59                                 "you should use to establish a tunnel or a remote "
60                                 "connection between endpoint",
61             flags = Flags.Design)
62         network = Attribute("network", "Network used by the port",
63             flags = Flags.Design)       
64
65         cls._register_attribute(port_name)
66         cls._register_attribute(endpoint_ip)
67         cls._register_attribute(network)
68
69     def __init__(self, ec, guid):
70         """
71         :param ec: The Experiment controller
72         :type ec: ExperimentController
73         :param guid: guid of the RM
74         :type guid: int
75     
76         """
77         super(OVSPort, self).__init__(ec, guid)
78
79
80         self._port_number = None
81         # in case of connection by tunnel        
82         self._remote_ip = None    
83
84     def log_message(self, msg):
85         return " guid %d - OVSPort - %s " % (self.guid, msg)
86
87     @property
88     def node(self):
89         """ Node that run the switch and the ports
90         """
91         rm_list = self.get_connected(OVSSwitch.get_rtype())
92         if rm_list:
93             for elt in rm_list:
94                 node = elt.get_connected(PlanetlabNode.get_rtype())
95                 if node: return node[0]
96         return node[0]
97
98     @property
99     def ovsswitch(self):
100         """ Switch where the port is created
101         """
102         ovsswitch = self.get_connected(OVSSwitch.get_rtype())
103         if ovsswitch: return ovsswitch[0]
104         return None
105         
106     @property
107     def remote_ip(self):
108         return self._remote_ip
109
110     @property
111     def port_number(self):
112         return self._port_number
113
114     def valid_connection(self, guid):
115         """ Check if the connection is available.
116
117         :param guid: Guid of the current RM
118         :type guid: int
119         :rtype:  Boolean
120
121         """
122         rm = self.ec.get_resource(guid)
123         if rm.get_rtype() in self._authorized_connections:
124             msg = "Connection between %s %s and %s %s accepted" % (self.get_rtype(), self._guid, rm.get_rtype(), guid)
125             self.debug(msg)
126             return True
127         msg = "Connection between %s %s and %s %s refused" % (self.get_rtype(), self._guid, rm.get_rtype(), guid)
128         self.debug(msg)
129
130     def create_port(self):
131         """ Create the desired port
132         """
133         msg = "Creating the port %s" % self.get('port_name')
134         self.debug(msg)
135
136         if not self.get('port_name'):
137             msg = "The port name is not assigned"
138             self.error(msg)
139             raise AttributeError, msg
140
141         if not self.ovsswitch:
142             msg = "The OVSwitch RM is not running"
143             self.error(msg)
144             raise AttributeError, msg
145
146         cmd = "sliver-ovs create-port %s %s" % (self.ovsswitch.get('bridge_name'),
147                                                 self.get('port_name'))   
148         self.node.run(cmd, self.ovsswitch.ovs_checks, 
149                 stderr = "stdout-%s" % self.get('port_name'), 
150                 stdout = "stderr-%s" % self.get('port_name'),
151                 sudo = True)
152
153         self.info("Created the port %s on switch %s" % (self.get('port_name'),
154                                              self.ovsswitch.get('bridge_name')))     
155             
156     def initiate_udp_connection(self, remote_endpoint, connection_app_home, 
157             connection_run_home, cipher, cipher_key, bwlimit, txqueuelen):
158         """ Get the local_endpoint of the port
159         """
160
161         self._remote_ip = remote_endpoint.node.get("ip")
162
163         msg = "Discovering the number of the port %s" % self.get('port_name')
164         self.info(msg)
165
166         command = "sliver-ovs get-local-endpoint %s" % self.get('port_name')
167         out = err = ""
168         (out, err), proc = self.node.run_and_wait(command, 
169                 self.ovsswitch.ovs_checks,
170                 shfile = "port_number-%s.sh" % self.get('port_name'),
171                 pidfile = "port_number_pidfile-%s" % self.get('port_name'),
172                 ecodefile = "port_number_exitcode-%s" % self.get('port_name'), 
173                 sudo = True, 
174                 stdout = "stdout-%s" % self.get('port_name'),    
175                 stderr = "stderr-%s" % self.get('port_name'))
176
177         if err != "":
178             msg = "Error retrieving the local endpoint of the port"
179             self.error(msg)
180             raise AttributeError, msg
181
182         if out:
183             self._port_number = int(out)
184
185         self.info("The number of the %s is %s" % (self.get('port_name'), 
186            self.port_number))
187
188         if remote_endpoint.is_rm_instance("PlanetlabTap"):
189             self._vroute = self.ec.register_resource("PlanetlabVroute")
190             self.ec.set(self._vroute, "action", "add")
191             self.ec.set(self._vroute, "network", self.get("network"))
192
193             print "Vroute Guid :" + str(self._vroute)
194
195             self.ec.register_connection(self._vroute, remote_endpoint.guid)
196             self.ec.deploy(guids=[self._vroute], group = self.deployment_group)
197
198             # For debugging
199             msg = "Route for the tap configured"
200             self.debug(msg)
201
202         return self.port_number
203
204
205     def establish_udp_connection(self,remote_endpoint, port):
206         establish_connection_command = self._establish_connection_command(port)
207
208         # upload command to connect.sh script
209         shfile = os.path.join(self.app_home, "sw-connect.sh")
210         self.node.upload_command(establish_connection_command,
211                 shfile = shfile,
212                 overwrite = False)
213
214         # invoke connect script
215         cmd = "bash %s" % shfile
216         (out, err), proc = self.node.run(cmd, self.run_home,
217                 sudo  = True,
218                 stdout = "sw_stdout",
219                 stderr = "sw_stderr") 
220              
221         # check if execution errors occurred
222         msg = "Failed to connect endpoints "
223         if proc.poll():
224             self.error(msg, out, err)
225             raise RuntimeError, msg
226     
227         # Wait for pid file to be generated
228         self._pid, self._ppid = self.node.wait_pid(self.run_home)
229         
230         # If the process is not running, check for error information
231         # on the remote machine
232         if not self._pid or not self._ppid:
233             (out, err), proc = self.node.check_errors(self.run_home)
234             # Out is what was written in the stderr file
235             if err:
236                 msg = " Failed to start command '%s' " % command
237                 self.error(msg, out, err)
238                 raise RuntimeError, msg
239
240         # For debugging
241         msg = "Connection on port configured"
242         self.debug(msg)
243
244
245     def _establish_connection_command(self, port):
246         """ Script to create the connection from a switch to a 
247              remote endpoint
248         """
249         local_port_name = self.get('port_name')
250
251         command = ["sliver-ovs"]
252         command.append("set-remote-endpoint ")
253         command.append("%s " % local_port_name)
254         command.append("%s " % self.remote_ip)
255         command.append("%s " % port)
256         command = " ".join(command)
257         command = self.replace_paths(command)
258         return command
259         
260     def verify_connection(self):
261         self.ovsswitch.ovs_status()
262
263     def terminate_connection(self):
264         return True
265
266     def check_status(self):
267         return self.node.status(self._pid, self._ppid)
268
269     def do_deploy(self):
270         """ Deploy the OVS port after the OVS Switch
271         """
272
273         if not self.ovsswitch or self.ovsswitch.state < ResourceState.READY:       
274             self.debug("---- RESCHEDULING DEPLOY ---- OVSwitch state %s " % self.ovsswitch.state )  
275             self.ec.schedule(reschedule_delay, self.deploy)
276             return
277
278         self.do_discover()
279         self.do_provision()
280
281         self.create_port()
282         end_ip = self.ovsswitch.get('virtual_ip_pref').split('/')
283         self.set("endpoint_ip", end_ip[0])
284
285         #Check the status of the OVS Switch
286         self.ovsswitch.ovs_status()
287
288         super(OVSPort, self).do_deploy()
289
290     def do_release(self):
291         """ Delete the port on the OVSwitch. It needs to wait for the tunnel
292         to be released.
293         """
294         from nepi.resources.linux.udptunnel import LinuxUdpTunnel
295         rm = self.get_connected(LinuxUdpTunnel.get_rtype())
296
297         if rm and rm[0].state < ResourceState.STOPPED:
298             self.ec.schedule(reschedule_delay, self.release)
299             return 
300             
301         cmd = "sliver-ovs del_port %s" % self.get('port_name')
302         (out, err), proc = self.node.run(cmd, self.ovsswitch.ovs_checks,
303                 sudo = True)
304
305         msg = "Deleting the port %s" % self.get('port_name')
306         self.info(msg)
307
308         if proc.poll():
309             self.error(msg, out, err)
310             raise RuntimeError, msg
311
312         super(OVSPort, self).do_release()
313