From a92223f91edd2eeb48e1ad70aa4c843d778bfe94 Mon Sep 17 00:00:00 2001 From: Alina Quereilhac Date: Tue, 25 Nov 2014 22:00:46 +0100 Subject: [PATCH] Adding NetNSApplication and test --- .../resources/linux/netns/netnsemulation.py | 363 ++++++++++++++++++ src/nepi/resources/netns/netnsapplication.py | 102 +++++ src/nepi/resources/netns/netnsbase.py | 143 +++++++ src/nepi/resources/netns/netnsemulation.py | 42 ++ src/nepi/resources/netns/netnsinterface.py | 28 ++ src/nepi/resources/netns/netnsipv4address.py | 64 +++ src/nepi/resources/netns/netnsnode.py | 48 +++ .../resources/netns/netnsnodeinterface.py | 68 ++++ src/nepi/resources/netns/netnsroute.py | 63 +++ src/nepi/resources/netns/netnsserver.py | 3 +- src/nepi/resources/netns/netnsswitch.py | 63 +++ src/nepi/resources/netns/netnswrapper.py | 2 +- src/nepi/resources/ns3/ns3base.py | 4 +- test/resources/linux/netns/netnsclient.py | 12 +- test/resources/linux/netns/netnsemulation.py | 103 +++++ 15 files changed, 1097 insertions(+), 11 deletions(-) create mode 100644 src/nepi/resources/linux/netns/netnsemulation.py create mode 100644 src/nepi/resources/netns/netnsapplication.py create mode 100644 src/nepi/resources/netns/netnsbase.py create mode 100644 src/nepi/resources/netns/netnsemulation.py create mode 100644 src/nepi/resources/netns/netnsinterface.py create mode 100644 src/nepi/resources/netns/netnsipv4address.py create mode 100644 src/nepi/resources/netns/netnsnode.py create mode 100644 src/nepi/resources/netns/netnsnodeinterface.py create mode 100644 src/nepi/resources/netns/netnsroute.py create mode 100644 src/nepi/resources/netns/netnsswitch.py create mode 100644 test/resources/linux/netns/netnsemulation.py diff --git a/src/nepi/resources/linux/netns/netnsemulation.py b/src/nepi/resources/linux/netns/netnsemulation.py new file mode 100644 index 00000000..c2d65d37 --- /dev/null +++ b/src/nepi/resources/linux/netns/netnsemulation.py @@ -0,0 +1,363 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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.trace import Trace, TraceAttr +from nepi.execution.resource import ResourceManager, clsinit_copy, \ + ResourceState, ResourceFactory, reschedule_delay +from nepi.resources.linux.application import LinuxApplication +from nepi.util.timefuncs import tnow, tdiffsec +from nepi.resources.netns.netnsemulation import NetNSEmulation +from nepi.resources.linux.netns.netnsclient import LinuxNetNSClient + +import os +import time +import threading + +@clsinit_copy +class LinuxNetNSEmulation(LinuxApplication, NetNSEmulation): + _rtype = "LinuxNetNSEmulation" + + @classmethod + def _register_attributes(cls): + verbose = Attribute("verbose", + "True to output debugging info for the client-server communication", + type = Types.Bool, + flags = Flags.Design) + + enable_dump = Attribute("enableDump", + "Enable dumping the remote executed commands to a script " + "in order to later reproduce and debug the experiment", + type = Types.Bool, + default = False, + flags = Flags.Design) + + version = Attribute("version", + "Version of netns to install from nsam repo", + default = "netns-dev", + flags = Flags.Design) + + cls._register_attribute(enable_dump) + cls._register_attribute(verbose) + cls._register_attribute(version) + + def __init__(self, ec, guid): + LinuxApplication.__init__(self, ec, guid) + NetNSEmulation.__init__(self) + + self._client = None + self._home = "netns-emu-%s" % self.guid + self._socket_name = "netns-%s.sock" % os.urandom(4).encode('hex') + + @property + def socket_name(self): + return self._socket_name + + @property + def remote_socket(self): + return os.path.join(self.run_home, self.socket_name) + + def upload_sources(self): + self.node.mkdir(os.path.join(self.node.src_dir, "netnswrapper")) + + # upload wrapper python script + wrapper = os.path.join(os.path.dirname(__file__), "..", "..", "netns", + "netnswrapper.py") + + self.node.upload(wrapper, + os.path.join(self.node.src_dir, "netnswrapper", "netnswrapper.py"), + overwrite = False) + + # upload wrapper debug python script + wrapper_debug = os.path.join(os.path.dirname(__file__), "..", "..", "netns", + "netnswrapper_debug.py") + + self.node.upload(wrapper_debug, + os.path.join(self.node.src_dir, "netnswrapper", "netnswrapper_debug.py"), + overwrite = False) + + # upload server python script + server = os.path.join(os.path.dirname(__file__), "..", "..", "netns", + "netnsserver.py") + + self.node.upload(server, + os.path.join(self.node.src_dir, "netnswrapper", "netnsserver.py"), + overwrite = False) + + # Upload user defined sources + self.node.mkdir(os.path.join(self.node.src_dir, "netns")) + src_dir = os.path.join(self.node.src_dir, "netns") + + super(LinuxNetNSEmulation, self).upload_sources(src_dir = src_dir) + + def upload_extra_sources(self, sources = None, src_dir = None): + return super(LinuxNetNSEmulation, self).upload_sources( + sources = sources, + src_dir = src_dir) + + def upload_start_command(self): + command = self.get("command") + env = self.get("env") + + # We want to make sure the emulator is 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 ) + env = self.replace_paths(env) + command = self.replace_paths(command) + + shfile = os.path.join(self.app_home, "start.sh") + self.node.upload_command(command, + shfile = shfile, + env = env, + overwrite = True) + + # Run the wrapper + self._run_in_background() + + # Wait until the remote socket is created + self.wait_remote_socket() + + def do_deploy(self): + if not self.node or self.node.state < ResourceState.READY: + self.debug("---- RESCHEDULING DEPLOY ---- node state %s " % self.node.state ) + + # ccnd needs to wait until node is deployed and running + self.ec.schedule(reschedule_delay, self.deploy) + else: + if not self.get("command"): + self.set("command", self._start_command) + + if not self.get("depends"): + self.set("depends", self._dependencies) + + if self.get("sources"): + sources = self.get("sources") + source = sources.split(" ")[0] + basename = os.path.basename(source) + version = ( basename.strip().replace(".tar.gz", "") + .replace(".tar","") + .replace(".gz","") + .replace(".zip","") ) + + self.set("version", version) + self.set("sources", source) + + if not self.get("build"): + self.set("build", self._build) + + if not self.get("env"): + self.set("env", self._environment) + + self.do_discover() + self.do_provision() + + # Create client + self._client = LinuxNetNSClient(self) + + self.set_ready() + + def do_release(self): + self.info("Releasing resource") + + tear_down = self.get("tearDown") + if tear_down: + self.node.execute(tear_down) + + self.do_stop() + self._client.shutdown() + LinuxApplication.do_stop(self) + + super(LinuxApplication, self).do_release() + + @property + def _start_command(self): + command = [] + + #command.append("sudo") + command.append("PYTHONPATH=$PYTHONPATH:${SRC}/netnswrapper/") + command.append("python ${SRC}/netnswrapper/netnsserver.py -S %s" % \ + os.path.basename(self.remote_socket) ) + + if self.get("enableDump"): + command.append("-D") + + if self.get("verbose"): + command.append("-v") + + command = " ".join(command) + return command + + @property + def _dependencies(self): + if self.node.use_rpm: + return ( " python python-devel mercurial unzip bridge-utils iproute") + elif self.node.use_deb: + return ( " python python-dev mercurial unzip bridge-utils iproute") + return "" + + @property + def netns_repo(self): + return "http://nepi.inria.fr/code/netns" + + @property + def netns_version(self): + version = self.get("version") + return version or "dev" + + @property + def python_unshare_repo(self): + return "http://nepi.inria.fr/code/python-unshare" + + @property + def python_unshare_version(self): + return "dev" + + @property + def python_passfd_repo(self): + return "http://nepi.inria.fr/code/python-passfd" + + @property + def python_passfd_version(self): + return "dev" + + @property + def netns_src(self): + location = "${SRC}/netns/%(version)s" \ + % { + "version": self.netns_version, + } + + return location + + @property + def python_unshare_src(self): + location = "${SRC}/python_unshare/%(version)s" \ + % { + "version": self.python_unshare_version, + } + + return location + + @property + def python_passfd_src(self): + location = "${SRC}/python_passfd/%(version)s" \ + % { + "version": self.python_passfd_version, + } + + return location + + def clone_command(self, name, repo, src): + clone_cmd = ( + # Test if alredy cloned + " ( " + " ( " + " ( test -d %(src)s ) " + " && echo '%(name)s binaries found, nothing to do'" + " ) " + " ) " + " || " + # clone source code + " ( " + " mkdir -p %(src)s && " + " hg clone %(repo)s %(src)s" + " ) " + ) % { + "repo": repo, + "src": src, + "name": name, + } + + return clone_cmd + + @property + def _build(self): + netns_clone = self.clone_command("netns", self.netns_repo, + self.netns_src) + python_unshare_clone = self.clone_command("python_unshare", + self.python_unshare_repo, self.python_unshare_src) + python_passfd_clone = self.clone_command("python_passfd", + self.python_passfd_repo, self.python_passfd_src) + + build_cmd = ( + # Netns installation + "( %(netns_clone)s )" + " && " + "( %(python_unshare_clone)s )" + " && " + "( %(python_passfd_clone)s )" + ) % { + "netns_clone": netns_clone, + "python_unshare_clone": python_unshare_clone, + "python_passfd_clone": python_passfd_clone, + } + + return build_cmd + + @property + def _environment(self): + env = [] + env.append("PYTHONPATH=$PYTHONPAH:%(netns_src)s/src/:%(python_unshare_src)s/src:%(python_passfd_src)s/src}" % { + "netns_src": self.netns_src, + "python_unshare_src": self.python_unshare_src, + "python_passfd_src": self.python_passfd_src, + }) + + return " ".join(env) + + def replace_paths(self, command): + """ + Replace all special path tags with shell-escaped actual paths. + """ + return ( command + .replace("${USR}", self.node.usr_dir) + .replace("${LIB}", self.node.lib_dir) + .replace("${BIN}", self.node.bin_dir) + .replace("${SRC}", self.node.src_dir) + .replace("${SHARE}", self.node.share_dir) + .replace("${EXP}", self.node.exp_dir) + .replace("${EXP_HOME}", self.node.exp_home) + .replace("${APP_HOME}", self.app_home) + .replace("${RUN_HOME}", self.run_home) + .replace("${NODE_HOME}", self.node.node_home) + .replace("${HOME}", self.node.home_dir) + ) + + def valid_connection(self, guid): + # TODO: Validate! + return True + + def wait_remote_socket(self): + """ Waits until the remote socket is created + """ + command = " [ -e %s ] && echo 'DONE' " % self.remote_socket + + for i in xrange(200): + (out, err), proc = self.node.execute(command, retry = 1, + with_lock = True) + + if out.find("DONE") > -1: + break + else: + raise RuntimeError("Remote socket not found at %s" % \ + self.remote_socket) + + diff --git a/src/nepi/resources/netns/netnsapplication.py b/src/nepi/resources/netns/netnsapplication.py new file mode 100644 index 00000000..b7904166 --- /dev/null +++ b/src/nepi/resources/netns/netnsapplication.py @@ -0,0 +1,102 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 clsinit_copy +from nepi.resources.netns.netnsbase import NetNSBase +from nepi.execution.resource import clsinit_copy, ResourceState, \ + reschedule_delay + +import shlex + +@clsinit_copy +class NetNSApplication(NetNSBase): + _rtype = "netns::Application" + + def __init__(self, ec, guid): + super(NetNSApplication, self).__init__(ec, guid) + self._traces = dict() + + @classmethod + def _register_attributes(cls): + command = Attribute("command", "Command to execute", flags=Flags.Design) + cls._register_attribute(command) + + @property + def emulation(self): + return self.node.emulation + + @property + def node(self): + from nepi.resources.netns.netnsnode import NetNSNode + node = self.get_connected(NetNSNode.get_rtype()) + + if not node: + msg = "Route not connected to Node!!" + self.error(msg) + raise RuntimeError, msg + + return node[0] + + @property + def _rms_to_wait(self): + return [self.node] + + def do_start(self): + if self.simulation.state < ResourceState.STARTED: + self.debug("---- RESCHEDULING START ----" ) + self.ec.schedule(reschedule_delay, self.start) + else: + self._configure_traces() + + command = shlex.split(self.get("command")) + stdout = self._traces["stdout"] + stderr = self._traces["stderr"] + self._uuid = self.emulation.invoke(self.node.uuid, + "Popen", command, stdout = stdout, + stderr = stderr) + + super(NetNSApplication, self).do_start() + self._start_time = self.emulation.start_time + + def _configure_traces(self): + stdout = "%s/%d.stdout " % (self.emulation.run_home, self.pid) + stderr = "%s/%d.stderr " % (self.emulation.run_home, self.pid) + self._trace_filename["stdout"] = stdout + self._trace_filename["stderr"] = stderr + self._traces["stdout"] = self.emulation.create("open", stdout, "w") + self._traces["stderr"] = self.emulation.create("open", stderr, "w") + + @property + def state(self): + if self._state == ResourceState.STARTED: + retcode = self.emulation.invoke(self.uuid, "poll") + + if retcode: + if ret == 0: + self.set_stopped() + else: + out = "" + msg = " Failed to execute command '%s'" % self.get("command") + err = self.trace("stderr") + self.error(msg, out, err) + self.do_fail() + + return self._state + diff --git a/src/nepi/resources/netns/netnsbase.py b/src/nepi/resources/netns/netnsbase.py new file mode 100644 index 00000000..32214600 --- /dev/null +++ b/src/nepi/resources/netns/netnsbase.py @@ -0,0 +1,143 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 ResourceManager, clsinit_copy, \ + ResourceState, reschedule_delay +from nepi.execution.attribute import Flags +from nepi.execution.trace import TraceAttr + +@clsinit_copy +class NetNSBase(ResourceManager): + _rtype = "abstract::netns::Object" + _backend_type = "netns" + + def __init__(self, ec, guid): + super(NetNSBase, self).__init__(ec, guid) + self._uuid = None + self._connected = set() + self._trace_filename = dict() + + @property + def connected(self): + return self._connected + + @property + def uuid(self): + return self._uuid + + def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0): + filename = self._trace_filename.get(name) + if not filename: + self.error("Can not resolve trace %s. Did you enabled it?" % name) + return "" + + return self.emulation.trace(filename, attr, block, offset) + + @property + def _rms_to_wait(self): + """ Returns the collection of RMs that this RM needs to + wait for before start + + This method should be overriden to wait for other + objects to be deployed before proceeding with the deployment + + """ + raise RuntimeError, "No dependencies defined!" + + def _instantiate_object(self): + pass + + def _wait_rms(self): + rms = set() + for rm in self._rms_to_wait: + if rm is not None: + rms.add(rm) + + """ Returns True if dependent RMs are not yer READY, False otherwise""" + for rm in rms: + if rm.state < ResourceState.READY: + return True + return False + + def do_provision(self): + self._instantiate_object() + + self.info("Provisioning finished") + + super(NetNSBase, self).do_provision() + + def do_deploy(self): + if self._wait_rms(): + self.debug("---- RESCHEDULING DEPLOY ----" ) + self.ec.schedule(reschedule_delay, self.deploy) + else: + self.do_discover() + self.do_provision() + + self.set_ready() + + def do_start(self): + if self.state == ResourceState.READY: + # No need to do anything, simulation.Run() will start every object + self.info("Starting") + self.set_started() + else: + msg = " Failed " + self.error(msg, out, err) + raise RuntimeError, msg + + def do_stop(self): + if self.state == ResourceState.STARTED: + # No need to do anything, simulation.Destroy() will stop every object + self.set_stopped() + + @property + def state(self): + return self._state + + def get(self, name): + #flags = Flags.NoWrite | Flags.NoRead | Flags.Design + flags = Flags.Design + if self.state in [ResourceState.READY, ResourceState.STARTED] \ + and not self.has_flag(name, flags): + return self.emulation.emu_get(self.uuid, name) + + value = super(NetNSBase, self).get(name) + if name != "critical": + print name, value, "lalal" + return value + + def set(self, name, value): + #flags = Flags.NoWrite | Flags.NoRead | Flags.Design + flags = Flags.Design + if self.has_flag(name, flags): + out = err = "" + msg = " Cannot change Design only attribue %s" % name + self.error(msg, out, err) + return + + if self.state in [ResourceState.READY, ResourceState.STARTED]: + self.emulation.emu_set(self.uuid, name, value) + + value = super(NetNSBase, self).set(name, value) + if name != "critical": + print name, value, "IEEEEEEEE:" + + return value + diff --git a/src/nepi/resources/netns/netnsemulation.py b/src/nepi/resources/netns/netnsemulation.py new file mode 100644 index 00000000..db548b6c --- /dev/null +++ b/src/nepi/resources/netns/netnsemulation.py @@ -0,0 +1,42 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 + +class NetNSEmulation(object): + @property + def client(self): + return self._client + + def create(self, *args, **kwargs): + return self.client.create(*args, **kwargs) + + def invoke(self, *args, **kwargs): + return self.client.invoke(*args, **kwargs) + + def emu_set(self, *args, **kwargs): + return self.client.set(*args, **kwargs) + + def emu_get(self, *args, **kwargs): + return self.client.get(*args, **kwargs) + + def flush(self, *args, **kwargs): + return self.client.flush(*args, **kwargs) + + def shutdown(self, *args, **kwargs): + return self.client.shutdown(*args, **kwargs) + diff --git a/src/nepi/resources/netns/netnsinterface.py b/src/nepi/resources/netns/netnsinterface.py new file mode 100644 index 00000000..2b3a371b --- /dev/null +++ b/src/nepi/resources/netns/netnsinterface.py @@ -0,0 +1,28 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 clsinit_copy +from nepi.resources.netns.netnsbase import NetNSBase + +@clsinit_copy +class NetNSInterface(NetNSBase): + _rtype = "asbtract::netns::NodeInterface" + + diff --git a/src/nepi/resources/netns/netnsipv4address.py b/src/nepi/resources/netns/netnsipv4address.py new file mode 100644 index 00000000..144cfe27 --- /dev/null +++ b/src/nepi/resources/netns/netnsipv4address.py @@ -0,0 +1,64 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 clsinit_copy +from nepi.resources.netns.netnsbase import NetNSBase + +@clsinit_copy +class NetNSIPv4Address(NetNSBase): + _rtype = "netns::IPv4Address" + + @classmethod + def _register_attributes(cls): + ip = Attribute("ip", "IPv4 address", flags=Flags.Design) + prefix = Attribute("prefix", "IPv4 prefix", flags=Flags.Design) + + cls._register_attribute(ip) + cls._register_attribute(prefix) + + @property + def emulation(self): + return self.node.emulation + + @property + def node(self): + return self.interface.node + + @property + def interface(self): + from nepi.resources.netns.netnsinterface import NetNSInterface + interface = self.get_connected(NetNSInterface.get_rtype()) + + if not interface: + msg = "IPv4Address not connected to Interface!!" + self.error(msg) + raise RuntimeError, msg + + return interface[0] + + @property + def _rms_to_wait(self): + return [self.interface] + + def _instantiate_object(self): + self._uuid = self.emulation.invoke(self.interface.uuid, "add_v4_address", + self.get("ip"), self.get("prefix")) + + diff --git a/src/nepi/resources/netns/netnsnode.py b/src/nepi/resources/netns/netnsnode.py new file mode 100644 index 00000000..47382659 --- /dev/null +++ b/src/nepi/resources/netns/netnsnode.py @@ -0,0 +1,48 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 clsinit_copy +from nepi.resources.netns.netnsbase import NetNSBase + +@clsinit_copy +class NetNSNode(NetNSBase): + _rtype = "netns::Node" + + @property + def emulation(self): + from nepi.resources.netns.netnsemulation import NetNSEmulation + + for guid in self.connections: + rm = self.ec.get_resource(guid) + if isinstance(rm, NetNSEmulation): + return rm + + msg = "Node not connected to Emulation" + self.error(msg) + raise RuntimeError, msg + + @property + def _rms_to_wait(self): + return [self.emulation] + + def _instantiate_object(self): + self._uuid = self.emulation.create("Node") + + diff --git a/src/nepi/resources/netns/netnsnodeinterface.py b/src/nepi/resources/netns/netnsnodeinterface.py new file mode 100644 index 00000000..da38d240 --- /dev/null +++ b/src/nepi/resources/netns/netnsnodeinterface.py @@ -0,0 +1,68 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 clsinit_copy +from nepi.resources.netns.netnsinterface import NetNSInterface + +@clsinit_copy +class NetNSNodeInterface(NetNSInterface): + _rtype = "netns::NodeInterface" + + @classmethod + def _register_attributes(cls): + up = Attribute("up", "Interface up", + default = True, + type = Types.Bool) + + cls._register_attribute(up) + + @property + def emulation(self): + return self.node.emulation + + @property + def node(self): + from nepi.resources.netns.netnsnode import NetNSNode + node = self.get_connected(NetNSNode.get_rtype()) + + if not node: + msg = "Route not connected to Node!!" + self.error(msg) + raise RuntimeError, msg + + return node[0] + + @property + def switch(self): + from nepi.resources.netns.netnsswitch import NetNSSwitch + switch = self.get_connected(NetNSSwitch.get_rtype()) + if switch: return switch[0] + return None + + @property + def _rms_to_wait(self): + return [self.node, self.switch] + + def _instantiate_object(self): + self._uuid = self.emulation.invoke(self.node.uuid, "add_if") + self.emulation.invoke(self.switch.uuid, "connect", self.uuid) + self.emulation.emu_set(self.uuid, "up", self.get("up")) + + diff --git a/src/nepi/resources/netns/netnsroute.py b/src/nepi/resources/netns/netnsroute.py new file mode 100644 index 00000000..a3ca6108 --- /dev/null +++ b/src/nepi/resources/netns/netnsroute.py @@ -0,0 +1,63 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 clsinit_copy +from nepi.resources.netns.netnsbase import NetNSBase + +@clsinit_copy +class NetNSIPv4Route(NetNSBase): + _rtype = "netns::IPv4Route" + + @classmethod + def _register_attributes(cls): + network = Attribute("network", "Network address", flags=Flags.Design) + prefix = Attribute("prefix", "IP prefix length", flags=Flags.Design) + nexthop = Attribute("nexthop", "Nexthop IP", flags=Flags.Design) + + cls._register_attribute(network) + cls._register_attribute(prefix) + cls._register_attribute(nexthop) + + @property + def emulation(self): + return self.node.emulation + + @property + def node(self): + from nepi.resources.netns.netnsnode import NetNSNode + node = self.get_connected(NetNSNode.get_rtype()) + + if not node: + msg = "Route not connected to Node!!" + self.error(msg) + raise RuntimeError, msg + + return node[0] + + @property + def _rms_to_wait(self): + return [self.node] + + def _instantiate_object(self): + self._uuid = self.emulation.invoke(self.device.uuid, "add_route", + prefix=self.get("network"), prefix_len=self.get("prefix"), + nexthop=self.get("nexthop")) + + diff --git a/src/nepi/resources/netns/netnsserver.py b/src/nepi/resources/netns/netnsserver.py index efa5e155..85853a13 100644 --- a/src/nepi/resources/netns/netnsserver.py +++ b/src/nepi/resources/netns/netnsserver.py @@ -148,8 +148,7 @@ def get_options(): (options, args) = parser.parse_args() - return (options.socket_name, options.verbose, options.ns_log, - options.enable_dump) + return (options.socket_name, options.verbose, options.enable_dump) def run_server(socket_name, level = logging.INFO, enable_dump = False): diff --git a/src/nepi/resources/netns/netnsswitch.py b/src/nepi/resources/netns/netnsswitch.py new file mode 100644 index 00000000..b7e28290 --- /dev/null +++ b/src/nepi/resources/netns/netnsswitch.py @@ -0,0 +1,63 @@ +# +# NEPI, a framework to manage network experiments +# Copyright (C) 2014 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 clsinit_copy +from nepi.resources.netns.netnsbase import NetNSBase + +@clsinit_copy +class NetNSSwitch(NetNSBase): + _rtype = "netns::Switch" + + @classmethod + def _register_attributes(cls): + up = Attribute("up", "Switch up", + default = True, + type = Types.Bool) + + cls._register_attribute(up) + + @property + def emulation(self): + return self.node.emulation + + @property + def node(self): + return self.interface.node + + @property + def interface(self): + from nepi.resources.netns.netnsinterface import NetNSInterface + interface = self.get_connected(NetNSInterface.get_rtype()) + + if not interface: + msg = "Switch not connected to any Interface!!" + self.error(msg) + raise RuntimeError, msg + + return interface[0] + + @property + def _rms_to_wait(self): + return [self.emulation] + + def _instantiate_object(self): + self._uuid = self.emulation.create("Switch") + self.emulation.emu_set(self.uuid, "up", self.get("up")) + diff --git a/src/nepi/resources/netns/netnswrapper.py b/src/nepi/resources/netns/netnswrapper.py index eff19f8b..74bd69a7 100644 --- a/src/nepi/resources/netns/netnswrapper.py +++ b/src/nepi/resources/netns/netnswrapper.py @@ -18,7 +18,6 @@ # Author: Alina Quereilhac import logging -import netns import time import os import sys @@ -54,6 +53,7 @@ class NetNSWrapper(object): def create(self, clazzname, *args): """ This method should be used to construct netns objects """ + import netns if clazzname not in ['open'] and not hasattr(netns, clazzname): msg = "Type %s not supported" % (clazzname) diff --git a/src/nepi/resources/ns3/ns3base.py b/src/nepi/resources/ns3/ns3base.py index f86bea77..77d68c7b 100644 --- a/src/nepi/resources/ns3/ns3base.py +++ b/src/nepi/resources/ns3/ns3base.py @@ -128,14 +128,14 @@ class NS3Base(ResourceManager): self.info("Starting") self.set_started() else: - msg = " Failed " + msg = "Failed" self.error(msg, out, err) raise RuntimeError, msg def do_stop(self): if self.state == ResourceState.STARTED: # No need to do anything, simulation.Destroy() will stop every object - self.info("Stopping command '%s'" % command) + self.info("Stopping") self.set_stopped() @property diff --git a/test/resources/linux/netns/netnsclient.py b/test/resources/linux/netns/netnsclient.py index 447e92c4..4f1bdf85 100644 --- a/test/resources/linux/netns/netnsclient.py +++ b/test/resources/linux/netns/netnsclient.py @@ -129,10 +129,10 @@ class LinuxNetNSClientTest(unittest.TestCase): # nexthop = '10.0.0.2') #n3.add_route(prefix = '10.0.0.0', prefix_len = 24, # nexthop = '10.0.1.1') - client.invoke(n1, "add_route", prefix = "10.0.1.0", prefix_len = 24, - nexthop = "10.0.0.2") - client.invoke(n3, "add_route", prefix = "10.0.0.0", prefix_len = 24, - nexthop = "10.0.1.1") + client.invoke(n1, "add_route", prefix="10.0.1.0", prefix_len=24, + nexthop="10.0.0.2") + client.invoke(n3, "add_route", prefix "10.0.0.0", prefix_len=24, + nexthop="10.0.1.1") ## launch pings #a1 = n1.Popen(['ping', '-qc1', '10.0.1.2'], stdout = null) @@ -141,8 +141,8 @@ class LinuxNetNSClientTest(unittest.TestCase): path2 = "/tmp/netns_file2" file1 = client.create("open", path1, "w") file2 = client.create("open", path2, "w") - a1 = client.invoke(n1, "Popen", ["ping", "-qc1", "10.0.1.2"], stdout = file1) - a2 = client.invoke(n3, "Popen", ["ping", "-qc1", "10.0.0.1"], stdout = file2) + a1 = client.invoke(n1, "Popen", ["ping", "-qc1", "10.0.1.2"], stdout=file1) + a2 = client.invoke(n3, "Popen", ["ping", "-qc1", "10.0.0.1"], stdout=file2) ## get ping status p1 = None diff --git a/test/resources/linux/netns/netnsemulation.py b/test/resources/linux/netns/netnsemulation.py new file mode 100644 index 00000000..d6d0b449 --- /dev/null +++ b/test/resources/linux/netns/netnsemulation.py @@ -0,0 +1,103 @@ +#!/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.execution.trace import TraceAttr + +from test_utils import skipIfNotAlive + +import os +import time +import unittest + +def add_node(ec, emu, addr, prefix): + node = ec.register_resource("netns::Node") + ec.register_connection(node, emu) + + iface = ec.register_resource("netns::NodeInterface") + ec.register_connection(iface, node) + + ip = ec.register_resource("netns::IPv4Address") + ec.set(ip, "ip", addr) + ec.set(ip, "prefix", prefix) + ec.register_connection(ip, iface) + + print ec.get(ip, "ip"), addr + print ec.get(ip, "prefix"), prefix + + return node, iface + +class LinuxNetNSEmulationTest(unittest.TestCase): + def setUp(self): + self.fedora_host = "mimas.inria.fr" + self.fedora_user = "aquereil" + self.fedora_identity = "%s/.ssh/id_rsa" % (os.environ['HOME']) + + @skipIfNotAlive + def t_ping(self, host, user = None, identity = None): + ec = ExperimentController(exp_id = "test-netns-p2p-ping") + + node = ec.register_resource("LinuxNode") + if host == "localhost": + ec.set(node, "hostname", "localhost") + else: + ec.set(node, "hostname", host) + ec.set(node, "username", user) + ec.set(node, "identity", identity) + + ec.set(node, "cleanProcesses", True) + #ec.set(node, "cleanHome", True) + + emu = ec.register_resource("LinuxNetNSEmulation") + ec.set(emu, "verbose", True) + ec.register_connection(emu, node) + + netnode1, iface1 = add_node(ec, emu, "10.0.0.1", "24") + netnode2, iface2 = add_node(ec, emu, "10.0.0.2", "24") + + switch = ec.register_resource("netns::Switch") + ec.register_connection(iface1, switch) + ec.register_connection(iface2, switch) + + ping = ec.register_resource("netns::Application") + ec.set(ping, "command", "10.0.0.2") + ec.register_connection(ping, netnode1) + + ec.deploy() + + ec.wait_finished([ping]) + + stdout = ec.trace(ping, "stdout") + + expected = "20 packets transmitted, 20 received, 0% packet loss" + self.assertTrue(stdout.find(expected) > -1) + + ec.shutdown() + + def ztest_ping_fedora(self): + self.t_ping(self.fedora_host, self.fedora_user, self.fedora_identity) + + def test_ping_local(self): + self.t_ping("localhost") + + +if __name__ == '__main__': + unittest.main() + -- 2.43.0