Push openflow RMs
authorAlexandros kouvakas <alexandros.kouvakas@inria.fr>
Tue, 6 Aug 2013 08:25:09 +0000 (10:25 +0200)
committerAlexandros kouvakas <alexandros.kouvakas@inria.fr>
Tue, 6 Aug 2013 08:25:09 +0000 (10:25 +0200)
setup.py
src/nepi/resources/planetlab/openvswitch/__init__.py [new file with mode: 0644]
src/nepi/resources/planetlab/openvswitch/ovs.py [new file with mode: 0644]
src/nepi/resources/planetlab/openvswitch/ovsport.py [new file with mode: 0644]
src/nepi/resources/planetlab/openvswitch/tunnel.py [new file with mode: 0644]
src/nepi/resources/planetlab/tap.py
test/resources/planetlab/ovs.py [new file with mode: 0644]

index 0c68d5b..aa9bfc6 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -24,6 +24,7 @@ setup(
             "nepi.resources.ns3",
             "nepi.resources.omf",
             "nepi.resources.planetlab",
+            "nepi.resources.planetlab.openvswitch",
             "nepi.util"],
         package_dir = {"": "src"},
         package_data = {
diff --git a/src/nepi/resources/planetlab/openvswitch/__init__.py b/src/nepi/resources/planetlab/openvswitch/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/nepi/resources/planetlab/openvswitch/ovs.py b/src/nepi/resources/planetlab/openvswitch/ovs.py
new file mode 100644 (file)
index 0000000..e8b8011
--- /dev/null
@@ -0,0 +1,321 @@
+#
+#    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>
+#         Alexandros Kouvakas <alexandros.kouvakas@inria.fr>
+
+
+from nepi.execution.resource import ResourceManager, clsinit_copy, ResourceState
+from nepi.execution.attribute import Attribute, Flags
+from nepi.resources.planetlab.node import PlanetlabNode        
+from nepi.resources.linux.application import LinuxApplication
+import os
+
+reschedule_delay = "0.5s"
+
+@clsinit_copy                    
+class OVSWitch(LinuxApplication):
+    
+    _rtype = "OVSWitch"
+    _authorized_connections = ["PlanetlabNode", "OVSPort", "LinuxNode"]       
+
+    @classmethod
+    def _register_attributes(cls):
+        """ Register the attributes of OVSWitch RM 
+
+        """
+        bridge_name = Attribute("bridge_name", "Name of the switch/bridge",
+                flags = Flags.ExecReadOnly)    
+        virtual_ip_pref = Attribute("virtual_ip_pref", "Virtual IP/PREFIX of the switch",
+                flags = Flags.ExecReadOnly)       
+        controller_ip = Attribute("controller_ip", "IP of the controller",
+                flags = Flags.ExecReadOnly)
+        controller_port = Attribute("controller_port", "Port of the controller",
+                flags = Flags.ExecReadOnly)
+
+        cls._register_attribute(bridge_name)
+        cls._register_attribute(virtual_ip_pref)
+        cls._register_attribute(controller_ip)
+        cls._register_attribute(controller_port)
+
+    def __init__(self, ec, guid):
+        """
+        :param ec: The Experiment controller
+        :type ec: ExperimentController
+        :param guid: guid of the RM
+        :type guid: int
+        :param creds: Credentials to communicate with the rm 
+        :type creds: dict
+    
+        """
+        super(OVSWitch, self).__init__(ec, guid)
+        self._pid = None
+        self._ppid = None
+        self._home = "ovswitch-%s" % self.guid
+        self._checks = "ovsChecks-%s" % self.guid
+
+    @property
+    def node(self):
+        node = self.get_connected(PlanetlabNode.rtype())
+        if node: return node[0]
+        return None
+
+    @property
+    def ovs_home(self):
+        return os.path.join(self.node.exp_home, self._home)
+
+    @property
+    def ovs_checks(self):
+        return os.path.join(self.ovs_home, self._checks)
+
+    @property
+    def pid(self):
+        return self._pid
+
+    @property
+    def ppid(self):
+        return self._ppid
+
+#    def valid_connection(self, guid):
+#        """ Check if the connection with the guid in parameter is possible. Only meaningful connections are allowed.
+
+#        :param guid: Guid of the current RM
+#        :type guid: int
+#        :rtype:  Boolean
+
+#        """
+#        rm = self.ec.get_resource(guid)
+#        if rm.rtype() in self._authorized_connections:
+#            msg = "Connection between %s %s and %s %s accepted" % \
+#                (self.rtype(), self._guid, rm.rtype(), guid)
+#            self.debug(msg)
+#            return True
+#        msg = "Connection between %s %s and %s %s refused" % \
+#             (self.rtype(), self._guid, rm.rtype(), guid)
+#        self.debug(msg)
+#        return False
+
+    def valid_connection(self, guid):
+        # TODO: Validate!
+        return True
+
+    def provision(self):
+        # create home dir for ovs
+        self.node.mkdir(self.ovs_home)
+        # create dir for ovs checks
+        self.node.mkdir(self.ovs_checks)
+
+    def check_sliver_ovs(self):  
+        """ Check if sliver-ovs exists. If it does not exist, we interrupt
+        the execution immediately. 
+        """
+        cmd = "compgen -c | grep sliver-ovs"                   
+        out = err = ""
+
+        (out,err), proc = self.node.run_and_wait(cmd, self.ovs_checks, 
+                   shfile = "check_cmd.sh",
+                pidfile = "check_cmd_pidfile",
+                ecodefile = "check_cmd_exitcode", 
+                sudo = True, 
+                stdout = "check_cmd_stdout", 
+                stderr = "check_cmd_stderr")
+
+        (out, err), proc = self.node.check_output(self.ovs_checks, 'check_cmd_exitcode')
+        if out != "0\n":
+            msg = "Command sliver-ovs does not exist on the VM"         
+            self.debug(msg)
+            raise RuntimeError, msg
+        msg = "Command sliver-ovs exists" 
+        self.debug(msg)                                                
+
+    def deploy(self):
+        """ Wait until node is associated and deployed
+        """
+        node = self.node
+        if not node or node.state < ResourceState.READY:
+            self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.node.state )
+            self.ec.schedule(reschedule_delay, self.deploy)
+
+        else:
+            try:
+                self.discover()
+                self.provision()
+                self.check_sliver_ovs()
+                self.servers_on()
+                self.create_bridge()
+                self.assign_contr()
+                self.ovs_status()
+            except:
+                self._state = ResourceState.FAILED
+                raise
+                
+            self._state = ResourceState.READY
+
+    def servers_on(self):
+        """ Start the openvswitch servers and also checking 
+            if they started successfully 
+        """
+        self.info("Starting the OVSWitch servers")
+        command = ("sliver-ovs start") 
+                               
+        out = err = ""                                                                 
+        (out, err), proc = self.node.run_and_wait(command, self.ovs_checks,   
+                shfile = "start_srv.sh",
+                pidfile = "start_srv_pidfile",
+                ecodefile = "start_srv_exitcode", 
+                sudo = True, 
+                raise_on_error = True,
+                stdout = "start_srv_stdout", 
+                stderr = "start_srv_stderr")
+
+        (out, err), proc = self.node.check_output(self.ovs_checks, 'start_srv_exitcode')
+
+        if out != "0\n":
+            self.debug("Servers have not started")
+            raise RuntimeError, msg    
+                               
+        cmd = "ps -A | grep ovsdb-server"
+        out = err = ""
+        (out, err), proc = self.node.run_and_wait(cmd, self.ovs_checks, 
+                shfile = "status_srv.sh",
+                pidfile = "status_srv_pidfile",
+                ecodefile = "status_srv_exitcode", 
+                sudo = True, 
+                stdout = "status_srv_stdout", 
+                stderr = "status_srv_stderr")
+
+        # Check if the servers are running or not
+        (out, err), proc = self.node.check_output(self.ovs_checks, 'status_srv_exitcode')
+        if out != "0\n":
+            self.debug("Servers are not running")
+            raise RuntimeError, msg
+        self.info("Servers started")  
+
+    def del_old_br(self):
+        # TODO: Delete old bridges that might exist maybe by adding atribute
+        """ With ovs-vsctl list-br
+        """
+        pass
+
+    def create_bridge(self):
+        """ Create the bridge/switch and we check if we have any 
+            error during the SSH connection         
+        """
+        # TODO: Add check for virtual_ip belonging to vsys_tag
+        self.del_old_br()
+       
+        if self.get("bridge_name") and self.get("virtual_ip_pref"):    
+            bridge_name = self.get("bridge_name")
+            virtual_ip_pref = self.get("virtual_ip_pref")
+            self.info(" Creating the bridge %s and assigning %s" %\
+                (bridge_name, virtual_ip_pref) )
+            cmd = "sliver-ovs create-bridge '%s' '%s'" %\
+                (bridge_name, virtual_ip_pref) 
+            out = err = ""
+            (out, err), proc = self.node.run_and_wait(cmd, self.ovs_checks,
+                    shfile = "create_br.sh",
+                    pidfile = "create_br_pidfile",
+                    ecodefile = "create_br_exitcode", 
+                    sudo = True, 
+                    stdout = "create_br_stdout", 
+                    stderr = "create_br_stderr") 
+            (out, err), proc = self.node.check_output(self.ovs_checks, 'create_br_exitcode')
+            if out != "0\n":
+                msg = "No such pltap netdev\novs-appctl: ovs-vswitchd: server returned an error"
+                self.debug("Check again the virtual IP")                       
+                raise RuntimeError, msg
+            self.info("Bridge %s created" % bridge_name)
+          
+        else:  
+            msg = "No assignment in one or both attributes"
+            self.error(msg)
+            self.debug("Bridge name is %s and virtual_ip_pref is %s" %\
+                (self.get("bridge_name"), self.get("virtual_ip_pref")) )
+            raise AttributeError, msg
+
+    def assign_contr(self):
+        """ Set the controller IP
+        """
+        if self.get("controller_ip") and self.get("controller_port"):
+            controller_ip = self.get("controller_ip")
+            controller_port = self.get("controller_port")
+            self.info("Assigning the controller to the %s" % self.get("bridge_name"))
+            cmd = "ovs-vsctl set-controller %s tcp:%s:%s" %\
+                (self.get("bridge_name"), controller_ip, controller_port)
+            out = err = ""
+            (out, err), proc = self.node.run(cmd, self.ovs_checks,
+                    sudo = True, 
+                    stdout = "stdout", 
+                    stderr = "stderr")
+            if err != "":
+                self.debug("SSH connection refusing in assign_contr")
+                raise RuntimeError, msg
+            self.info("Controller assigned")
+           
+    def ovs_status(self):
+        """ Print the status of the created bridge                                     
+        """
+        cmd = "sliver-ovs show | tail -n +2"
+        out = err = ""
+        (out, err), proc = self.node.run_and_wait(cmd, self.ovs_home,
+                sudo = True, 
+                stdout = "show_stdout", 
+                stderr = "show_stderr") 
+        (out, err), proc = self.node.check_output(self.ovs_home, 'show_stdout')
+        self.info(out)
+
+    def start(self):
+        """ Start the RM. It means nothing special for 
+            ovswitch for now.  
+        """
+        pass
+
+    def stop(self):
+        """ Stop the RM.It means nothing 
+            for ovswitch for now.
+        """
+        pass
+
+    def release(self):
+        """ Delete the bridge and 
+            close the servers
+        """
+        # Node needs to wait until all associated RMs are released
+        # to be released
+        from nepi.resources.planetlab.openvswitch.ovsport import OVSPort
+        rm = self.get_connected(OVSPort.rtype())
+
+        if rm[0].state < ResourceState.FINISHED:
+            self.ec.schedule(reschedule_delay, self.release)
+            return 
+            
+        msg = "Deleting the bridge %s" % self.get('bridge_name')
+        self.info(msg)
+        cmd = "sliver-ovs del-bridge %s" % self.get('bridge_name')
+        (out, err), proc = self.node.run(cmd, self.ovs_checks,
+                sudo = True)
+        cmd = "sliver-ovs stop"
+        (out, err), proc = self.node.run(cmd, self.ovs_checks,
+                sudo = True)
+        
+        if proc.poll():
+            self.fail()
+            self.error(msg, out, err)
+            raise RuntimeError, msg
+     
+        self._state = ResourceState.RELEASED
+        
diff --git a/src/nepi/resources/planetlab/openvswitch/ovsport.py b/src/nepi/resources/planetlab/openvswitch/ovsport.py
new file mode 100644 (file)
index 0000000..ce59af2
--- /dev/null
@@ -0,0 +1,245 @@
+#
+#    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>
+#            Alexandros Kouvakas <alexandros.kouvakas@gmail.com>
+
+from nepi.execution.attribute import Attribute, Flags, Types
+from nepi.execution.resource import ResourceManager, clsinit_copy, ResourceState       
+from nepi.resources.planetlab.openvswitch.ovs import OVSWitch        
+from nepi.resources.planetlab.node import PlanetlabNode        
+from nepi.resources.linux.application import LinuxApplication
+
+reschedule_delay = "0.5s"
+
+@clsinit_copy                 
+class OVSPort(LinuxApplication):
+    """
+    .. class:: Class Args :
+      
+        :param ec: The Experiment controller
+        :type ec: ExperimentController
+        :param guid: guid of the RM
+        :type guid: int
+        :param creds: Credentials to communicate with the rm 
+        :type creds: dict
+
+    """
+    
+    _rtype = "OVSPort"
+    _authorized_connections = ["OVSWitch", "Tunnel"]      
+
+    @classmethod
+    def _register_attributes(cls):
+        """ Register the attributes of OVSPort RM 
+
+        """
+        port_name = Attribute("port_name", "Name of the port",
+            flags = Flags.ExecReadOnly)                        
+
+        cls._register_attribute(port_name)
+
+    def __init__(self, ec, guid):
+        """
+        :param ec: The Experiment controller
+        :type ec: ExperimentController
+        :param guid: guid of the RM
+        :type guid: int
+    
+        """
+        super(OVSPort, self).__init__(ec, guid)
+        self._port_number = None
+        self.port_info = []         
+
+    @property
+    def node(self):
+        rm_list = self.get_connected(OVSWitch.rtype())
+        if rm_list:
+            for elt in rm_list:
+                node = elt.get_connected(PlanetlabNode.rtype())
+                if node: return node[0]
+        return node[0]
+
+    @property
+    def ovswitch(self):
+        ovswitch = self.get_connected(OVSWitch.rtype())
+        if ovswitch: return ovswitch[0]
+        return None
+        
+    @property
+    def port_number(self):
+        return self._port_number
+
+    def valid_connection(self, guid):
+        # TODO: Validate!
+        return True
+
+#    def valid_connection(self, guid):
+#        """ Check if the connection is available.
+
+#        :param guid: Guid of the current RM
+#        :type guid: int
+#        :rtype:  Boolean
+
+#        """
+#        rm = self.ec.get_resource(guid)
+#        if rm.rtype() in self._authorized_connections:
+#            msg = "Connection between %s %s and %s %s accepted" % (self.rtype(), self._guid, rm.rtype(), guid)
+#            self.debug(msg)
+#            return True
+#        msg = "Connection between %s %s and %s %s refused" % (self.rtype(), self._guid, rm.rtype(), guid)
+#        self.debug(msg)
+
+    def get_host_ip(self):
+        """ Get the hostname of the node that
+        the port belongs to. We use it for tunnel.
+        """
+        get_host_ip = self.node
+        if not get_host_ip: 
+            msg = "info_list is empty"
+            self.debug(msg)
+            raise RuntimeError, msg
+        import socket
+        self.port_info.append(get_host_ip.get('hostname'))
+        self.port_info.append(socket.gethostbyname(self.port_info[0]))   
+    
+    def create_port(self):
+        """ Create the desired port
+        """
+        port_name = self.get('port_name')
+        if not (port_name or self.ovswitch):
+            msg = "The rm_list is empty or the port name is not assigned\n Failed to create port"
+            self.error(msg)
+            self.debug("ovswitch_list = %s and port_name = %s" % (self.ovswitch, port_name) )
+            raise AttributeError, msg
+
+        self.info("Create the port %s on switch %s" % (port_name, self.ovswitch.get('bridge_name')))     
+        self.port_info.append(port_name)
+        self.port_info.append(self.ovswitch.get('virtual_ip_pref'))
+        cmd = "sliver-ovs create-port %s %s" % (self.ovswitch.get('bridge_name'), port_name)   
+        self.node.run(cmd, self.ovswitch.ovs_checks, 
+                stderr = "stdout-%s" % port_name, 
+                stdout = "stderr-%s" % port_name,
+                sudo = True)
+           
+    def get_local_end(self):
+        """ Get the local_endpoint of the port
+        """
+        msg = "Discovering the number of the port %s"\
+            % self.get('port_name')
+        self.info(msg)
+
+        command = "sliver-ovs get-local-endpoint %s"\
+            % self.get('port_name')
+        out = err = ""
+        (out, err), proc = self.node.run_and_wait(command, self.ovswitch.ovs_checks, 
+                shfile = "port_number-%s.sh" % self.get('port_name'),
+                pidfile = "port_number_pidfile-%s" % self.get('port_name'),
+                ecodefile = "port_number_exitcode-%s" % self.get('port_name'), 
+                sudo = True, 
+                stdout = "stdout-%s" % self.get('port_name'),    
+                stderr = "stderr-%s" % self.get('port_name'))
+
+        if err != "":
+            msg = "No assignment in attribute port_name"
+            self.error(msg)
+            self.debug("You are in the method get_local_end and the port_name = %s" % self.get('port_name'))
+            raise AttributeError, msg
+        self._port_number = None
+        self._port_number = int(out)
+        self.port_info.append(self._port_number)                               
+        self.info("The number of the %s is %s" % (self.get('port_name'), self._port_number))
+   
+    def switch_connect_command(self, local_port_name, 
+            remote_ip, remote_port_num):
+        """ Script for switch links
+        """
+        command = ["sliver-ovs"]
+        command.append("set-remote-endpoint ")
+        command.append("%s " % local_port_name)
+        command.append("%s " % remote_ip)
+        command.append("%s " % remote_port_num)
+        command = " ".join(command)
+        command = self.replace_paths(command)
+        return command
+        
+    def provision(self):
+        """ Provision the ports.No meaning.
+        """
+        pass
+
+    def discover(self):
+        """ Discover the ports.No meaning
+        """    
+        pass
+
+    def deploy(self):
+        """ Wait until ovswitch is started
+        """
+        ovswitch = self.ovswitch
+        if not ovswitch or ovswitch.state < ResourceState.READY:       
+            self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.ovswitch.state )  
+            self.ec.schedule(reschedule_delay, self.deploy)
+            
+        else:
+            try:
+                self.discover()
+                self.provision()
+                self.get_host_ip()
+                self.create_port()
+                self.get_local_end()
+                self.ovswitch.ovs_status()
+                self._state = ResourceState.READY
+            except:
+                self._state = ResourceState.FAILED
+                raise
+
+    def start(self):
+        """ Start the RM. It means nothing special for 
+            ovsport for now.
+        """
+        pass
+       
+    def stop(self):
+        """ Stop the RM. It means nothing special for 
+            ovsport for now.        
+        """
+        pass
+        
+    def release(self):
+        """ Release the port RM means delete the ports
+        """
+        # OVS needs to wait until all associated RMs are released
+        # to be released
+        from nepi.resources.planetlab.openvswitch.tunnel import Tunnel
+        rm = self.get_connected(Tunnel.rtype())
+        if rm[0].state < ResourceState.FINISHED:
+            self.ec.schedule(reschedule_delay, self.release)
+            return 
+            
+        msg = "Deleting the port %s" % self.get('port_name')
+        self.info(msg)
+        cmd = "sliver-ovs del_port %s" % self.get('port_name')
+        (out, err), proc = self.node.run(cmd, self.ovswitch.ovs_checks,
+                sudo = True)
+
+        if proc.poll():
+            self.fail()
+            self.error(msg, out, err)
+            raise RuntimeError, msg
+
+        self._state = ResourceState.RELEASED
diff --git a/src/nepi/resources/planetlab/openvswitch/tunnel.py b/src/nepi/resources/planetlab/openvswitch/tunnel.py
new file mode 100644 (file)
index 0000000..72c6728
--- /dev/null
@@ -0,0 +1,430 @@
+#
+#    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>
+#            Alexandros Kouvakas <alexandros.kouvakas@gmail.com>
+
+from nepi.execution.attribute import Attribute, Flags, Types
+from nepi.execution.resource import ResourceManager, clsinit_copy, ResourceState 
+from nepi.resources.linux.application import LinuxApplication
+from nepi.resources.planetlab.node import PlanetlabNode            
+from nepi.resources.planetlab.openvswitch.ovs import OVSWitch   
+from nepi.util.timefuncs import tnow, tdiffsec     
+
+import os
+import time
+import socket
+
+
+reschedule_delay = "0.5s"
+
+@clsinit_copy                 
+class Tunnel(LinuxApplication):
+    """
+    .. class:: Class Args :
+      
+        :param ec: The Experiment controller
+        :type ec: ExperimentController
+        :param guid: guid of the RM
+        :type guid: int
+        :param creds: Credentials to communicate with the rm 
+        :type creds: dict
+
+    """
+    
+    _rtype = "Tunnel"
+    _authorized_connections = ["OVSPort", "PlanetlabTap"]    
+
+    @classmethod
+    def _register_attributes(cls):
+        """ Register the attributes of Connection RM 
+
+        """
+        cipher = Attribute("cipher",
+               "Cipher to encript communication. "
+                "One of PLAIN, AES, Blowfish, DES, DES3. ",
+                default = None,
+                allowed = ["PLAIN", "AES", "Blowfish", "DES", "DES3"],
+                type = Types.Enumerate, 
+                flags = Flags.ExecReadOnly)
+
+        cipher_key = Attribute("cipherKey",
+                "Specify a symmetric encryption key with which to protect "
+                "packets across the tunnel. python-crypto must be installed "
+                "on the system." ,
+                flags = Flags.ExecReadOnly)
+
+        txqueuelen = Attribute("txQueueLen",
+                "Specifies the interface's transmission queue length. "
+                "Defaults to 1000. ", 
+                type = Types.Integer, 
+                flags = Flags.ExecReadOnly)
+
+        bwlimit = Attribute("bwLimit",
+                "Specifies the interface's emulated bandwidth in bytes "
+                "per second.",
+                type = Types.Integer, 
+                flags = Flags.ExecReadOnly)
+
+        cls._register_attribute(cipher)
+        cls._register_attribute(cipher_key)
+        cls._register_attribute(txqueuelen)
+        cls._register_attribute(bwlimit)
+
+    def __init__(self, ec, guid):
+        """
+        :param ec: The Experiment controller
+        :type ec: ExperimentController
+        :param guid: guid of the RM
+        :type guid: int
+    
+        """
+        super(Tunnel, self).__init__(ec, guid)
+        self._home = "tunnel-%s" % self.guid
+        self.port_info_tunl = []
+        self._nodes = []
+        self._pid = None
+        self._ppid = None
+
+    @property
+    def node(self):
+        return self._nodes[0]
+
+    def app_home(self, node):
+        return os.path.join(node.exp_home, self._home)
+
+    def run_home(self, node):
+        return os.path.join(self.app_home(node), self.ec.run_id)
+
+    def port_endpoints(self):
+        # Switch-Switch connection
+        connected = []
+        for guid in self.connections:
+            rm = self.ec.get_resource(guid)
+            if hasattr(rm, "create_port"):
+                connected.append(rm)
+        return connected
+
+    def mixed_endpoints(self):
+        # Switch-Host connection
+        connected = [1, 2]
+        for guid in self.connections:
+            rm = self.ec.get_resource(guid)
+            if hasattr(rm, "create_port"):
+                connected[0] = rm
+            elif hasattr(rm, "udp_connect_command"):
+                connected[1] = rm
+        return connected
+
+    def get_node(self, endpoint):
+        # Get connected to the nodes
+        if hasattr(endpoint, "create_port"):
+            res = []
+            rm_list = endpoint.get_connected(OVSWitch.rtype())
+            if rm_list:
+                rm = rm_list[0].get_connected(PlanetlabNode.rtype())
+                if rm: 
+                    res.append(rm[0])
+            return res
+        else:
+            res = []
+            rm = endpoint.get_connected(PlanetlabNode.rtype())
+            if rm :
+                res.append(rm[0])
+            return res
+
+    @property
+    def endpoint1(self):
+        if self.check_endpoints():
+            port_endpoints = self.port_endpoints()
+            if port_endpoints: return port_endpoints[0]
+        else:
+            mixed_endpoints = self.mixed_endpoints()
+            if mixed_endpoints: return mixed_endpoints[0]
+
+    @property
+    def endpoint2(self):
+        if self.check_endpoints():
+            port_endpoints = self.port_endpoints()
+            if port_endpoints: return port_endpoints[1]
+        else:
+            mixed_endpoints = self.mixed_endpoints()
+            if mixed_endpoints: return mixed_endpoints[1]
+                
+    def check_endpoints(self):
+        """ Check if the links are between switches
+            or switch-host. Return False for latter.
+        """
+        port_endpoints = self.port_endpoints()
+        if len(port_endpoints) == 2:
+            return True
+        else: 
+            return False
+
+    def get_port_info(self, endpoint, rem_endpoint):
+        """ Retrieve the port_info list for each port
+       
+            :param port_info_tunl: [hostname, publ_IP_addr, port_name,
+                virtual_ip, local_port_Numb]
+            :type port_info_tunl: list
+        """
+        self.port_info_tunl = []
+        if self.check_endpoints():
+            # Use for the link switch-->switch
+            self.port_info_tunl.append(endpoint.port_info)
+            host0, ip0, pname0, virt_ip0, pnumber0 = self.port_info_tunl[0]
+            self.port_info_tunl.append(rem_endpoint.port_info)
+            host1, ip1, pname1, virt_ip1, pnumber1 = self.port_info_tunl[1]
+            return (pname0, ip1, pnumber1)      
+         
+        else:
+            # Use for the link host-->switch
+            self.port_info_tunl.append(endpoint.port_info)
+            host0, ip0, pname0, virt_ip0, pnumber0 = self.port_info_tunl[0]
+            return pnumber0
+    
+    def udp_connect(self, endpoint, rem_endpoint):     
+        # Collect info from rem_endpoint
+        self._nodes = self.get_node(rem_endpoint)
+        remote_ip = socket.gethostbyname(self.node.get("hostname"))
+        # Collect info from endpoint
+        self._nodes = self.get_node(endpoint) 
+        local_port_file = os.path.join(self.run_home(self.node), 
+                "local_port")
+        remote_port_file = os.path.join(self.run_home(self.node), 
+                "remote_port")
+        ret_file = os.path.join(self.run_home(self.node), 
+                "ret_file")
+        cipher = self.get("cipher")
+        cipher_key = self.get("cipherKey")
+        bwlimit = self.get("bwLimit")
+        txqueuelen = self.get("txQueueLen")
+
+        rem_port = str(self.get_port_info(rem_endpoint, endpoint))           
+        # Upload the remote port in a file
+        self.node.upload(rem_port,
+                remote_port_file,
+                text = True,
+                overwrite = False)
+       
+        udp_connect_command = endpoint.udp_connect_command(
+                remote_ip, local_port_file, remote_port_file,
+                ret_file, cipher, cipher_key, bwlimit, txqueuelen) 
+
+        # upload command to host_connect.sh script
+        shfile = os.path.join(self.app_home(self.node), "host_connect.sh")
+        self.node.upload(udp_connect_command,
+                shfile,
+                text = True,
+                overwrite = False)
+
+        # invoke connect script
+        cmd = "bash %s" % shfile
+        (out, err), proc = self.node.run(cmd, self.run_home(self.node),
+                sudo  = True,
+                stdout = "udp_stdout",
+                stderr = "udp_stderr")
+
+        # check if execution errors
+        msg = "Failed to connect endpoints"
+
+        if proc.poll():
+            self.fail()
+            self.error(msg, out, err)
+            raise RuntimeError, msg
+        msg = "Connection on host %s configured" \
+            % self.node.get("hostname")
+        self.info(msg)
+         
+        # Wait for pid file to be generated
+        self._nodes = self.get_node(endpoint) 
+        pid, ppid = self.node.wait_pid(self.run_home(self.node))
+        
+        # If the process is not running, check for error information
+        # on the remote machine
+        if not pid or not ppid:
+            (out, err), proc = self.node.check_errors(self.run_home(self.node))
+            # 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
+                
+        return (pid, ppid)
+
+    def switch_connect(self, endpoint, rem_endpoint):
+        """ Get switch connect command
+        """
+        # Get and configure switch connection command
+        (local_port_name, remote_ip, remote_port_num) = self.get_port_info(
+                endpoint, rem_endpoint)
+        switch_connect_command = endpoint.switch_connect_command(
+                local_port_name, remote_ip, remote_port_num)
+        self._nodes = self.get_node(endpoint) 
+
+        # Upload command to the file sw_connect.sh
+        shfile = os.path.join(self.app_home(self.node), "sw_connect.sh")
+        self.node.upload(switch_connect_command,
+                shfile,
+                text = True,
+                overwrite = False)
+
+        #invoke connect script
+        cmd = "bash %s" % shfile
+        (out, err), proc = self.node.run(cmd, self.run_home(self.node),
+                sudo  = True,
+                stdout = "sw_stdout",
+                stderr = "sw_stderr")
+        
+        # check if execution errors occured
+        msg = "Failed to connect endpoints"
+
+        if proc.poll():
+            self.fail()
+            self.error(msg, out, err)
+            raise RuntimeError, msg
+        else:
+            msg = "Connection on port %s configured" % local_port_name
+            self.info(msg)
+            return 
+
+    def sw_host_connect(self, endpoint, rem_endpoint):
+        """Link switch--> host
+        """
+        # Retrieve remote port number from rem_endpoint
+        local_port_name = endpoint.get('port_name')
+        self._nodes = self.get_node(rem_endpoint)
+        time.sleep(2) # Without this, sometimes I get nothing in remote_port_num
+        remote_port_num = ''
+        (out, err), proc = self.node.check_output(self.run_home(self.node), 'local_port')
+        remote_port_num = int(out)
+        remote_ip = socket.gethostbyname(self.node.get("hostname"))
+        switch_connect_command = endpoint.switch_connect_command(
+                local_port_name, remote_ip, remote_port_num)
+
+        # Upload command to the file sw_connect.sh
+        self._nodes = self.get_node(endpoint) 
+        shfile = os.path.join(self.app_home(self.node), "sw_connect.sh")
+        self.node.upload(switch_connect_command,
+                shfile,
+                text = True,
+                overwrite = False)
+
+        #invoke connect script
+        cmd = "bash %s" % shfile
+        (out, err), proc = self.node.run(cmd, self.run_home(self.node),
+                sudo  = True,
+                stdout = "sw_stdout",
+                stderr = "sw_stderr")
+        
+        # check if execution errors occured
+        msg = "Failed to connect endpoints"
+
+        if proc.poll():
+            self.fail()
+            self.error(msg, out, err)
+            raise RuntimeError, msg
+        else:
+            msg = "Connection on port %s configured" % local_port_name
+            self.info(msg)
+            return                                                      
+
+    def provision(self):
+        """ Provision the tunnel
+        """
+        # Create folders
+        self._nodes = self.get_node(self.endpoint1)
+        self.node.mkdir(self.run_home(self.node))
+        self._nodes = self.get_node(self.endpoint2)
+        self.node.mkdir(self.run_home(self.node))
+
+        if self.check_endpoints():
+            #Invoke connect script between switches
+            switch_connect1 = self.switch_connect(self.endpoint1, self.endpoint2)
+            switch_connect2 = self.switch_connect(self.endpoint2, self.endpoint1)
+
+        else: 
+            # Invoke connect script between switch & host
+            (self._pid, self._ppid) = self.udp_connect(self.endpoint2, self.endpoint1)
+            switch_connect = self.sw_host_connect(self.endpoint1, self.endpoint2)
+
+        self.debug("------- READY -------")
+        self._provision_time = tnow()
+        self._state = ResourceState.PROVISIONED
+
+    def discover(self):
+        """ Discover the tunnel
+
+        """    
+        pass
+
+    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):
+        """ Start the RM. It means nothing special for 
+            ovsport for now.
+        """
+        pass
+        
+       
+    def stop(self):
+        """ Stop the RM. It means nothing special for 
+            ovsport for now.        
+        """
+        pass
+
+    def release(self):
+        """ Release the udp_tunnel on endpoint2.
+            On endpoint1 means nothing special.        
+        """
+        if not self.check_endpoints():
+            # Kill the TAP devices
+            # TODO: Make more generic Release method of PLTAP
+            if self._pid and self._ppid:
+                self._nodes = self.get_node(self.endpoint2) 
+                (out, err), proc = self.node.kill(self._pid,
+                        self._ppid, sudo = True)
+            if err or proc.poll():
+                    # check if execution errors occurred
+                    msg = " Failed to delete TAP device"
+                    self.error(msg, err, err)
+                    self.fail()
+            
+        self._state = ResourceState.RELEASED
+
+
+
+
+
+
+
+
index 411eb51..47a5725 100644 (file)
@@ -185,7 +185,7 @@ class PlanetlabTap(LinuxApplication):
         
         if self.state == ResourceState.STARTED:
             self.info("Stopping command '%s'" % command)
-
+            self.info("STOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP")
             command = "bash %s" % os.path.join(self.app_home, "stop.sh")
             (out, err), proc = self.execute_command(command,
                     blocking = True)
@@ -204,6 +204,7 @@ class PlanetlabTap(LinuxApplication):
 
                 if out.strip().find(self.get("deviceName")) == -1: 
                     # tap is not running is not running (socket not found)
+                    print "HEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
                     self._finish_time = tnow()
                     self._state = ResourceState.FINISHED
 
diff --git a/test/resources/planetlab/ovs.py b/test/resources/planetlab/ovs.py
new file mode 100644 (file)
index 0000000..0cb6c20
--- /dev/null
@@ -0,0 +1,162 @@
+#!/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>
+#         Alexandros Kouvakas <alexandros.kouvakas@gmail.com>
+
+#         Switch1 ------- Switch2         
+#            /                \           
+#           /                  \          
+#          /                    \         
+#       Host1                  Host2      
+
+from nepi.execution.ec import ExperimentController 
+
+from test_utils import skipIfAnyNotAlive
+
+import os
+import time
+import unittest
+
+class OvsTestCase(unittest.TestCase):
+    def setUp(self):
+        self.switch1 = "planetlab2.virtues.fi"
+        self.switch2 = "planetlab2.upc.es"
+        self.host1 = "planetlab2.ionio.gr"
+        self.host2 = "planetlab2.cs.aueb.gr"
+        self.user = "inria_nepi"
+
+    @skipIfAnyNotAlive
+    def t_ovs(self, user1, switch1, user2, switch2, user3, host1, user4, host2):
+
+        ec = ExperimentController(exp_id = "test-ovs")
+        
+        node1 = ec.register_resource("PlanetlabNode")
+        ec.set(node1, "hostname", switch1)
+        ec.set(node1, "username", user1)
+        ec.set(node1, "cleanHome", True)
+        ec.set(node1, "cleanProcesses", True)  
+
+        ovs1 = ec.register_resource("OVSWitch")
+        ec.set(ovs1, "bridge_name", "nepi_bridge")
+        ec.set(ovs1, "virtual_ip_pref", "192.168.3.1/24")
+        ec.set(ovs1, "controller_ip", "85.23.168.77")
+        ec.set(ovs1, "controller_port", "6633")
+        ec.register_connection(ovs1, node1)
+
+        port1 = ec.register_resource("OVSPort")
+        ec.set(port1, "port_name", "port-1")
+        ec.register_connection(port1, ovs1)
+
+        port2 = ec.register_resource("OVSPort")
+        ec.set(port2, "port_name", "port-2")
+        ec.register_connection(port2, ovs1)
+
+        node2 = ec.register_resource("PlanetlabNode")
+        ec.set(node2, "hostname", switch2)
+        ec.set(node2, "username", user2)
+        ec.set(node2, "cleanHome", True)
+        ec.set(node2, "cleanProcesses", True) 
+
+        ovs2 = ec.register_resource("OVSWitch")
+        ec.set(ovs2, "bridge_name", "nepi_bridge")
+        ec.set(ovs2, "virtual_ip_pref", "192.168.3.2/24")
+        ec.set(ovs2, "controller_ip", "85.23.168.77")
+        ec.set(ovs2, "controller_port", "6633")
+        ec.register_connection(ovs2, node2)
+
+        port3 = ec.register_resource("OVSPort")
+        ec.set(port3, "port_name", "port-3")
+        ec.register_connection(port3, ovs2)  
+
+        port4 = ec.register_resource("OVSPort")
+        ec.set(port4, "port_name", "port-4")
+        ec.register_connection(port4, ovs2)
+
+        node3 = ec.register_resource("PlanetlabNode")
+        ec.set(node3, "hostname", host1)
+        ec.set(node3, "username", user3)
+        ec.set(node3, "cleanHome", True)
+        ec.set(node3, "cleanProcesses", True)
+
+        tap1 = ec.register_resource("PlanetlabTap")
+        ec.set(tap1, "ip4", "192.168.3.3")
+        ec.set(tap1, "pointopoint", "192.168.3.1")
+        ec.set(tap1, "prefix4", 24)
+        ec.register_connection(tap1, node3)
+
+        node4 = ec.register_resource("PlanetlabNode")
+        ec.set(node4, "hostname", host2)
+        ec.set(node4, "username", user4)
+        ec.set(node4, "cleanHome", True)
+        ec.set(node4, "cleanProcesses", True)
+
+        tap2 = ec.register_resource("PlanetlabTap")
+        ec.set(tap2, "ip4", "192.168.3.4")
+        ec.set(tap2, "pointopoint", "192.168.3.2")
+        ec.set(tap2, "prefix4", 24)
+        ec.register_connection(tap2, node4)
+
+        ovstun1 = ec.register_resource("Tunnel")
+        ec.register_connection(port1, ovstun1)
+        ec.register_connection(tap1, ovstun1)
+
+        ovstun2 = ec.register_resource("Tunnel")
+        ec.register_connection(port3, ovstun2)
+        ec.register_connection(tap2, ovstun2)
+
+        ovstun3 = ec.register_resource("Tunnel")
+        ec.register_connection(port2, ovstun3)
+        ec.register_connection(port4, ovstun3)
+
+        app1 = ec.register_resource("LinuxApplication")
+        cmd = "ping -c3 192.168.3.2"
+        ec.set(app1, "command", cmd)
+        ec.register_connection(app1, node1)
+
+        app2 = ec.register_resource("LinuxApplication")
+        cmd = "ping -c3 192.168.3.4"
+        ec.set(app2, "command", cmd)
+        ec.register_connection(app2, node2)
+
+        ec.deploy()
+
+        ec.wait_finished(app2)
+        
+        if_name = ec.get(tap1, "deviceName")
+        self.assertTrue(if_name.startswith("tap"))
+        
+        if_name = ec.get(tap2, "deviceName")
+        self.assertTrue(if_name.startswith("tap"))
+
+        ping1 = ec.trace(app1, 'stdout')
+        expected1 = """3 packets transmitted, 3 received, 0% packet loss"""
+        self.assertTrue(ping1.find(expected1) > -1)
+
+        ping2 = ec.trace(app2, 'stdout')
+        expected2 = """3 packets transmitted, 3 received, 0% packet loss"""
+        self.assertTrue(ping2.find(expected2) > -1)
+
+        ec.shutdown()
+
+    def test_ovs(self):
+        self.t_ovs(self.user, self.switch1, self.user, self.switch2, self.user, self.host1, self.user, self.host2)
+
+if __name__ == '__main__':
+    unittest.main()
+