Added PlanetlabTAP & PlanetlabTUN
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Tue, 9 Jul 2013 06:15:24 +0000 (23:15 -0700)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Tue, 9 Jul 2013 06:15:24 +0000 (23:15 -0700)
19 files changed:
Makefile
src/nepi/execution/resource.py
src/nepi/resources/linux/application.py
src/nepi/resources/linux/ccn/ccnapplication.py
src/nepi/resources/linux/ccn/ccncontent.py
src/nepi/resources/linux/ccn/ccnr.py
src/nepi/resources/linux/ccn/fibentry.py
src/nepi/resources/linux/interface.py
src/nepi/resources/linux/node.py
src/nepi/resources/omf/application.py
src/nepi/resources/omf/interface.py
src/nepi/resources/planetlab/node.py
src/nepi/resources/planetlab/tap.py [new file with mode: 0644]
src/nepi/resources/planetlab/tun.py [new file with mode: 0644]
test/execution/resource.py
test/lib/test_utils.py [moved from test/resources/linux/test_utils.py with 100% similarity]
test/resources/planetlab/plcapi.py
test/resources/planetlab/tap.py [new file with mode: 0755]
test/resources/planetlab/tun.py [new file with mode: 0644]

index 4909c6c..1a3420c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
 SRCDIR      = $(CURDIR)/src
 TESTDIR     = $(CURDIR)/test
+TESTLIB  = $(TESTDIR)/lib
 BUILDDIR    = $(CURDIR)/build
 DISTDIR     = $(CURDIR)/dist
 
@@ -16,7 +17,7 @@ else
 BUILDDIR := $(BUILDDIR)/lib
 endif
 
-PYPATH = $(BUILDDIR):$(PYTHONPATH)
+PYPATH = $(BUILDDIR):$(TESTLIB):$(PYTHONPATH)
 COVERAGE = $(or $(shell which coverage), $(shell which python-coverage), \
           coverage)
 
@@ -29,10 +30,14 @@ install: all
 test: all
        retval=0; \
               for i in `find "$(TESTDIR)" -iname '*.py' -perm -u+x -type f`; do \
-              echo $$i; \
+              @echo $$i; \
               PYTHONPATH="$(PYPATH)" $$i -v || retval=$$?; \
               done; exit $$retval
 
+test-one: all
+       @echo $(file) $(case)
+       PYTHONPATH="$(PYPATH)" python $(file) $(case)
+
 coverage: all
        rm -f .coverage
        for i in `find "$(TESTDIR)" -perm -u+x -type f`; do \
index 3882523..80e4a3e 100644 (file)
@@ -467,7 +467,7 @@ class ResourceManager(Logger):
                     newgrp.difference_update(intsec)
                     conditions[idx] = (newgrp, state, time)
                  
-    def get_connected(self, rtype = None):
+    def get_connected(self, rclass = None):
         """ Returns the list of RM with the type 'rtype'
 
         :param rtype: Type of the RM we look for
@@ -477,7 +477,7 @@ class ResourceManager(Logger):
         connected = []
         for guid in self.connections:
             rm = self.ec.get_resource(guid)
-            if not rtype or rm.rtype() == rtype:
+            if not rclass or isinstance(rm, rclass):
                 connected.append(rm)
         return connected
 
index 453010e..9111cc1 100644 (file)
@@ -171,8 +171,13 @@ class LinuxApplication(ResourceManager):
         self._pid = None
         self._ppid = None
         self._home = "app-%s" % self.guid
+        # whether the command should run in foreground attached
+        # to a terminal
         self._in_foreground = False
 
+        # whether to use sudo to kill the application process
+        self._sudo_kill = False
+
         # keep a reference to the running process handler when 
         # the command is not executed as remote daemon in background
         self._proc = None
@@ -186,7 +191,7 @@ class LinuxApplication(ResourceManager):
 
     @property
     def node(self):
-        node = self.get_connected(LinuxNode.rtype())
+        node = self.get_connected(LinuxNode)
         if node: return node[0]
         return None
 
@@ -238,7 +243,7 @@ class LinuxApplication(ResourceManager):
         if attr == TraceAttr.ALL:
             (out, err), proc = self.node.check_output(self.run_home, name)
             
-            if err and proc.poll():
+            if proc.poll():
                 msg = " Couldn't read trace %s " % name
                 self.error(msg, out, err)
                 return None
@@ -252,7 +257,7 @@ class LinuxApplication(ResourceManager):
 
         (out, err), proc = self.node.execute(cmd)
 
-        if err and proc.poll():
+        if proc.poll():
             msg = " Couldn't find trace %s " % name
             self.error(msg, out, err)
             return None
@@ -488,13 +493,13 @@ class LinuxApplication(ResourceManager):
         else:
 
             if self.in_foreground:
-                self._start_in_foreground()
+                self._run_in_foreground()
             else:
-                self._start_in_background()
+                self._run_in_background()
 
             super(LinuxApplication, self).start()
 
-    def _start_in_foreground(self):
+    def _run_in_foreground(self):
         command = self.get("command")
         sudo = self.get("sudo") or False
         x11 = self.get("forwardX11")
@@ -506,17 +511,12 @@ class LinuxApplication(ResourceManager):
         # Command will be launched in foreground and attached to the
         # terminal using the node 'execute' in non blocking mode.
 
-        # Export environment
-        env = self.get("env")
-        environ = self.node.format_environment(env, inline = True)
-        command = environ + command
-        command = self.replace_paths(command)
-
         # We save the reference to the process in self._proc 
         # to be able to kill the process from the stop method.
         # We also set blocking = False, since we don't want the
         # thread to block until the execution finishes.
-        (out, err), self._proc = self.node.execute(command,
+        (out, err), self._proc = self.execute_command(self, command, 
+                env = env,
                 sudo = sudo,
                 stdin = stdin,
                 forward_x11 = x11,
@@ -527,7 +527,7 @@ class LinuxApplication(ResourceManager):
             self.error(msg, out, err)
             raise RuntimeError, msg
 
-    def _start_in_background(self):
+    def _run_in_background(self):
         command = self.get("command")
         env = self.get("env")
         sudo = self.get("sudo") or False
@@ -581,6 +581,7 @@ class LinuxApplication(ResourceManager):
         command = self.get('command') or ''
 
         if self.state == ResourceState.STARTED:
+        
             stopped = True
 
             self.info("Stopping command '%s'" % command)
@@ -596,7 +597,8 @@ 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)
+                    (out, err), proc = self.node.kill(self.pid, self.ppid, sudo = 
+                            self._sudo_kill)
 
                     if out or err:
                         # check if execution errors occurred
@@ -669,6 +671,25 @@ class LinuxApplication(ResourceManager):
 
         return self._state
 
+    def execute_command(self, command, 
+            env = None,
+            sudo = False,
+            stdin = None,
+            forward_x11 = False,
+            blocking = False):
+
+        environ = ""
+        if env:
+            environ = self.node.format_environment(env, inline = True)
+        command = environ + command
+        command = self.replace_paths(command)
+
+        return self.node.execute(command,
+                sudo = sudo,
+                stdin = stdin,
+                forward_x11 = forward_x11,
+                blocking = blocking)
+
     def replace_paths(self, command):
         """
         Replace all special path tags with shell-escaped actual paths.
index 740d366..5e4a832 100644 (file)
@@ -35,7 +35,7 @@ class LinuxCCNApplication(LinuxApplication):
 
     @property
     def ccnd(self):
-        ccnd = self.get_connected(LinuxCCND.rtype())
+        ccnd = self.get_connected(LinuxCCND)
         if ccnd: return ccnd[0]
         return None
 
@@ -71,13 +71,6 @@ class LinuxCCNApplication(LinuxApplication):
     def _environment(self):
         return self.ccnd.path
        
-    def execute_command(self, command, env):
-        environ = self.node.format_environment(env, inline = True)
-        command = environ + command
-        command = self.replace_paths(command)
-
-        return self.node.execute(command)
-
     def valid_connection(self, guid):
         # TODO: Validate!
         return True
index 5222d03..e72d6a9 100644 (file)
@@ -49,7 +49,7 @@ class LinuxCCNContent(LinuxApplication):
         
     @property
     def ccnr(self):
-        ccnr = self.get_connected(LinuxCCNR.rtype())
+        ccnr = self.get_connected(LinuxCCNR)
         if ccnr: return ccnr[0]
         return None
 
@@ -142,13 +142,6 @@ class LinuxCCNContent(LinuxApplication):
     def _environment(self):
         return self.ccnd.path
        
-    def execute_command(self, command, env):
-        environ = self.node.format_environment(env, inline = True)
-        command = environ + command
-        command = self.replace_paths(command)
-
-        return self.node.execute(command)
-
     def valid_connection(self, guid):
         # TODO: Validate!
         return True
index 7ed260c..f0ca21b 100644 (file)
@@ -185,7 +185,7 @@ class LinuxCCNR(LinuxApplication):
 
     @property
     def ccnd(self):
-        ccnd = self.get_connected(LinuxCCND.rtype())
+        ccnd = self.get_connected(LinuxCCND)
         if ccnd: return ccnd[0]
         return None
 
index cf2c8fe..46bfae1 100644 (file)
@@ -70,7 +70,7 @@ class LinuxFIBEntry(LinuxApplication):
 
     @property
     def ccnd(self):
-        ccnd = self.get_connected(LinuxCCND.rtype())
+        ccnd = self.get_connected(LinuxCCND)
         if ccnd: return ccnd[0]
         return None
 
@@ -194,13 +194,6 @@ class LinuxFIBEntry(LinuxApplication):
     def _environment(self):
         return self.ccnd.path
        
-    def execute_command(self, command, env):
-        environ = self.node.format_environment(env, inline = True)
-        command = environ + command
-        command = self.replace_paths(command)
-
-        return self.node.execute(command)
-
     def valid_connection(self, guid):
         # TODO: Validate!
         return True
index f5f7025..f0497e8 100644 (file)
@@ -90,13 +90,13 @@ class LinuxInterface(ResourceManager):
 
     @property
     def node(self):
-        node = self.get_connected(LinuxNode.rtype())
+        node = self.get_connected(LinuxNode)
         if node: return node[0]
         return None
 
     @property
     def channel(self):
-        chan = self.get_connected(LinuxChannel.rtype())
+        chan = self.get_connected(LinuxChannel)
         if chan: return chan[0]
         return None
 
index 49f5342..0410b16 100644 (file)
@@ -336,7 +336,7 @@ class LinuxNode(ResourceManager):
         # Node needs to wait until all associated interfaces are 
         # ready before it can finalize deployment
         from nepi.resources.linux.interface import LinuxInterface
-        ifaces = self.get_connected(LinuxInterface.rtype())
+        ifaces = self.get_connected(LinuxInterface)
         for iface in ifaces:
             if iface.state < ResourceState.READY:
                 self.ec.schedule(reschedule_delay, self.deploy)
@@ -819,8 +819,8 @@ class LinuxNode(ResourceManager):
         return self.upload(command, shfile, text = True, overwrite = overwrite)
 
     def format_environment(self, env, inline = False):
-        """Format environmental variables for command to be executed either
-        as an inline command
+        """ Formats the environment variables for a command to be executed
+        either as an inline command
         (i.e. export PYTHONPATH=src/..; export LALAL= ..;python script.py) or 
         as a bash script (i.e. export PYTHONPATH=src/.. \n export LALA=.. \n)
         """
@@ -835,8 +835,7 @@ class LinuxNode(ResourceManager):
     def check_errors(self, home, 
             ecodefile = "exitcode", 
             stderr = "stderr"):
-        """
-        Checks whether errors occurred while running a command.
+        """ Checks whether errors occurred while running a command.
         It first checks the exit code for the command, and only if the
         exit code is an error one it returns the error output.
 
index 170410b..e57b374 100644 (file)
@@ -21,6 +21,7 @@
 from nepi.execution.resource import ResourceManager, clsinit, ResourceState, \
         reschedule_delay
 from nepi.execution.attribute import Attribute, Flags 
+from nepi.resources.omf.node import OMFNode
 from nepi.resources.omf.omf_api import OMFAPIFactory
 
 
@@ -134,7 +135,7 @@ class OMFApplication(ResourceManager):
                 self.get('appid') + " : " + self.get('path') + " : " + \
                 self.get('args') + " : " + self.get('env')
             self.info(msg)
-            rm_list = self.get_connected("OMFNode")
+            rm_list = self.get_connected(OMFNode)
             try:
                 for rm_node in rm_list:
                     if rm_node.get('hostname') :
index b15be5e..3151362 100644 (file)
@@ -22,6 +22,7 @@ from nepi.execution.resource import ResourceManager, clsinit, ResourceState, \
         reschedule_delay
 from nepi.execution.attribute import Attribute, Flags 
 
+from nepi.resources.omf.node import OMFNode
 from nepi.resources.omf.omf_api import OMFAPIFactory
 
 
@@ -117,7 +118,7 @@ class OMFWifiInterface(ResourceManager):
             self.debug(" " + self.rtype() + " ( Guid : " + str(self._guid) +") : " + \
                 self.get('mode') + " : " + self.get('type') + " : " + \
                 self.get('essid') + " : " + self.get('ip'))
-            rm_list = self.get_connected("OMFNode"
+            rm_list = self.get_connected(OMFNode
             for rm_node in rm_list:
                 if rm_node.state < ResourceState.READY:
                     self.ec.schedule(reschedule_delay, self.deploy)
index 25358a2..9d1b295 100644 (file)
@@ -30,14 +30,9 @@ class PlanetlabNode(LinuxNode):
 
     @classmethod
     def _register_attributes(cls):
-        cls._remove_attribute("username")
-
         ip = Attribute("ip", "PlanetLab host public IP address",
                 flags = Flags.ReadOnly)
 
-        slicename = Attribute("slice", "PlanetLab slice name",
-                flags = Flags.Credential)
-
         pl_url = Attribute("plcApiUrl", "URL of PlanetLab PLCAPI host (e.g. www.planet-lab.eu or www.planet-lab.org) ",
                 default = "www.planet-lab.eu",
                 flags = Flags.Credential)
@@ -142,7 +137,6 @@ class PlanetlabNode(LinuxNode):
                  flags = Flags.Filter)
 
         cls._register_attribute(ip)
-        cls._register_attribute(slicename)
         cls._register_attribute(pl_url)
         cls._register_attribute(pl_ptn)
         cls._register_attribute(city)
@@ -161,14 +155,14 @@ class PlanetlabNode(LinuxNode):
         cls._register_attribute(timeframe)
 
     def __init__(self, ec, guid):
-        super(PLanetlabNode, self).__init__(ec, guid)
+        super(PlanetlabNode, self).__init__(ec, guid)
 
         self._plapi = None
     
     @property
     def plapi(self):
         if not self._plapi:
-            slicename = self.get("slice")
+            slicename = self.get("username")
             pl_pass = self.get("password")
             pl_url = self.get("plcApiUrl")
             pl_ptn = self.get("plcApiPattern")
@@ -178,83 +172,10 @@ class PlanetlabNode(LinuxNode):
             
         return self._plapi
 
-    @property
-    def os(self):
-        if self._os:
-            return self._os
-
-        if (not self.get("hostname") or not self.get("username")):
-            msg = "Can't resolve OS, insufficient data "
-            self.error(msg)
-            raise RuntimeError, msg
-
-        (out, err), proc = self.execute("cat /etc/issue", with_lock = True)
-
-        if err and proc.poll():
-            msg = "Error detecting OS "
-            self.error(msg, out, err)
-            raise RuntimeError, "%s - %s - %s" %( msg, out, err )
-
-        if out.find("Fedora release 12") == 0:
-            self._os = "f12"
-        elif out.find("Fedora release 14") == 0:
-            self._os = "f14"
-        else:
-            msg = "Unsupported OS"
-            self.error(msg, out)
-            raise RuntimeError, "%s - %s " %( msg, out )
-
-        return self._os
-
-    def provision(self):
-        if not self.is_alive():
-            self._state = ResourceState.FAILED
-            msg = "Deploy failed. Unresponsive node %s" % self.get("hostname")
-            self.error(msg)
-            raise RuntimeError, msg
-
-        if self.get("cleanProcesses"):
-            self.clean_processes()
-
-        if self.get("cleanHome"):
-            self.clean_home()
-       
-        self.mkdir(self.node_home)
-
-        super(PlanetlabNode, self).provision()
-
-    def deploy(self):
-        if self.state == ResourceState.NEW:
-            try:
-               self.discover()
-               if self.state == ResourceState.DISCOVERED:
-                   self.provision()
-            except:
-                self._state = ResourceState.FAILED
-                raise
-
-        if self.state != ResourceState.PROVISIONED:
-           self.ec.schedule(reschedule_delay, self.deploy)
-
-        super(PlanetlabNode, self).deploy()
-
     def valid_connection(self, guid):
         # TODO: Validate!
         return True
 
-    def clean_processes(self, killer = False):
-        self.info("Cleaning up processes")
-    
-        # Hardcore kill
-        cmd = ("sudo -S killall python tcpdump || /bin/true ; " +
-            "sudo -S killall python tcpdump || /bin/true ; " +
-            "sudo -S kill $(ps -N -T -o pid --no-heading | grep -v $PPID | sort) || /bin/true ; " +
-            "sudo -S killall -u root || /bin/true ; " +
-            "sudo -S killall -u root || /bin/true ; ")
-
-        out = err = ""
-        (out, err), proc = self.execute(cmd, retry = 1, with_lock = True) 
-            
     def blacklist(self):
         # TODO!!!!
         self.warn(" Blacklisting malfunctioning node ")
diff --git a/src/nepi/resources/planetlab/tap.py b/src/nepi/resources/planetlab/tap.py
new file mode 100644 (file)
index 0000000..a60961f
--- /dev/null
@@ -0,0 +1,260 @@
+#
+#    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.resources.planetlab.node import PlanetlabNode
+from nepi.util.timefuncs import tnow, tdiffsec
+
+import os
+import time
+
+# TODO: - routes!!!
+#       - Make base clase 'virtual device' and redefine vif_type
+#       - write the name of the device (if_name) to a file and allow the 
+#           RM to read it and set the 'deviceName' attribute
+#       - Instead of doing an infinite loop, open a port for communication allowing
+#           to pass the fd to another process
+
+PYTHON_VSYS_VERSION = "1.0"
+
+@clsinit_copy
+class PlanetlabTap(LinuxApplication):
+    _rtype = "PlanetlabTap"
+
+    @classmethod
+    def _register_attributes(cls):
+        ip4 = Attribute("ip4", "IPv4 Address",
+              flags = Flags.ExecReadOnly)
+
+        mac = Attribute("mac", "MAC Address",
+                flags = Flags.ExecReadOnly)
+
+        prefix4 = Attribute("prefix4", "IPv4 network prefix",
+                flags = Flags.ExecReadOnly)
+
+        mtu = Attribute("mtu", "Maximum transmition unit for device",
+                type = Types.Integer)
+
+        devname = Attribute("deviceName", 
+                "Name of the network interface (e.g. eth0, wlan0, etc)",
+                flags = Flags.ReadOnly)
+
+        up = Attribute("up", "Link up", type = Types.Bool)
+        
+        snat = Attribute("snat", "Set SNAT=1", type = Types.Bool,
+                flags = Flags.ReadOnly)
+        
+        pointopoint = Attribute("pointopoint", "Peer IP address", 
+                flags = Flags.ReadOnly)
+
+        tear_down = Attribute("tearDown", "Bash script to be executed before " + \
+                "releasing the resource",
+                flags = Flags.ExecReadOnly)
+
+        cls._register_attribute(ip4)
+        cls._register_attribute(mac)
+        cls._register_attribute(prefix4)
+        cls._register_attribute(mtu)
+        cls._register_attribute(devname)
+        cls._register_attribute(up)
+        cls._register_attribute(snat)
+        cls._register_attribute(pointopoint)
+        cls._register_attribute(tear_down)
+
+    def __init__(self, ec, guid):
+        super(PlanetlabTap, self).__init__(ec, guid)
+        self._home = "tap-%s" % self.guid
+
+    @property
+    def node(self):
+        node = self.get_connected(PlanetlabNode)
+        if node: return node[0]
+        return None
+
+    def upload_sources(self):
+        depends = "mercurial make gcc"
+        self.set("depends", depends)
+
+        install = ( " ( "
+                    "   python -c 'import vsys, os;  vsys.__version__ == \"%(version)s\" or os._exit(1)' "
+                    " ) "
+                    " ||"
+                    " ( "
+                    "   cd ${SRC} ; "
+                    "   hg clone http://nepi.inria.fr/code/python-vsys ; "
+                    "   cd python-vsys ; "
+                    "   make all ; "
+                    "   sudo -S make install "
+                    " )" ) % ({
+                        "version": PYTHON_VSYS_VERSION
+                        })
+
+        self.set("install", install)
+
+    def upload_start_command(self):
+        # upload tap-creation python script
+        start_script = self.replace_paths(self._start_script)
+        self.node.upload(start_script,
+                os.path.join(self.app_home, "tap_create.py"),
+                text = True, 
+                overwrite = False)
+
+        # upload start.sh
+        start_command = self.replace_paths(self._start_command)
+
+        self.info("Uploading command '%s'" % start_command)
+        
+        self.set("command", start_command)
+        self.node.upload(start_command,
+                os.path.join(self.app_home, "start.sh"),
+                text = True, 
+                overwrite = False)
+
+        # We want to make sure the device is up and running
+        # before the experiment starts.
+        # Run the command as a bash script in background,
+        # in the host ( but wait until the command has
+        # finished to continue )
+        self._run_in_background()
+        
+        # Retrive if_name
+        if_name = self.wait_if_name()
+        self.set("deviceName", if_name) 
+
+    def deploy(self):
+        if not self.node or self.node.state < ResourceState.PROVISIONED:
+            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 = "rm %s" % os.path.join(self.run_home, "if_stop")
+            (out, err), proc = self.execute_command(command)
+
+            self._stop_time = tnow()
+            self._state = ResourceState.STOPPED
+
+    @property
+    def state(self):
+        # First check if the ccnd has failed
+        state_check_delay = 0.5
+        if self._state == ResourceState.STARTED and \
+                tdiffsec(tnow(), self._last_state_check) > state_check_delay:
+
+            if self.get("deviceName"):
+                (out, err), proc = self.node.execute("ifconfig")
+
+                if out.strip().find(self.get("deviceName")) == -1: 
+                    # tap is not running is not running (socket not found)
+                    self._state = ResourceState.FINISHED
+
+            self._last_state_check = tnow()
+
+        return self._state
+
+    def wait_if_name(self):
+        """ Waits until the if_name file for the command is generated, 
+            and returns the if_name for the devide """
+        if_name = None
+        delay = 1.0
+
+        for i in xrange(4):
+            (out, err), proc = self.node.check_output(self.run_home, "if_name")
+
+            if out:
+                if_name = out.strip()
+                break
+            else:
+                time.sleep(delay)
+                delay = delay * 1.5
+        else:
+            msg = "Couldn't retrieve if_name"
+            self.error(msg, out, err)
+            self.fail()
+            raise RuntimeError, msg
+
+        return if_name
+
+    @property
+    def _start_command(self):
+        return "sudo -S python ${APP_HOME}/tap_create.py" 
+
+    @property
+    def _start_script(self):
+        return ( "import vsys, time, os \n"
+                 "(fd, if_name) = vsys.fd_tuntap(vsys.%(devtype)s)\n"
+                 "vsys.vif_up(if_name, '%(ip)s', %(prefix)s%(snat)s%(pointopoint)s)\n"
+                 "f = open('%(if_name_file)s', 'w')\n"
+                 "f.write(if_name)\n"
+                 "f.close()\n\n"
+                 "f = open('%(if_stop_file)s', 'w')\n"
+                 "f.close()\n\n"
+                 "while os.path.exists('%(if_stop_file)s'):\n"
+                 "    time.sleep(2)\n"
+               ) % ({
+                   "devtype": self._vif_type,
+                   "ip": self.get("ip4"),
+                   "prefix": self.get("prefix4"),
+                   "snat": ", snat=True" if self.get("snat") else "",
+                   "pointopoint": ", pointopoint=%s" % self.get("pointopoint") \
+                           if self.get("pointopoint") else "",
+                    "if_name_file": os.path.join(self.run_home, "if_name"),
+                    "if_stop_file": os.path.join(self.run_home, "if_stop"),
+                   })
+
+    @property
+    def _vif_type(self):
+        return "IFF_TAP"
+
+    def valid_connection(self, guid):
+        # TODO: Validate!
+        return True
+
diff --git a/src/nepi/resources/planetlab/tun.py b/src/nepi/resources/planetlab/tun.py
new file mode 100644 (file)
index 0000000..2c16cb1
--- /dev/null
@@ -0,0 +1,35 @@
+#
+#    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.resource import clsinit_copy
+from nepi.resources.planetlab.tap import PlanetlabTap
+
+@clsinit_copy
+class PlanetlabTun(PlanetlabTap):
+    _rtype = "PlanetlabTun"
+
+    def __init__(self, ec, guid):
+        super(PlanetlabTun, self).__init__(ec, guid)
+        self._home = "tun-%s" % self.guid
+
+    @property
+    def _vif_type(self):
+        return "IFF_TUN"
+
+
index 365ca08..5db230a 100755 (executable)
@@ -66,8 +66,8 @@ class Interface(ResourceManager):
         super(Interface, self).__init__(ec, guid)
 
     def deploy(self):
-        node = self.get_connected(Node.rtype())[0]
-        chan = self.get_connected(Channel.rtype())[0]
+        node = self.get_connected(Node)[0]
+        chan = self.get_connected(Channel)[0]
 
         if node.state < ResourceState.PROVISIONED:
             self.ec.schedule("0.5s", self.deploy)
@@ -91,7 +91,7 @@ class Node(ResourceManager):
             self.logger.debug(" -------- PROVISIONED ------- ")
             self.ec.schedule("3s", self.deploy)
         elif self.state == ResourceState.PROVISIONED:
-            ifaces = self.get_connected(Interface.rtype())
+            ifaces = self.get_connected(Interface)
             for rm in ifaces:
                 if rm.state < ResourceState.READY:
                     self.ec.schedule("0.5s", self.deploy)
@@ -107,7 +107,7 @@ class Application(ResourceManager):
         super(Application, self).__init__(ec, guid)
 
     def deploy(self):
-        node = self.get_connected(Node.rtype())[0]
+        node = self.get_connected(Node)[0]
         if node.state < ResourceState.READY:
             self.ec.schedule("0.5s", self.deploy)
         else:
index 93e4edc..85e0b71 100755 (executable)
@@ -30,7 +30,6 @@ class PlanetlabAPITestCase(unittest.TestCase):
         self.host1 = 'nepi2.pl.sophia.inria.fr'
         self.host2 = 'nepi5.pl.sophia.inria.fr'
 
-
     def test_list_hosts(self):
         slicename = os.environ.get('PL_USER')
         pl_pass = os.environ.get('PL_PASS')
diff --git a/test/resources/planetlab/tap.py b/test/resources/planetlab/tap.py
new file mode 100755 (executable)
index 0000000..091babe
--- /dev/null
@@ -0,0 +1,81 @@
+#!/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 nepi.resources.planetlab.node import PlanetlabNode
+from nepi.resources.planetlab.tap import PlanetlabTap
+from nepi.resources.linux.application import LinuxApplication
+
+from test_utils import skipIfNotAlive, skipInteractive
+
+import os
+import time
+import unittest
+
+class PlanetlabTapTestCase(unittest.TestCase):
+    def setUp(self):
+        self.host = "nepi2.pl.sophia.inria.fr"
+        self.user = "inria_nepi"
+
+    @skipIfNotAlive
+    def t_tap_create(self, host, user):
+        from nepi.execution.resource import ResourceFactory
+        
+        ResourceFactory.register_type(PlanetlabNode)
+        ResourceFactory.register_type(PlanetlabTap)
+        ResourceFactory.register_type(LinuxApplication)
+
+        ec = ExperimentController(exp_id = "test-tap-create")
+        
+        node = ec.register_resource("PlanetlabNode")
+        ec.set(node, "hostname", host)
+        ec.set(node, "username", user)
+        ec.set(node, "cleanHome", True)
+        ec.set(node, "cleanProcesses", True)
+
+        tap = ec.register_resource("PlanetlabTap")
+        ec.set(tap, "ip4", "192.168.1.1")
+        ec.set(tap, "prefix4", "24")
+        ec.register_connection(tap, node)
+
+        app = ec.register_resource("LinuxApplication")
+        cmd = "ping -c3 192.168.1.1" 
+        ec.set(app, "command", cmd)
+        ec.register_connection(app, node)
+
+        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(tap, "deviceName")
+        self.assertTrue(if_name.startswith("tap"))
+
+        ec.shutdown()
+
+    def test_tap_create(self):
+        self.t_tap_create(self.host, self.user)
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/test/resources/planetlab/tun.py b/test/resources/planetlab/tun.py
new file mode 100644 (file)
index 0000000..392f2e9
--- /dev/null
@@ -0,0 +1,81 @@
+#!/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 nepi.resources.planetlab.node import PlanetlabNode
+from nepi.resources.planetlab.tun import PlanetlabTun
+from nepi.resources.linux.application import LinuxApplication
+
+from test_utils import skipIfNotAlive, skipInteractive
+
+import os
+import time
+import unittest
+
+class PlanetlabTunTestCase(unittest.TestCase):
+    def setUp(self):
+        self.host = "nepi2.pl.sophia.inria.fr"
+        self.user = "inria_nepi"
+
+    @skipIfNotAlive
+    def t_tun_create(self, host, user):
+        from nepi.execution.resource import ResourceFactory
+        
+        ResourceFactory.register_type(PlanetlabNode)
+        ResourceFactory.register_type(PlanetlabTun)
+        ResourceFactory.register_type(LinuxApplication)
+
+        ec = ExperimentController(exp_id = "test-un-create")
+        
+        node = ec.register_resource("PlanetlabNode")
+        ec.set(node, "hostname", host)
+        ec.set(node, "username", user)
+        ec.set(node, "cleanHome", True)
+        ec.set(node, "cleanProcesses", True)
+
+        tun = ec.register_resource("PlanetlabTun")
+        ec.set(tun, "ip4", "192.168.1.1")
+        ec.set(tun, "prefix4", "24")
+        ec.register_connection(tun, node)
+
+        app = ec.register_resource("LinuxApplication")
+        cmd = "ping -c3 192.168.1.1" 
+        ec.set(app, "command", cmd)
+        ec.register_connection(app, node)
+
+        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(tun, "deviceName")
+        self.assertTrue(if_name.startswith("tun"))
+
+        ec.shutdown()
+
+    def test_tun_create(self):
+        self.t_tun_create(self.host, self.user)
+
+if __name__ == '__main__':
+    unittest.main()
+