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 # Authors: Alina Quereilhac <alina.quereilhac@inria.fr>
19 # Alexandros Kouvakas <alexandros.kouvakas@inria.fr>
20 # Julien Tribino <julien.tribino@inria.fr>
23 from nepi.execution.attribute import Attribute, Flags, Types
24 from nepi.execution.resource import ResourceManager, ResourceFactory, clsinit_copy, \
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
37 reschedule_delay = "0.5s"
40 class OVSTunnel(LinuxApplication):
42 .. class:: Class Args :
44 :param ec: The Experiment controller
45 :type ec: ExperimentController
46 :param guid: guid of the RM
48 :param creds: Credentials to communicate with the rm
54 _authorized_connections = ["OVSPort", "PlanetlabTap"]
57 def _register_attributes(cls):
58 """ Register the attributes of OVSTunnel RM
61 network = Attribute("network", "IPv4 Network Address",
64 cipher = Attribute("cipher",
65 "Cipher to encript communication. "
66 "One of PLAIN, AES, Blowfish, DES, DES3. ",
68 allowed = ["PLAIN", "AES", "Blowfish", "DES", "DES3"],
69 type = Types.Enumerate,
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 "
78 txqueuelen = Attribute("txQueueLen",
79 "Specifies the interface's transmission queue length. "
84 bwlimit = Attribute("bwLimit",
85 "Specifies the interface's emulated bandwidth in bytes "
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)
96 def __init__(self, ec, guid):
98 :param ec: The Experiment controller
99 :type ec: ExperimentController
100 :param guid: guid of the RM
104 super(OVSTunnel, self).__init__(ec, guid)
105 self._home = "tunnel-%s" % self.guid
106 self.port_info_tunl = []
110 self._node_endpoint1 = None
111 self._node_endpoint2 = None
113 def log_message(self, msg):
114 return " guid %d - Tunnel - %s " % (self.guid, msg)
116 def app_home(self, node):
117 return os.path.join(node.exp_home, self._home)
119 def run_home(self, node):
120 return os.path.join(self.app_home(node), self.ec.run_id)
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):
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())
142 def check_switch_host_link(self):
143 """ Check if the links are between switches
144 or switch-host. Return False for the latter.
152 """ Return the list with the two connected elements.
153 Either Switch-Switch or Switch-Host
157 for guid in self.connections:
158 rm = self.ec.get_resource(guid)
159 if hasattr(rm, "create_port"):
160 connected[position] = rm
162 elif hasattr(rm, "udp_connect"):
166 def get_node(self, endpoint):
167 """ Get the nodes of the endpoint
170 if hasattr(endpoint, "create_port"):
171 rm_list = endpoint.get_connected(OVSSwitch.get_rtype())
173 rm = rm_list[0].get_connected(PlanetlabNode.get_rtype())
175 rm = endpoint.get_connected(PlanetlabNode.get_rtype())
182 """ Return the first endpoint : Always a Switch
184 endpoint = self.endpoints()
189 """ Return the second endpoint : Either a Switch or a TAP
191 endpoint = self.endpoints()
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
199 if self.check_switch_host_link :
200 host0, ip0, pname0, virt_ip0, pnumber0 = endpoint1.port_info
203 host0, ip0, pname0, virt_ip0, pnumber0 = endpoint1.port_info
204 host1, ip1, pname1, virt_ip1, pnumber1 = endpoint2.port_info
206 return pname0, ip1, pnumber1
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 """
215 #TODO : Need to change it with reschedule to avoid the problem
216 # of the order of connection
218 (out, err), proc = node_endpoint.check_output(self.run_home(node_endpoint), 'local_port')
220 local_port = int(out)
226 msg = "Couldn't retrieve local_port"
227 self.error(msg, out, err)
228 raise RuntimeError, msg
232 def connection(self, local_endpoint, rm_endpoint):
233 """ Create the connect command for each case :
238 local_node = self.get_node(local_endpoint)
239 local_node.mkdir(self.run_home(local_node))
241 rm_node = self.get_node(rm_endpoint)
242 rm_node.mkdir(self.run_home(rm_node))
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"))
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")
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,
265 self._pid, self._ppid = local_endpoint.udp_connect(
266 rm_node, connection_run_home, connection_app_home,
267 cipher, cipher_key, bwlimit, txqueuelen)
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)
274 # self.connection_command(connect_command, local_node, rm_node)
276 # # Wait for pid file to be generated
277 # self._pid, self._ppid = local_node.wait_pid(self.run_home(local_node))
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
283 msg = " Failed to start connection of the OVS Tunnel "
284 self.error(msg, out, err)
285 raise RuntimeError, msg
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"))
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)
298 connect_command = local_endpoint.switch_connect_command(
299 local_port_name, remote_ip, remote_port_num)
301 self.connection_command(connect_command, local_node, rm_node)
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.
307 shfile = os.path.join(self.app_home(node_endpoint), "sw_connect.sh")
308 node_endpoint.upload(command,
313 # Invoke connect script
315 cmd = "bash %s" % shfile
316 (out, err), proc = node_endpoint.run(cmd, self.run_home(node_endpoint),
318 stdout = "sw_stdout",
319 stderr = "sw_stderr")
321 # Check if execution errors occured
324 msg = "Failed to connect endpoints"
325 self.error(msg, out, err)
326 raise RuntimeError, msg
329 msg = "Connection on port configured"
332 def do_provision(self):
333 """ Provision the tunnel
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)
341 def configure_route(self):
342 """ Configure the route for the tap device
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
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"))
354 self.ec.register_connection(self._vroute, self.tap.guid)
355 self.ec.deploy(guids=[self._vroute], group = self.deployment_group)
358 """ Deploy the tunnel after the endpoint get ready
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)
367 self.configure_route()
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()
376 def do_release(self):
377 """ Release the tunnel by releasing the Tap Device if exists
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)
386 if err or proc.poll():
387 msg = " Failed to delete TAP device"
388 self.error(msg, out, err)
390 super(OVSTunnel, self).do_release()