From 7f6a711e6d4a245a1f4b104ab9e999af484a8d69 Mon Sep 17 00:00:00 2001 From: Alina Quereilhac Date: Mon, 15 Jul 2013 18:59:31 -0700 Subject: [PATCH] Adding working UdpTunnel for Planetlab and Linux --- Makefile | 4 +- setup.py | 2 +- src/nepi/execution/resource.py | 2 +- src/nepi/resources/linux/application.py | 8 +- .../{all => linux}/scripts/tunchannel.py | 0 src/nepi/resources/linux/udptunnel.py | 304 ++++++++++++++++++ .../planetlab/scripts/pl-vif-tunconnect.py | 70 ---- .../planetlab/scripts/pl-vif-udp-connect.py | 156 +++++++++ src/nepi/resources/planetlab/tap.py | 53 ++- test/lib/test_utils.py | 19 ++ test/resources/planetlab/tap.py | 2 +- test/resources/planetlab/tun.py | 2 +- test/resources/planetlab/udptunnel.py | 95 ++++++ 13 files changed, 621 insertions(+), 96 deletions(-) rename src/nepi/resources/{all => linux}/scripts/tunchannel.py (100%) create mode 100644 src/nepi/resources/linux/udptunnel.py delete mode 100644 src/nepi/resources/planetlab/scripts/pl-vif-tunconnect.py create mode 100644 src/nepi/resources/planetlab/scripts/pl-vif-udp-connect.py create mode 100755 test/resources/planetlab/udptunnel.py diff --git a/Makefile b/Makefile index c2fb9517..24e5b158 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ PYPATH = $(BUILDDIR):$(TESTLIB):$(PYTHONPATH) COVERAGE = $(or $(shell which coverage), $(shell which python-coverage), \ coverage) -all: +all: clean PYTHONPATH="$(PYTHONPATH):$(SRCDIR)" ./setup.py build install: all @@ -49,8 +49,8 @@ coverage: all rm -f .coverage clean: - ./setup.py clean rm -f `find -name \*.pyc` .coverage *.pcap + rm -rf "$(BUILDDIR)" distclean: clean rm -rf "$(DISTDIR)" diff --git a/setup.py b/setup.py index 407e8422..0c68d5b5 100755 --- a/setup.py +++ b/setup.py @@ -28,6 +28,6 @@ setup( package_dir = {"": "src"}, package_data = { "nepi.resources.planetlab" : [ "scripts/*.py" ], - "nepi.resources.all" : [ "scripts/*.py" ] + "nepi.resources.linux" : [ "scripts/*.py" ] } ) diff --git a/src/nepi/execution/resource.py b/src/nepi/execution/resource.py index 0eebc6f6..8cb36730 100644 --- a/src/nepi/execution/resource.py +++ b/src/nepi/execution/resource.py @@ -785,7 +785,7 @@ def find_types(): import logging err = traceback.format_exc() logger = logging.getLogger("Resource.find_types()") - logger.error("Error while lading Resource Managers %s" % err) + logger.error("Error while loading Resource Managers %s" % err) return types diff --git a/src/nepi/resources/linux/application.py b/src/nepi/resources/linux/application.py index 8b975acb..a55fab54 100644 --- a/src/nepi/resources/linux/application.py +++ b/src/nepi/resources/linux/application.py @@ -600,10 +600,10 @@ class LinuxApplication(ResourceManager): # Only try to kill the process if the pid and ppid # were retrieved if self.pid and self.ppid: - (out, err), proc = self.node.kill(self.pid, self.ppid, sudo = - self._sudo_kill) + (out, err), proc = self.node.kill(self.pid, self.ppid, + sudo = self._sudo_kill) - if out or err: + if proc.poll() or err: # check if execution errors occurred msg = " Failed to STOP command '%s' " % self.get("command") self.error(msg, out, err) @@ -649,7 +649,7 @@ class LinuxApplication(ResourceManager): else: # We need to query the status of the command we launched in - # background. In oredr to avoid overwhelming the remote host and + # background. In order to avoid overwhelming the remote host and # the local processor with too many ssh queries, the state is only # requested every 'state_check_delay' seconds. state_check_delay = 0.5 diff --git a/src/nepi/resources/all/scripts/tunchannel.py b/src/nepi/resources/linux/scripts/tunchannel.py similarity index 100% rename from src/nepi/resources/all/scripts/tunchannel.py rename to src/nepi/resources/linux/scripts/tunchannel.py diff --git a/src/nepi/resources/linux/udptunnel.py b/src/nepi/resources/linux/udptunnel.py new file mode 100644 index 00000000..d240416b --- /dev/null +++ b/src/nepi/resources/linux/udptunnel.py @@ -0,0 +1,304 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.attribute import Attribute, Flags, Types +from nepi.execution.resource import ResourceManager, clsinit_copy, ResourceState, \ + reschedule_delay +from nepi.resources.linux.application import LinuxApplication +from nepi.util.timefuncs import tnow, tdiffsec + +import os +import socket +import time + +@clsinit_copy +class UdpTunnel(LinuxApplication): + _rtype = "UdpTunnel" + + def __init__(self, ec, guid): + super(UdpTunnel, self).__init__(ec, guid) + self._home = "udp-tunnel-%s" % self.guid + self._pid1 = None + self._ppid1 = None + self._pid2 = None + self._ppid2 = None + + def log_message(self, msg): + return " guid %d - tunnel %s - %s - %s " % (self.guid, + self.endpoint1.node.get("hostname"), + self.endpoint2.node.get("hostname"), + msg) + + def get_endpoints(self): + """ Returns the list of RM that are endpoints to the tunnel + """ + connected = [] + for guid in self.connections: + rm = self.ec.get_resource(guid) + if hasattr(rm, "udp_connect_command"): + connected.append(rm) + return connected + + @property + def endpoint1(self): + endpoints = self.get_endpoints() + if endpoints: return endpoints[0] + return None + + @property + def endpoint2(self): + endpoints = self.get_endpoints() + if endpoints and len(endpoints) > 1: return endpoints[1] + return None + + def app_home(self, endpoint): + return os.path.join(endpoint.node.exp_home, self._home) + + def run_home(self, endpoint): + return os.path.join(self.app_home(endpoint), self.ec.run_id) + + def udp_connect(self, endpoint, remote_ip): + # Get udp connect command + local_port_file = os.path.join(self.run_home(endpoint), + "local_port") + remote_port_file = os.path.join(self.run_home(endpoint), + "remote_port") + ret_file = os.path.join(self.run_home(endpoint), + "ret_file") + udp_connect_command = endpoint.udp_connect_command( + remote_ip, local_port_file, remote_port_file, + ret_file) + + # upload command to connect.sh script + shfile = os.path.join(self.app_home(endpoint), "udp-connect.sh") + endpoint.node.upload(udp_connect_command, + shfile, + text = True, + overwrite = False) + + # invoke connect script + cmd = "bash %s" % shfile + (out, err), proc = endpoint.node.run(cmd, self.run_home(endpoint)) + + # check if execution errors occurred + msg = " Failed to connect endpoints " + + if proc.poll(): + self.fail() + self.error(msg, out, err) + raise RuntimeError, msg + + # Wait for pid file to be generated + pid, ppid = endpoint.node.wait_pid(self.run_home(endpoint)) + + # If the process is not running, check for error information + # on the remote machine + if not pid or not ppid: + (out, err), proc = endpoint.node.check_errors(self.run_home(endpoint)) + # Out is what was written in the stderr file + if err: + self.fail() + msg = " Failed to start command '%s' " % command + self.error(msg, out, err) + raise RuntimeError, msg + + # wait until port is written to file + port = self.wait_local_port(endpoint) + return (port, pid, ppid) + + def provision(self): + # create run dir for tunnel on each node + self.endpoint1.node.mkdir(self.run_home(self.endpoint1)) + self.endpoint2.node.mkdir(self.run_home(self.endpoint2)) + + # Invoke connect script in endpoint 1 + remote_ip1 = socket.gethostbyname(self.endpoint2.node.get("hostname")) + (port1, self._pid1, self._ppid1) = self.udp_connect(self.endpoint1, + remote_ip1) + + # Invoke connect script in endpoint 2 + remote_ip2 = socket.gethostbyname(self.endpoint1.node.get("hostname")) + (port2, self._pid2, self._ppid2) = self.udp_connect(self.endpoint2, + remote_ip2) + + # upload file with port 2 to endpoint 1 + self.upload_remote_port(self.endpoint1, port2) + + # upload file with port 1 to endpoint 2 + self.upload_remote_port(self.endpoint2, port1) + + # check if connection was successful on both sides + self.wait_result(self.endpoint1) + self.wait_result(self.endpoint2) + + self.info("Provisioning finished") + + self.debug("----- READY ---- ") + self._provision_time = tnow() + self._state = ResourceState.PROVISIONED + + def deploy(self): + if (not self.endpoint1 or self.endpoint1.state < ResourceState.READY) or \ + (not self.endpoint2 or self.endpoint2.state < ResourceState.READY): + self.ec.schedule(reschedule_delay, self.deploy) + else: + try: + self.discover() + self.provision() + except: + self.fail() + raise + + self.debug("----- READY ---- ") + self._ready_time = tnow() + self._state = ResourceState.READY + + def start(self): + if self._state == ResourceState.READY: + command = self.get("command") + self.info("Starting command '%s'" % command) + + self._start_time = tnow() + self._state = ResourceState.STARTED + else: + msg = " Failed to execute command '%s'" % command + self.error(msg, out, err) + self._state = ResourceState.FAILED + raise RuntimeError, msg + + def stop(self): + command = self.get('command') or '' + state = self.state + + if state == ResourceState.STARTED: + self.info("Stopping command '%s'" % command) + + command = "bash %s" % os.path.join(self.app_home, "stop.sh") + (out, err), proc = self.execute_command(command, + blocking = True) + + self._stop_time = tnow() + self._state = ResourceState.STOPPED + + def stop(self): + """ Stops application execution + """ + if self.state == ResourceState.STARTED: + stopped = True + self.info("Stopping tunnel") + + # Only try to kill the process if the pid and ppid + # were retrieved + if self._pid1 and self._ppid1 and self._pid2 and self._ppid2: + (out1, err1), proc1 = self.endpoint1.node.kill(self._pid1, + self._ppid1, sudo = True) + (out2, err2), proc2 = self.endpoint2.node.kill(self._pid2, + self._ppid2, sudo = True) + + if err1 or err2 or pro1.poll() or proc2.poll(): + # check if execution errors occurred + msg = " Failed to STOP tunnel" + self.error(msg, out, err) + self.fail() + stopped = False + + if stopped: + self._stop_time = tnow() + self._state = ResourceState.STOPPED + + @property + def state(self): + """ Returns the state of the application + """ + if self._state == ResourceState.STARTED: + # In order to avoid overwhelming the remote host and + # the local processor with too many ssh queries, the state is only + # requested every 'state_check_delay' seconds. + state_check_delay = 0.5 + if tdiffsec(tnow(), self._last_state_check) > state_check_delay: + # check if execution errors occurred + (out1, err1), proc1 = self.endpoint1.node.check_errors( + self.run_home(self.endpoint1)) + + (out2, err2), proc2 = self.endpoint2.node.check_errors( + self.run_home(self.endpoint2)) + + if err1 or err2: + msg = " Failed to connect endpoints " + self.error(msg, err1, err2) + self.fail() + + elif self._pid1 and self._ppid1 and self._pid2 and self._ppid2: + # No execution errors occurred. Make sure the background + # process with the recorded pid is still running. + status1 = self.node.status(self._pid1, self._ppid1) + status2 = self.node.status(self._pid2, self._ppid2) + + if status1 == ProcStatus.FINISHED and \ + satus2 == ProcStatus.FINISHED: + self._state = ResourceState.FINISHED + + self._last_state_check = tnow() + + return self._state + + def wait_local_port(self, endpoint): + """ Waits until the local_port file for the endpoint is generated, + and returns the port number """ + return self.wait_file(endpoint, "local_port") + + def wait_result(self, endpoint): + """ Waits until the return code file for the endpoint is generated """ + return self.wait_file(endpoint, "ret_file") + + def wait_file(self, endpoint, filename): + """ Waits until file on endpoint is generated """ + result = None + delay = 1.0 + + for i in xrange(4): + (out, err), proc = endpoint.node.check_output( + self.run_home(endpoint), filename) + + if out: + result = out.strip() + break + else: + time.sleep(delay) + delay = delay * 1.5 + else: + msg = "Couldn't retrieve %s" % filename + self.error(msg, out, err) + self.fail() + raise RuntimeError, msg + + return result + + def upload_remote_port(self, endpoint, port): + # upload remote port number to file + port = "%s\n" % port + endpoint.node.upload(port, + os.path.join(self.run_home(endpoint), "remote_port"), + text = True, + overwrite = False) + + def valid_connection(self, guid): + # TODO: Validate! + return True + diff --git a/src/nepi/resources/planetlab/scripts/pl-vif-tunconnect.py b/src/nepi/resources/planetlab/scripts/pl-vif-tunconnect.py deleted file mode 100644 index 98fa9232..00000000 --- a/src/nepi/resources/planetlab/scripts/pl-vif-tunconnect.py +++ /dev/null @@ -1,70 +0,0 @@ -# -# NEPI, a framework to manage network experiments -# Copyright (C) 2013 INRIA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Author: Alina Quereilhac - -import base64 -import errno -import passfd -import vsys -import socket -from optparse import OptionParser, SUPPRESS_HELP - -PASSFD_MSG = "PASSFD" - -def get_options(): - usage = ("usage: %prog -S ") - - parser = OptionParser(usage = usage) - - parser.add_option("-S", "--socket-name", dest="socket_name", - help = "Name for the unix socket used to interact with this process", - default = "tap.sock", type="str") - - (options, args) = parser.parse_args() - - return (options.socket_name) - -if __name__ == '__main__': - - (socket_name) = get_options() - - # Socket to recive the file descriptor - fdsock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) - fdsock.bind("") - address = fdsock.getsockname() - - # vif-create-socket to send the PASSFD message - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(socket_name) - emsg = base64.b64encode(PASSFD_MSG) - eargs = base64.b64encode(address) - encoded = "%s|%s\n" % (emsg, eargs) - sock.send(encoded) - - # Receive fd - (fd, msg) = passfd.recvfd(fdsock) - - # Receive reply - reply = sock.recv(1024) - reply = base64.b64decode(reply) - - print reply, fd - - - - diff --git a/src/nepi/resources/planetlab/scripts/pl-vif-udp-connect.py b/src/nepi/resources/planetlab/scripts/pl-vif-udp-connect.py new file mode 100644 index 00000000..9b0b514e --- /dev/null +++ b/src/nepi/resources/planetlab/scripts/pl-vif-udp-connect.py @@ -0,0 +1,156 @@ +import base64 +import errno +import os +import passfd +import signal +import socket +import time +import tunchannel +import vsys + +from optparse import OptionParser, SUPPRESS_HELP + +PASSFD_MSG = "PASSFD" + +# Trak SIGTERM, and set global termination flag instead of dying +TERMINATE = [] +def _finalize(sig,frame): + global TERMINATE + TERMINATE.append(None) +signal.signal(signal.SIGTERM, _finalize) + +# SIGUSR1 suspends forwading, SIGUSR2 resumes forwarding +SUSPEND = [] +def _suspend(sig,frame): + global SUSPEND + if not SUSPEND: + SUSPEND.append(None) +signal.signal(signal.SIGUSR1, _suspend) + +def _resume(sig,frame): + global SUSPEND + if SUSPEND: + SUSPEND.remove(None) +signal.signal(signal.SIGUSR2, _resume) + +def get_fd(socket_name): + # Socket to recive the file descriptor + fdsock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + fdsock.bind("") + address = fdsock.getsockname() + + # Socket to connect to the pl-vif-create process + # and send the PASSFD message + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(socket_name) + emsg = base64.b64encode(PASSFD_MSG) + eargs = base64.b64encode(address) + encoded = "%s|%s\n" % (emsg, eargs) + sock.send(encoded) + + # Receive fd + (fd, msg) = passfd.recvfd(fdsock) + + # Receive reply + reply = sock.recv(1024) + reply = base64.b64decode(reply) + + sock.close() + fdsock.close() + return fd + +def get_options(): + usage = ("usage: %prog -t -S " + "-l -r -H " + "-R ") + + parser = OptionParser(usage = usage) + + parser.add_option("-t", "--vif-type", dest="vif_type", + help = "Virtual interface type. Either IFF_TAP or IFF_TUN. " + "Defaults to IFF_TAP. ", type="str") + parser.add_option("-S", "--fd-socket-name", dest="fd_socket_name", + help = "Name for the unix socket to request the TAP file descriptor", + default = "tap.sock", type="str") + parser.add_option("-l", "--local-port-file", dest="local_port_file", + help = "File where to store the local binded UDP port number ", + default = "local_port_file", type="str") + parser.add_option("-r", "--remote-port-file", dest="remote_port_file", + help = "File where to read the remote UDP port number to connect to", + default = "remote_port_file", type="str") + parser.add_option("-H", "--remote-host", dest="remote_host", + help = "Remote host IP", + default = "remote_host", type="str") + parser.add_option("-R", "--ret-file", dest="ret_file", + help = "File where to store return code (success of connection) ", + default = "ret_file", type="str") + + (options, args) = parser.parse_args() + + vif_type = vsys.IFF_TAP + if options.vif_type and options.vif_type == "IFF_TUN": + vif_type = vsys.IFF_TUN + + return ( vif_type, options.fd_socket_name, options.local_port_file, + options.remote_port_file, options.remote_host, + options.ret_file ) + +if __name__ == '__main__': + + ( vif_type, socket_name, local_port_file, remote_port_file, + remote_host, ret_file ) = get_options() + + # Get the file descriptor of the TAP device from the process + # that created it + fd = get_fd(socket_name) + tun = os.fdopen(int(fd), 'r+b', 0) + + # Create a local socket to stablish the tunnel connection + hostaddr = socket.gethostbyname(socket.gethostname()) + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) + sock.bind((hostaddr, 0)) + (local_host, local_port) = sock.getsockname() + + # Save local port information to file + f = open(local_port_file, 'w') + f.write("%d\n" % local_port) + f.close() + + # Wait until remote port information is available + while not os.path.exists(remote_port_file): + time.sleep(2) + + # Read remote port from file + f = open(remote_port_file, 'r') + remote_port = f.read() + f.close() + + remote_port = remote_port.strip() + remote_port = int(remote_port) + + # Connect local socket to remote port + sock.connect((remote_host, remote_port)) + remote = os.fdopen(sock.fileno(), 'r+b', 0) + + # TODO: Test connectivity! + + # Create a ret_file to indicate success + f = open(ret_file, 'w') + f.write("0") + f.close() + + # Establish tunnel + # TODO: ADD parameters tunqueue, tunkqueue, cipher_key + tunchannel.tun_fwd(tun, remote, + # Planetlab TAP devices add PI headers + with_pi = True, + ether_mode = (vif_type == vsys.IFF_TAP), + cipher_key = None, + udp = True, + TERMINATE = TERMINATE, + SUSPEND = SUSPEND, + tunqueue = 1000, + tunkqueue = 500, + ) + + diff --git a/src/nepi/resources/planetlab/tap.py b/src/nepi/resources/planetlab/tap.py index 54d18637..b9691740 100644 --- a/src/nepi/resources/planetlab/tap.py +++ b/src/nepi/resources/planetlab/tap.py @@ -28,6 +28,8 @@ import os import time # TODO: - routes!!! +# - CREATE GRE - PlanetlabGRE - it only needs to set the gre and remote +# properties when configuring the vif_up PYTHON_VSYS_VERSION = "1.0" @@ -54,13 +56,15 @@ class PlanetlabTap(LinuxApplication): "Name of the network interface (e.g. eth0, wlan0, etc)", flags = Flags.ReadOnly) - up = Attribute("up", "Link up", type = Types.Bool) + up = Attribute("up", "Link up", + type = Types.Bool) - snat = Attribute("snat", "Set SNAT=1", type = Types.Bool, - flags = Flags.ReadOnly) + snat = Attribute("snat", "Set SNAT=1", + type = Types.Bool, + flags = Flags.ExecReadOnly) pointopoint = Attribute("pointopoint", "Peer IP address", - flags = Flags.ReadOnly) + flags = Flags.ExecReadOnly) tear_down = Attribute("tearDown", "Bash script to be executed before " + \ "releasing the resource", @@ -92,7 +96,7 @@ class PlanetlabTap(LinuxApplication): "pl-vif-create.py") self.node.upload(pl_vif_create, - os.path.join(self.app_home, "pl-vif-create.py"), + os.path.join(self.node.src_dir, "pl-vif-create.py"), overwrite = False) # upload vif-stop python script @@ -100,23 +104,23 @@ class PlanetlabTap(LinuxApplication): "pl-vif-stop.py") self.node.upload(pl_vif_stop, - os.path.join(self.app_home, "pl-vif-stop.py"), + os.path.join(self.node.src_dir, "pl-vif-stop.py"), overwrite = False) # upload vif-connect python script pl_vif_connect = os.path.join(os.path.dirname(__file__), "scripts", - "pl-vif-tunconnect.py") + "pl-vif-udp-connect.py") self.node.upload(pl_vif_connect, - os.path.join(self.app_home, "pl-vif-connect.py"), + os.path.join(self.node.src_dir, "pl-vif-udp-connect.py"), overwrite = False) # upload tun-connect python script - tunchannel = os.path.join(os.path.dirname(__file__), "..", "all", "scripts", - "tunchannel.py") + tunchannel = os.path.join(os.path.dirname(__file__), "..", "linux", + "scripts", "tunchannel.py") self.node.upload(tunchannel, - os.path.join(self.app_home, "tunchannel.py"), + os.path.join(self.node.src_dir, "tunchannel.py"), overwrite = False) # upload stop.sh script @@ -138,7 +142,7 @@ class PlanetlabTap(LinuxApplication): self._run_in_background() # Retrive if_name - if_name = self.wait_if_name() + if_name = self._wait_if_name() self.set("deviceName", if_name) def deploy(self): @@ -210,9 +214,9 @@ class PlanetlabTap(LinuxApplication): return self._state - def wait_if_name(self): + def _wait_if_name(self): """ Waits until the if_name file for the command is generated, - and returns the if_name for the devide """ + and returns the if_name for the device """ if_name = None delay = 1.0 @@ -233,9 +237,25 @@ class PlanetlabTap(LinuxApplication): return if_name + def udp_connect_command(self, remote_ip, local_port_file, + remote_port_file, ret_file): + command = ["sudo -S "] + command.append("PYTHONPATH=$PYTHONPATH:${SRC}") + command.append("python ${SRC}/pl-vif-udp-connect.py") + command.append("-t %s" % self.vif_type) + command.append("-S %s " % self.sock_name) + command.append("-l %s " % local_port_file) + command.append("-r %s " % remote_port_file) + command.append("-H %s " % remote_ip) + command.append("-R %s " % ret_file) + + command = " ".join(command) + command = self.replace_paths(command) + return command + @property def _start_command(self): - command = ["sudo -S python ${APP_HOME}/pl-vif-create.py"] + command = ["sudo -S python ${SRC}/pl-vif-create.py"] command.append("-t %s" % self.vif_type) command.append("-a %s" % self.get("ip4")) @@ -251,7 +271,7 @@ class PlanetlabTap(LinuxApplication): @property def _stop_command(self): - command = ["sudo -S python ${APP_HOME}/pl-vif-stop.py"] + command = ["sudo -S python ${SRC}/pl-vif-stop.py"] command.append("-S %s " % self.sock_name) return " ".join(command) @@ -274,6 +294,7 @@ class PlanetlabTap(LinuxApplication): @property def _install(self): + # Install python-vsys and python-passfd install_vsys = ( " ( " " python -c 'import vsys, os; vsys.__version__ == \"%(version)s\" or os._exit(1)' " " ) " diff --git a/test/lib/test_utils.py b/test/lib/test_utils.py index d26c9a8c..e6f0d2e1 100644 --- a/test/lib/test_utils.py +++ b/test/lib/test_utils.py @@ -51,6 +51,25 @@ def skipIfNotAlive(func): return wrapped +def skipIfAnyNotAlive(func): + name = func.__name__ + def wrapped(*args, **kwargs): + argss = list(args) + argss.pop(0) + username = argss.pop(0) + + for hostname in argss: + node, ec = create_node(hostname, username) + + if not node.is_alive(): + print "*** WARNING: Skipping test %s: Node %s is not alive\n" % ( + name, node.get("hostname")) + return + + return func(*args, **kwargs) + + return wrapped + def skipInteractive(func): name = func.__name__ def wrapped(*args, **kwargs): diff --git a/test/resources/planetlab/tap.py b/test/resources/planetlab/tap.py index 67fa147b..d2c86cb0 100755 --- a/test/resources/planetlab/tap.py +++ b/test/resources/planetlab/tap.py @@ -20,7 +20,7 @@ from nepi.execution.ec import ExperimentController -from test_utils import skipIfNotAlive, skipInteractive +from test_utils import skipIfNotAlive import os import time diff --git a/test/resources/planetlab/tun.py b/test/resources/planetlab/tun.py index 8c79ed1d..07214c5a 100755 --- a/test/resources/planetlab/tun.py +++ b/test/resources/planetlab/tun.py @@ -20,7 +20,7 @@ from nepi.execution.ec import ExperimentController -from test_utils import skipIfNotAlive, skipInteractive +from test_utils import skipIfNotAlive import os import time diff --git a/test/resources/planetlab/udptunnel.py b/test/resources/planetlab/udptunnel.py new file mode 100755 index 00000000..41351485 --- /dev/null +++ b/test/resources/planetlab/udptunnel.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2013 INRIA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Alina Quereilhac + +from nepi.execution.ec import ExperimentController + +from test_utils import skipIfAnyNotAlive + +import os +import time +import unittest + +class UdpTunnelTestCase(unittest.TestCase): + def setUp(self): + self.host1 = "nepi2.pl.sophia.inria.fr" + self.host2 = "nepi5.pl.sophia.inria.fr" + self.user = "inria_nepi" + + @skipIfAnyNotAlive + def t_tap_udp_tunnel(self, user, host1, host2): + + ec = ExperimentController(exp_id = "test-tap-udp-tunnel") + + node1 = ec.register_resource("PlanetlabNode") + ec.set(node1, "hostname", host1) + ec.set(node1, "username", user) + ec.set(node1, "cleanHome", True) + ec.set(node1, "cleanProcesses", True) + + tap1 = ec.register_resource("PlanetlabTap") + ec.set(tap1, "ip4", "192.168.1.1") + ec.set(tap1, "pointopoint", "192.168.1.2") + ec.set(tap1, "prefix4", 24) + ec.register_connection(tap1, node1) + + node2 = ec.register_resource("PlanetlabNode") + ec.set(node2, "hostname", host2) + ec.set(node2, "username", user) + ec.set(node2, "cleanHome", True) + ec.set(node2, "cleanProcesses", True) + + tap2 = ec.register_resource("PlanetlabTap") + ec.set(tap2, "ip4", "192.168.1.2") + ec.set(tap2, "pointopoint", "192.168.1.1") + ec.set(tap2, "prefix4", 24) + ec.register_connection(tap2, node2) + + udptun = ec.register_resource("UdpTunnel") + ec.register_connection(tap1, udptun) + ec.register_connection(tap2, udptun) + + app = ec.register_resource("LinuxApplication") + cmd = "ping -c3 192.168.1.2" + ec.set(app, "command", cmd) + ec.register_connection(app, node1) + + ec.deploy() + + ec.wait_finished(app) + + ping = ec.trace(app, 'stdout') + expected = """3 packets transmitted, 3 received, 0% packet loss""" + self.assertTrue(ping.find(expected) > -1) + + if_name = ec.get(tap1, "deviceName") + self.assertTrue(if_name.startswith("tap")) + + if_name = ec.get(tap2, "deviceName") + self.assertTrue(if_name.startswith("tap")) + + + ec.shutdown() + + def test_tap_udp_tunnel(self): + self.t_tap_udp_tunnel(self.user, self.host1, self.host2) + +if __name__ == '__main__': + unittest.main() + -- 2.43.0