From 5d80869516dfcecb8e7fe6f117e2fb658b57b25b Mon Sep 17 00:00:00 2001 From: Alina Quereilhac Date: Mon, 8 Jul 2013 23:15:24 -0700 Subject: [PATCH] Added PlanetlabTAP & PlanetlabTUN --- Makefile | 9 +- src/nepi/execution/resource.py | 4 +- src/nepi/resources/linux/application.py | 51 +++- .../resources/linux/ccn/ccnapplication.py | 9 +- src/nepi/resources/linux/ccn/ccncontent.py | 9 +- src/nepi/resources/linux/ccn/ccnr.py | 2 +- src/nepi/resources/linux/ccn/fibentry.py | 9 +- src/nepi/resources/linux/interface.py | 4 +- src/nepi/resources/linux/node.py | 9 +- src/nepi/resources/omf/application.py | 3 +- src/nepi/resources/omf/interface.py | 3 +- src/nepi/resources/planetlab/node.py | 83 +----- src/nepi/resources/planetlab/tap.py | 260 ++++++++++++++++++ src/nepi/resources/planetlab/tun.py | 35 +++ test/execution/resource.py | 8 +- test/{resources/linux => lib}/test_utils.py | 0 test/resources/planetlab/plcapi.py | 1 - test/resources/planetlab/tap.py | 81 ++++++ test/resources/planetlab/tun.py | 81 ++++++ 19 files changed, 522 insertions(+), 139 deletions(-) create mode 100644 src/nepi/resources/planetlab/tap.py create mode 100644 src/nepi/resources/planetlab/tun.py rename test/{resources/linux => lib}/test_utils.py (100%) create mode 100755 test/resources/planetlab/tap.py create mode 100644 test/resources/planetlab/tun.py diff --git a/Makefile b/Makefile index 4909c6cb..1a3420c2 100644 --- 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 \ diff --git a/src/nepi/execution/resource.py b/src/nepi/execution/resource.py index 3882523d..80e4a3e5 100644 --- a/src/nepi/execution/resource.py +++ b/src/nepi/execution/resource.py @@ -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 diff --git a/src/nepi/resources/linux/application.py b/src/nepi/resources/linux/application.py index 453010ef..9111cc12 100644 --- a/src/nepi/resources/linux/application.py +++ b/src/nepi/resources/linux/application.py @@ -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. diff --git a/src/nepi/resources/linux/ccn/ccnapplication.py b/src/nepi/resources/linux/ccn/ccnapplication.py index 740d3669..5e4a8321 100644 --- a/src/nepi/resources/linux/ccn/ccnapplication.py +++ b/src/nepi/resources/linux/ccn/ccnapplication.py @@ -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 diff --git a/src/nepi/resources/linux/ccn/ccncontent.py b/src/nepi/resources/linux/ccn/ccncontent.py index 5222d034..e72d6a93 100644 --- a/src/nepi/resources/linux/ccn/ccncontent.py +++ b/src/nepi/resources/linux/ccn/ccncontent.py @@ -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 diff --git a/src/nepi/resources/linux/ccn/ccnr.py b/src/nepi/resources/linux/ccn/ccnr.py index 7ed260c2..f0ca21b3 100644 --- a/src/nepi/resources/linux/ccn/ccnr.py +++ b/src/nepi/resources/linux/ccn/ccnr.py @@ -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 diff --git a/src/nepi/resources/linux/ccn/fibentry.py b/src/nepi/resources/linux/ccn/fibentry.py index cf2c8fe6..46bfae1a 100644 --- a/src/nepi/resources/linux/ccn/fibentry.py +++ b/src/nepi/resources/linux/ccn/fibentry.py @@ -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 diff --git a/src/nepi/resources/linux/interface.py b/src/nepi/resources/linux/interface.py index f5f7025d..f0497e8f 100644 --- a/src/nepi/resources/linux/interface.py +++ b/src/nepi/resources/linux/interface.py @@ -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 diff --git a/src/nepi/resources/linux/node.py b/src/nepi/resources/linux/node.py index 49f5342e..0410b16b 100644 --- a/src/nepi/resources/linux/node.py +++ b/src/nepi/resources/linux/node.py @@ -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. diff --git a/src/nepi/resources/omf/application.py b/src/nepi/resources/omf/application.py index 170410b2..e57b3747 100644 --- a/src/nepi/resources/omf/application.py +++ b/src/nepi/resources/omf/application.py @@ -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') : diff --git a/src/nepi/resources/omf/interface.py b/src/nepi/resources/omf/interface.py index b15be5ea..31513622 100644 --- a/src/nepi/resources/omf/interface.py +++ b/src/nepi/resources/omf/interface.py @@ -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) diff --git a/src/nepi/resources/planetlab/node.py b/src/nepi/resources/planetlab/node.py index 25358a27..9d1b2954 100644 --- a/src/nepi/resources/planetlab/node.py +++ b/src/nepi/resources/planetlab/node.py @@ -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 index 00000000..a60961f6 --- /dev/null +++ b/src/nepi/resources/planetlab/tap.py @@ -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 . +# +# Author: Alina Quereilhac + +from nepi.execution.attribute import Attribute, Flags, Types +from nepi.execution.resource import ResourceManager, clsinit_copy, ResourceState, \ + reschedule_delay +from nepi.resources.linux.application import LinuxApplication +from nepi.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 index 00000000..2c16cb19 --- /dev/null +++ b/src/nepi/resources/planetlab/tun.py @@ -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 . +# +# Author: Alina Quereilhac + +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" + + diff --git a/test/execution/resource.py b/test/execution/resource.py index 365ca08c..5db230a4 100755 --- a/test/execution/resource.py +++ b/test/execution/resource.py @@ -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: diff --git a/test/resources/linux/test_utils.py b/test/lib/test_utils.py similarity index 100% rename from test/resources/linux/test_utils.py rename to test/lib/test_utils.py diff --git a/test/resources/planetlab/plcapi.py b/test/resources/planetlab/plcapi.py index 93e4edc9..85e0b712 100755 --- a/test/resources/planetlab/plcapi.py +++ b/test/resources/planetlab/plcapi.py @@ -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 index 00000000..091babe5 --- /dev/null +++ b/test/resources/planetlab/tap.py @@ -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 . +# +# Author: Alina Quereilhac + +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 index 00000000..392f2e9f --- /dev/null +++ b/test/resources/planetlab/tun.py @@ -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 . +# +# Author: Alina Quereilhac + +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() + -- 2.43.0