Adding working UdpTunnel for Planetlab and Linux
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Tue, 16 Jul 2013 01:59:31 +0000 (18:59 -0700)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Tue, 16 Jul 2013 01:59:31 +0000 (18:59 -0700)
13 files changed:
Makefile
setup.py
src/nepi/execution/resource.py
src/nepi/resources/linux/application.py
src/nepi/resources/linux/scripts/tunchannel.py [moved from src/nepi/resources/all/scripts/tunchannel.py with 100% similarity]
src/nepi/resources/linux/udptunnel.py [new file with mode: 0644]
src/nepi/resources/planetlab/scripts/pl-vif-tunconnect.py [deleted file]
src/nepi/resources/planetlab/scripts/pl-vif-udp-connect.py [new file with mode: 0644]
src/nepi/resources/planetlab/tap.py
test/lib/test_utils.py
test/resources/planetlab/tap.py
test/resources/planetlab/tun.py
test/resources/planetlab/udptunnel.py [new file with mode: 0755]

index c2fb951..24e5b15 100644 (file)
--- 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)"
index 407e842..0c68d5b 100755 (executable)
--- 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" ]
             }
     )
index 0eebc6f..8cb3673 100644 (file)
@@ -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
 
index 8b975ac..a55fab5 100644 (file)
@@ -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/linux/udptunnel.py b/src/nepi/resources/linux/udptunnel.py
new file mode 100644 (file)
index 0000000..d240416
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+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 (file)
index 98fa923..0000000
+++ /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 <http://www.gnu.org/licenses/>.
-#
-# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
-
-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 <socket-name>")
-    
-    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 (file)
index 0000000..9b0b514
--- /dev/null
@@ -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 <vif-type> -S <fd-socket-name> "
+        "-l <local-port-file> -r <remote-port-file> -H <remote-host> "
+        "-R <ret-file> ")
+    
+    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,
+    ) 
+
index 54d1863..b969174 100644 (file)
@@ -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)' "
                     " ) "
index d26c9a8..e6f0d2e 100644 (file)
@@ -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):
index 67fa147..d2c86cb 100755 (executable)
@@ -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
index 8c79ed1..07214c5 100755 (executable)
@@ -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 (executable)
index 0000000..4135148
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+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()
+