From 98404dbb5666f17408ed6166d47bba12110a12f1 Mon Sep 17 00:00:00 2001 From: Alexandros kouvakas Date: Tue, 6 Aug 2013 10:25:09 +0200 Subject: [PATCH] Push openflow RMs --- setup.py | 1 + .../planetlab/openvswitch/__init__.py | 0 .../resources/planetlab/openvswitch/ovs.py | 321 +++++++++++++ .../planetlab/openvswitch/ovsport.py | 245 ++++++++++ .../resources/planetlab/openvswitch/tunnel.py | 430 ++++++++++++++++++ src/nepi/resources/planetlab/tap.py | 3 +- test/resources/planetlab/ovs.py | 162 +++++++ 7 files changed, 1161 insertions(+), 1 deletion(-) create mode 100644 src/nepi/resources/planetlab/openvswitch/__init__.py create mode 100644 src/nepi/resources/planetlab/openvswitch/ovs.py create mode 100644 src/nepi/resources/planetlab/openvswitch/ovsport.py create mode 100644 src/nepi/resources/planetlab/openvswitch/tunnel.py create mode 100644 test/resources/planetlab/ovs.py diff --git a/setup.py b/setup.py index 0c68d5b5..aa9bfc6b 100755 --- 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 index 00000000..e69de29b diff --git a/src/nepi/resources/planetlab/openvswitch/ovs.py b/src/nepi/resources/planetlab/openvswitch/ovs.py new file mode 100644 index 00000000..e8b8011e --- /dev/null +++ b/src/nepi/resources/planetlab/openvswitch/ovs.py @@ -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 . +# +# Author: Alina Quereilhac +# Alexandros Kouvakas + + +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 index 00000000..ce59af24 --- /dev/null +++ b/src/nepi/resources/planetlab/openvswitch/ovsport.py @@ -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 . +# +# Author: Alina Quereilhac +# Alexandros Kouvakas + +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 index 00000000..72c6728b --- /dev/null +++ b/src/nepi/resources/planetlab/openvswitch/tunnel.py @@ -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 . +# +# Author: Alina Quereilhac +# Alexandros Kouvakas + +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 + + + + + + + + diff --git a/src/nepi/resources/planetlab/tap.py b/src/nepi/resources/planetlab/tap.py index 411eb514..47a57255 100644 --- a/src/nepi/resources/planetlab/tap.py +++ b/src/nepi/resources/planetlab/tap.py @@ -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 index 00000000..0cb6c20b --- /dev/null +++ b/test/resources/planetlab/ovs.py @@ -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 . +# +# Author: Alina Quereilhac +# Alexandros Kouvakas + +# 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() + -- 2.43.0