diff options
| -rw-r--r-- | AUTHORS.txt | 1 | ||||
| -rw-r--r-- | README.md | 14 | ||||
| -rwxr-xr-x | examples/converged-operator-network.py | 2 | ||||
| -rwxr-xr-x | examples/docker-ouroboros.py | 65 | ||||
| -rwxr-xr-x | examples/example.py | 2 | ||||
| -rwxr-xr-x | examples/mouse.py | 2 | ||||
| -rwxr-xr-x | examples/snake.py | 2 | ||||
| -rwxr-xr-x | examples/two-layers.py | 6 | ||||
| -rwxr-xr-x | examples/vpn.py | 2 | ||||
| -rw-r--r-- | rumba/executors/__init__.py | 26 | ||||
| -rw-r--r-- | rumba/executors/docker.py | 80 | ||||
| -rw-r--r-- | rumba/executors/local.py | 55 | ||||
| -rw-r--r-- | rumba/executors/ssh.py | 64 | ||||
| -rw-r--r-- | rumba/model.py | 130 | ||||
| -rw-r--r-- | rumba/prototypes/ouroboros.py | 49 | ||||
| -rw-r--r-- | rumba/ssh_support.py | 4 | ||||
| -rw-r--r-- | rumba/testbeds/dockertb.py | 190 | ||||
| -rw-r--r-- | rumba/testbeds/emulab.py | 10 | ||||
| -rw-r--r-- | rumba/testbeds/jfed.py | 5 | ||||
| -rw-r--r-- | rumba/testbeds/local.py (renamed from rumba/testbeds/faketestbed.py) | 7 | ||||
| -rw-r--r-- | rumba/testbeds/qemu.py | 16 | ||||
| -rwxr-xr-x | setup.py | 3 | ||||
| -rwxr-xr-x | tools/democonf2rumba.py | 6 | 
23 files changed, 612 insertions, 129 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt index 55a1c91..0e929fb 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -2,3 +2,4 @@ Sander Vrijders <sander.vrijders@intec.ugent.be>  Dimitri Staessens <dimitri.staessens@ugent.be>  Vincenzo Maffione <v.maffione@nextworks.it>  Marco Capitani <m.capitani@nextworks.it> +Nick Aerts <nick.aerts@ugent.be>
\ No newline at end of file @@ -160,6 +160,20 @@ Example scripts can be found in the examples/ folder.     for the previous commands, without changing the user (e.g. using su     or sudo). + * [Docker](https://www.docker.com/) is a container runtime environment. + +   To use the Docker testbed the Docker software needs to be installed, see +   [Install Docker](https://docs.docker.com/install/) and complete +   [Post-installation steps for Linux](https://docs.docker +   .com/install/linux/linux-postinstall/) + +   To use a Docker testbed you can for example use: + +        tb = docker.Testbed(exp_name = "ouroboros", +                            base_image = "ouroborosrumba/prototype") + +   This will pull the latest ouroboros image from Docker Hub. +  ## Accessing nodes after swap-in     To access a node once the experiment swapped in, use the following diff --git a/examples/converged-operator-network.py b/examples/converged-operator-network.py index 3b80746..8ed79ed 100755 --- a/examples/converged-operator-network.py +++ b/examples/converged-operator-network.py @@ -10,7 +10,7 @@ from rumba.utils import *  # import testbed plugins  import rumba.testbeds.emulab as emulab  import rumba.testbeds.jfed as jfed -import rumba.testbeds.faketestbed as fake +import rumba.testbeds.local as local  import rumba.testbeds.qemu as qemu  # import prototype plugins diff --git a/examples/docker-ouroboros.py b/examples/docker-ouroboros.py new file mode 100755 index 0000000..1c6009c --- /dev/null +++ b/examples/docker-ouroboros.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +# An example script using the rumba package + +from rumba.model import * +from rumba.utils import ExperimentManager + +# import testbed plugins +import rumba.testbeds.dockertb as docker + +# import prototype plugins +import rumba.prototypes.ouroboros as our + +import argparse +import sys + + +description = "Script to create an ouroboros" + +argparser = argparse.ArgumentParser(description = description) +argparser.add_argument('-n', '--nodes', type = int, default = '10', +                       help = "Total number of nodes") + +args = argparser.parse_args() + +log.set_logging_level('DEBUG') + +n01 = NormalDIF("n01") + +if (args.nodes < 3): +    print("The ouroboros must be longer than 2 nodes") +    sys.exit(-1) + +nodes = [] + +shim_prev = None +for i in range(0, args.nodes): +    shim = ShimEthDIF("e" + str(i)) + +    if shim_prev == None and shim != None: +        node = Node("node" + str(i), difs = [n01, shim], +                    dif_registrations = {n01 : [shim]}) +    elif shim_prev != None and shim != None: +        node = Node("node" + str(i), difs = [n01, shim, shim_prev], +                    dif_registrations = {n01 : [shim, shim_prev]}) +    else: +        node = Node("node" + str(i), difs = [n01, shim_prev], +                    dif_registrations = {n01 : [shim_prev]}) + +    shim_prev = shim +    nodes.append(node) + +nodes[0].add_dif(shim_prev) +nodes[0].add_dif_registration(n01, shim_prev) + +tb = docker.Testbed(exp_name = "ouroboros", +                    base_image = "ouroborosrumba/prototype") + +exp = our.Experiment(tb, nodes = nodes) + +print(exp) + +with ExperimentManager(exp): +    exp.swap_in() +    exp.bootstrap_prototype() diff --git a/examples/example.py b/examples/example.py index eee966b..62d5a53 100755 --- a/examples/example.py +++ b/examples/example.py @@ -9,7 +9,7 @@ from rumba.storyboard import *  # import testbed plugins  import rumba.testbeds.emulab as emulab  import rumba.testbeds.jfed as jfed -import rumba.testbeds.faketestbed as fake +import rumba.testbeds.local as local  import rumba.testbeds.qemu as qemu  # import prototype plugins diff --git a/examples/mouse.py b/examples/mouse.py index 026b3bc..16fdf49 100755 --- a/examples/mouse.py +++ b/examples/mouse.py @@ -8,7 +8,7 @@ from rumba.utils import ExperimentManager  # import testbed plugins  import rumba.testbeds.emulab as emulab  import rumba.testbeds.jfed as jfed -import rumba.testbeds.faketestbed as fake +import rumba.testbeds.local as local  import rumba.testbeds.qemu as qemu  # import prototype plugins diff --git a/examples/snake.py b/examples/snake.py index 6f9c48d..3d76797 100755 --- a/examples/snake.py +++ b/examples/snake.py @@ -8,7 +8,7 @@ from rumba.utils import ExperimentManager  # import testbed plugins  import rumba.testbeds.emulab as emulab  import rumba.testbeds.jfed as jfed -import rumba.testbeds.faketestbed as fake +import rumba.testbeds.local as local  import rumba.testbeds.qemu as qemu  # import prototype plugins diff --git a/examples/two-layers.py b/examples/two-layers.py index c375088..3f50037 100755 --- a/examples/two-layers.py +++ b/examples/two-layers.py @@ -8,7 +8,7 @@ from rumba.storyboard import *  # import testbed plugins  import rumba.testbeds.emulab as emulab  import rumba.testbeds.jfed as jfed -import rumba.testbeds.faketestbed as fake +import rumba.testbeds.local as local  import rumba.testbeds.qemu as qemu  # import prototype plugins @@ -59,6 +59,6 @@ with ExperimentManager(exp):      exp.swap_in()      exp.bootstrap_prototype()      sb = StoryBoard(experiment=exp, duration=15, servers=[]) -    sb.run_command(7.5, a, 'echo "7.5 secs in. We are at $(hostname)"') -    sb.run_command(12, b, 'echo "12 secs in. We are at $(hostname)"') +    sb.schedule_command(7.5, a, 'echo "7.5 secs in. We are at $(hostname)"') +    sb.schedule_command(12, b, 'echo "12 secs in. We are at $(hostname)"')      sb.start() diff --git a/examples/vpn.py b/examples/vpn.py index 28de09e..6aa8db6 100755 --- a/examples/vpn.py +++ b/examples/vpn.py @@ -8,7 +8,7 @@ from rumba.utils import ExperimentManager  # import testbed plugins  import rumba.testbeds.emulab as emulab  import rumba.testbeds.jfed as jfed -import rumba.testbeds.faketestbed as fake +import rumba.testbeds.local as local  import rumba.testbeds.qemu as qemu  # import prototype plugins diff --git a/rumba/executors/__init__.py b/rumba/executors/__init__.py new file mode 100644 index 0000000..58fcfb5 --- /dev/null +++ b/rumba/executors/__init__.py @@ -0,0 +1,26 @@ +# +# A library to manage ARCFIRE experiments +# +#    Copyright (C) 2017 Nextworks S.r.l. +#    Copyright (C) 2017 imec +# +#    Sander Vrijders   <sander.vrijders@ugent.be> +#    Dimitri Staessens <dimitri.staessens@ugent.be> +#    Vincenzo Maffione <v.maffione@nextworks.it> +#    Marco Capitani    <m.capitani@nextworks.it> +#    Nick Aerts        <nick.aerts@ugent.be> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# diff --git a/rumba/executors/docker.py b/rumba/executors/docker.py new file mode 100644 index 0000000..e1e1480 --- /dev/null +++ b/rumba/executors/docker.py @@ -0,0 +1,80 @@ +# +# A library to manage ARCFIRE experiments +# +#    Copyright (C) 2017 Nextworks S.r.l. +#    Copyright (C) 2017 imec +# +#    Sander Vrijders   <sander.vrijders@ugent.be> +#    Dimitri Staessens <dimitri.staessens@ugent.be> +#    Vincenzo Maffione <v.maffione@nextworks.it> +#    Marco Capitani    <m.capitani@nextworks.it> +#    Nick Aerts        <nick.aerts@ugent.be> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +from rumba import model as mod + +import tempfile +import tarfile + +from rumba import log + +logger = log.get_logger(__name__) + + +class DockerException(Exception): +    pass + + +class DockerExecutor(mod.Executor): +    def __init__(self, testbed): +        self.testbed = testbed +        self.running_containers = testbed.running_containers + +    def execute_command(self, node, command, sudo=False, time_out=3): +        logger.debug("%s >> %s" % (node.name, command)) + +        c, o = self.running_containers[node.name].exec_run(["sh", "-c", +                                                            command]) +        if c: +            raise DockerException('A remote command returned an error. ' +                                  'Output:\n\n\t' + o.decode("utf-8")) + +        return o.decode("utf-8") + +    def fetch_file(self, node, path, destination, as_root=False): +        if not path.startswith("/"): +            workingdir = self.running_containers[node.name].attrs["Config"][ +                "WorkingDir"] +            path = os.path.join(workingdir, path) + +        try: +            with tempfile.NamedTemporaryFile() as tmp: +                archive, _ = self.running_containers[node.name].get_archive( +                    path) + +                for c in archive: +                    tmp.write(c) + +                tmp.seek(0) + +                tarfile.TarFile(fileobj=tmp, mode='r').extract( +                    path.basename(path), destination) +        except: +            logger.error("Error when extracting %s" % path) + +    def copy_file(self, node, path, destination): +        pass diff --git a/rumba/executors/local.py b/rumba/executors/local.py new file mode 100644 index 0000000..9f3f484 --- /dev/null +++ b/rumba/executors/local.py @@ -0,0 +1,55 @@ +# +# A library to manage ARCFIRE experiments +# +#    Copyright (C) 2017 Nextworks S.r.l. +#    Copyright (C) 2017 imec +# +#    Sander Vrijders   <sander.vrijders@ugent.be> +#    Dimitri Staessens <dimitri.staessens@ugent.be> +#    Vincenzo Maffione <v.maffione@nextworks.it> +#    Marco Capitani    <m.capitani@nextworks.it> +#    Nick Aerts        <nick.aerts@ugent.be> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +from rumba import model as mod + +import subprocess + +from rumba import log + +logger = log.get_logger(__name__) + + +class LocalExecutor(mod.Executor): +    def __init__(self, testbed): +        self.testbed = testbed + +    def execute_command(self, node, cmd, sudo=False, time_out=3): +        try: +            logger.debug("%s >> %s" % (node.name, cmd)) +            subprocess.check_call(cmd.split(' ')) +        except subprocess.CalledProcessError as e: +            logger.error("Return code was " + str(e.returncode)) +            raise + +    def fetch_file(self, node, path, destination, as_root=False): +        logger.error("Fetch_file not supported for local testbed") +        raise + +    def copy_file(self, node, path, destination): +        logger.error("Copy_file not supported for local testbed") +        raise diff --git a/rumba/executors/ssh.py b/rumba/executors/ssh.py new file mode 100644 index 0000000..6a3a41d --- /dev/null +++ b/rumba/executors/ssh.py @@ -0,0 +1,64 @@ +# +# A library to manage ARCFIRE experiments +# +#    Copyright (C) 2017 Nextworks S.r.l. +#    Copyright (C) 2017 imec +# +#    Sander Vrijders   <sander.vrijders@ugent.be> +#    Dimitri Staessens <dimitri.staessens@ugent.be> +#    Vincenzo Maffione <v.maffione@nextworks.it> +#    Marco Capitani    <m.capitani@nextworks.it> +#    Nick Aerts        <nick.aerts@ugent.be> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +from rumba.model import Executor +from rumba.ssh_support import execute_command, execute_commands, \ +    copy_file_to_testbed, copy_file_from_testbed, execute_proxy_command, \ +    execute_proxy_commands + + +class SSHExecutor(Executor): +    def __init__(self, testbed, use_proxy=False): +        self.testbed = testbed +        self.use_proxy = use_proxy + +    def execute_command(self, node, command, as_root=False, time_out=3): +        if as_root: +            if node.ssh_config.username != 'root': +                command = "sudo %s" % command + +        if self.use_proxy: +            return execute_proxy_command(self, node.ssh_config, command, +                                         time_out) +        else: +            return execute_command(self.testbed, node.ssh_config, command, +                                   time_out) + +    def execute_commands(self, node, commands, as_root=False, time_out=3): +        if self.use_proxy: +            return execute_proxy_commands(self, node.ssh_config, commands, +                                          time_out) +        else: +            return execute_commands(self.testbed, node.ssh_config, commands, +                                    time_out) + +    def fetch_file(self, node, path, destination, sudo): +        copy_file_from_testbed(self.testbed, node.ssh_config, path, +                               destination, sudo) + +    def copy_file(self, node, path, destination): +        copy_file_to_testbed(self.testbed, node.ssh_config, path, destination)
\ No newline at end of file diff --git a/rumba/model.py b/rumba/model.py index 39528be..20f8215 100644 --- a/rumba/model.py +++ b/rumba/model.py @@ -31,7 +31,6 @@ import time  import shutil  import rumba.log as log -from rumba import ssh_support  logger = log.get_logger(__name__) @@ -88,7 +87,8 @@ class Testbed(object):      @abc.abstractmethod      def swap_in(self, experiment): -        raise Exception('swap_in() not implemented') +        for node in experiment.nodes: +            node.executor = self.executor      @abc.abstractmethod      def swap_out(self, experiment): @@ -245,6 +245,9 @@ class Node(object):              if hasattr(dif, 'policy'):  # check if the dif supports policies                  self.policies[dif] = policies.get(dif, Policy(dif, self)) +        self.executor = None # will be set by testbed on swap_in +        self.startup_command = None # will be set by prototype +          self._validate()      def get_ipcp_by_dif(self, dif): @@ -328,88 +331,34 @@ class Node(object):      def get_policy(self, dif):          return self.policies[dif] -    def execute_commands(self, commands, time_out=3, use_proxy=False): -        # Ssh_config is used twice since it doubles as testbed info -        # (it holds fields username and password) -        if use_proxy: -            return ssh_support.execute_proxy_commands( -                self.ssh_config, -                self.ssh_config, -                commands, -                time_out -            ) -        # else: -        return ssh_support.execute_commands( -            self.ssh_config, -            self.ssh_config, -            commands, -            time_out -        ) - -    def execute_command(self, command, time_out=3, -                        use_proxy=False, as_root=False): -        # Ssh_config is used twice since it doubles as testbed info -        # (it holds fields username and password) -        if as_root: -            if self.ssh_config.username != 'root': -                command = "sudo %s" % command - -        if use_proxy: -            return ssh_support.execute_proxy_command( -                self.ssh_config, -                self.ssh_config, -                command, -                time_out -            ) -        # else: -        return ssh_support.execute_command( -                self.ssh_config, -                self.ssh_config, -                command, -                time_out -        ) +    def execute_commands(self, commands, as_root=False, time_out=3, +                         use_proxy=False): +        return self.executor.execute_commands(self, commands, as_root, time_out) + +    def execute_command(self, command, as_root=False, time_out=3, +                        use_proxy=False): +        return self.executor.execute_command(self, command, as_root, time_out)      def write_text_to_file(self, text, file_name): -        ssh_support.write_text_to_file( -            self.ssh_config, -            self.ssh_config, -            text, -            file_name -        ) +        # ssh_support.write_text_to_file( +        #     self.ssh_config, +        #     self.ssh_config, +        #     text, +        #     file_name +        # ) +        return      def copy_file(self, path, destination): -        ssh_support.copy_file_to_testbed( -            self.ssh_config, -            self.ssh_config, -            path, -            destination -        ) +        self.executor.copy_file(self, path, destination)      def copy_files(self, paths, destination): -        ssh_support.copy_files_to_testbed( -            self.ssh_config, -            self.ssh_config, -            paths, -            destination -        ) +        self.executor.copy_files(self, paths, destination)      def fetch_files(self, paths, destination, sudo=False): -        ssh_support.copy_files_from_testbed( -            self.ssh_config, -            self.ssh_config, -            paths, -            destination, -            sudo=sudo -        ) +        self.executor.fetch_files(self, paths, destination, sudo)      def fetch_file(self, path, destination, sudo=False): -        ssh_support.copy_file_from_testbed( -            self.ssh_config, -            self.ssh_config, -            path, -            destination, -            sudo=sudo -        ) +        self.executor.fetch_files(self, path, destination, sudo)      def set_link_state(self, ipcp, state):          self.execute_command('ip link set dev ' + ipcp.ifname + ' ' + state, @@ -869,6 +818,10 @@ class Experiment(object):          end = time.time()          logger.info("Install took %.2f seconds", end - start) +    def set_startup_command(self, command): +        for node in self.nodes: +            node.startup_command = command +      def bootstrap_prototype(self):          start = time.time()          self._bootstrap_prototype() @@ -912,3 +865,32 @@ class Experiment(object):          self.testbed.swap_out(self)          end = time.time()          logger.info("Swap-out took %.2f seconds", end - start) + + +class Executor: +    __metaclass__ = abc.ABCMeta + +    @abc.abstractmethod +    def execute_command(self, node, command, as_root=False, time_out=3): +        # Execute command on a node +        return + +    def execute_commands(self, node, commands, as_root=False, time_out=3): +        for command in commands: +            self.execute_command(node, command, as_root, time_out) + +    @abc.abstractmethod +    def copy_file(self, node, path, destination): +        return + +    def copy_files(self, node, paths, destination): +        for path in paths: +            self.copy_file(node, path, destination) + +    @abc.abstractmethod +    def fetch_file(self, node, path, destination, sudo=False): +        return + +    def fetch_files(self, node, paths, destination, sudo=False): +        for path in paths: +            self.fetch_file(node, path, destination, sudo)
\ No newline at end of file diff --git a/rumba/prototypes/ouroboros.py b/rumba/prototypes/ouroboros.py index 33ecd0b..97f6e1a 100644 --- a/rumba/prototypes/ouroboros.py +++ b/rumba/prototypes/ouroboros.py @@ -31,7 +31,8 @@ import rumba.ssh_support as ssh  import rumba.model as mod  import rumba.multiprocess as m_processing  import rumba.log as log -import rumba.testbeds.faketestbed as fake +import rumba.testbeds.local as local +import rumba.testbeds.dockertb as docker  logger = log.get_logger(__name__) @@ -45,6 +46,8 @@ class Experiment(mod.Experiment):          mod.Experiment.__init__(self, testbed, nodes, git_repo, git_branch)          self.r_ipcps = dict() +        self.set_startup_command("irmd") +      @staticmethod      def make_executor(node, packages, testbed):          def executor(commands): @@ -68,19 +71,20 @@ class Experiment(mod.Experiment):              self.exec_local_cmd(cmd)      def setup_ouroboros(self): -        if isinstance(self.testbed, fake.Testbed): +        if isinstance(self.testbed, docker.Testbed): +            return + +        if isinstance(self.testbed, local.Testbed):              subprocess.check_call('sudo -v'.split())              self.irmd = subprocess.Popen(["sudo", "irmd"])              logger.info("Started IRMd, sleeping 2 seconds...")              time.sleep(2)          else:              for node in self.nodes: -                ssh.execute_command(self.testbed, node.ssh_config, -                                    "sudo nohup irmd > /dev/null &", -                                    time_out=None) +                node.execute_command("sudo nohup irmd > /dev/null &", time_out=None)      def install_ouroboros(self): -        if isinstance(self.testbed, fake.Testbed): +        if isinstance(self.testbed, local.Testbed):              return          packages = ["cmake", "protobuf-c-compiler", "git", "libfuse-dev", @@ -120,7 +124,7 @@ class Experiment(mod.Experiment):                      cmd = "irm i c n " + ipcp.name                  if isinstance(ipcp.dif, mod.ShimEthDIF): -                    if isinstance(self.testbed, fake.Testbed): +                    if isinstance(self.testbed, local.Testbed):                          cmd += " type local layer " + ipcp.dif.name                      else:                          cmd += " type eth-llc dev " + ipcp.ifname @@ -154,11 +158,7 @@ class Experiment(mod.Experiment):                  # Postpone registrations                  self.r_ipcps[ipcp] = cmds2 -            if isinstance(self.testbed, fake.Testbed): -                self.exec_local_cmds(cmds) -            else: -                ssh.execute_commands(self.testbed, node.ssh_config, cmds, -                                     time_out=None) +            node.execute_commands(cmds, time_out=None)      def enroll_dif(self, el):          for e in el: @@ -167,13 +167,8 @@ class Experiment(mod.Experiment):              # Execute postponed registration              if e['enroller'] in self.r_ipcps: -                if isinstance(self.testbed, fake.Testbed): -                    self.exec_local_cmds(self.r_ipcps[e['enroller']]) -                else: -                    ssh.execute_commands(self.testbed, -                                         e['enroller'].node.ssh_config, -                                         self.r_ipcps[e['enroller']], -                                         time_out=None) +                e['enroller'].node.execute_commands(self.r_ipcps[e['enroller']], +                                                    time_out=None)                  self.r_ipcps.pop(e['enroller'], None)              cmd = "irm r n " + ipcp.name @@ -188,12 +183,7 @@ class Experiment(mod.Experiment):                  cmd += " layer " + dif_b.name              cmds.append(cmd) -            if isinstance(self.testbed, fake.Testbed): -                self.exec_local_cmds(cmds) -            else: -                ssh.execute_commands(self.testbed, -                                     e['enrollee'].node.ssh_config, -                                     cmds, time_out=None) +            e['enrollee'].node.execute_commands(cmds, time_out=None)      def setup_flows(self, el, comp):          for e in el: @@ -201,12 +191,7 @@ class Experiment(mod.Experiment):              cmd = "irm i conn n " + ipcp.name + " comp " + \                    comp + " dst " + e['dst'].name -            if isinstance(self.testbed, fake.Testbed): -                self.exec_local_cmd(cmd) -            else: -                ssh.execute_command(self.testbed, -                                    ipcp.node.ssh_config, -                                    cmd, time_out=None) +            ipcp.node.execute_command(cmd, time_out=None)      def _install_prototype(self):          logger.info("Installing Ouroboros...") @@ -230,6 +215,6 @@ class Experiment(mod.Experiment):          logger.info("All done, have fun!")      def _terminate_prototype(self): -        if isinstance(self.testbed, fake.Testbed): +        if isinstance(self.testbed, local.Testbed):              logger.info("Killing IRMd...")              subprocess.check_call('sudo killall -15 irmd'.split()) diff --git a/rumba/ssh_support.py b/rumba/ssh_support.py index 88334ea..b1492e7 100644 --- a/rumba/ssh_support.py +++ b/rumba/ssh_support.py @@ -32,6 +32,8 @@ import time  import rumba.log as log  # Fix input reordering +from rumba.model import Executor +  try:      import builtins  # Only in Python 3 @@ -434,4 +436,4 @@ def aptitude_install(testbed, node, packages):              "while ! " + sudo("apt-get update") + "; do sleep 1; done",              "while ! " + sudo(package_install) + "; do sleep 1; done"] -    execute_proxy_commands(testbed, node.ssh_config, cmds, time_out=None) +    execute_proxy_commands(testbed, node.ssh_config, cmds, time_out=None)
\ No newline at end of file diff --git a/rumba/testbeds/dockertb.py b/rumba/testbeds/dockertb.py new file mode 100644 index 0000000..3895a7e --- /dev/null +++ b/rumba/testbeds/dockertb.py @@ -0,0 +1,190 @@ +# +# A library to manage ARCFIRE experiments +# +#    Copyright (C) 2017 Nextworks S.r.l. +#    Copyright (C) 2017 imec +# +#    Sander Vrijders   <sander.vrijders@ugent.be> +#    Dimitri Staessens <dimitri.staessens@ugent.be> +#    Vincenzo Maffione <v.maffione@nextworks.it> +#    Marco Capitani    <m.capitani@nextworks.it> +#    Nick Aerts        <nick.aerts@ugent.be> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# +import os +from time import sleep +import docker +import subprocess + +import rumba.model as mod +import rumba.log as log +from rumba.executors.docker import DockerExecutor + + +logger = log.get_logger(__name__) + +class Testbed(mod.Testbed): +    def __init__(self, exp_name, base_image, pull_image=True, +                 use_ovs=False): +        mod.Testbed.__init__(self, exp_name, "", "", "") + +        img = base_image.rsplit(":", 1) + +        self.base_image_repo = img[0] +        self.base_image_tag = "latest" if len(img) == 1 else img[1] +        self.base_image = "%s:%s" % (self.base_image_repo, self.base_image_tag) +        self.pull_image = pull_image +        self.use_ovs = use_ovs + +        self.running_containers = {} +        self.active_bridges = set() +        self.active_ipcps = set() + +        self.docker_client = docker.from_env() +        self.executor = DockerExecutor(self) + +    def swap_in(self, experiment): +        docker_client = self.docker_client + +        mod.Testbed.swap_in(self, experiment) + +        # Pull image +        if self.pull_image: +                docker_client.images.pull(self.base_image_repo, +                                          self.base_image_tag) + +        docker_client.images.get("%s:%s" % (self.base_image_repo, +                                            self.base_image_tag)) + +        # Start all nodes +        for node in experiment.nodes: +            self.running_containers[node.name] = docker_client.containers.run( +                self.base_image, command=node.startup_command, name=node.name, +                detach=True, network="none", privileged=True, +                devices=["/dev/fuse"]) + +        logger.info("Nodes starting") + +        if not os.path.exists("/var/run/netns"): +            subprocess.check_call('sudo mkdir /var/run/netns'.split()) + +        for shim in experiment.dif_ordering: +            if not isinstance(shim, mod.ShimEthDIF): +                # Nothing to do here +                continue + +            cmd = "" +            if self.use_ovs: +                cmd += 'sudo ovs-vsctl add-br %(shim)s' +            else: +                cmd += 'sudo ip link add %(shim)s type bridge' +            cmd = cmd % {'shim': shim.name} + +            self.active_bridges.add(shim.name) + +            subprocess.check_call(cmd.split()) + +            if not self.use_ovs: +                cmd = 'sudo ip link set dev %(shim)s up' % {'shim': shim.name} +                subprocess.check_call(cmd.split()) +        for node in experiment.nodes: +            container = self.running_containers[node.name] + +            container.reload() + +            state = container.attrs["State"] + +            while not state["Running"]: +                sleep(0.2) +                container.reload() +                state = container.attrs["State"] + +            pid = state["Pid"] + +            subprocess.check_call(('sudo ln -s /proc/%(pid)i/ns/net ' +                             '/var/run/netns/%(pid)i' % {'pid': pid}).split()) + +            for ipcp in node.ipcps: +                if isinstance(ipcp, mod.ShimEthIPCP): +                    if ipcp.ifname is None: +                        ipcp.ifname = "eth%i" % node.ipcps.index(ipcp) + +                    cmd = ('sudo ip link add %(node)s.%(ifname)s type veth ' +                           'peer name _%(node)s.%(ifname)s'\ +                           % {'node': node.name, 'ifname': ipcp.ifname}) +                    subprocess.check_call(cmd.split()) + +                    cmd = "" +                    if self.use_ovs: +                        cmd += ('sudo ovs-vsctl add-port %(dif)s %(node)s.%(' +                               'ifname)s') +                    else: +                        cmd += ('sudo ip link set %(node)s.%(ifname)s master ' +                               '%(dif)s') + +                    cmd = (cmd % {'node': node.name, +                                 'ifname': ipcp.ifname, +                                 'dif': ipcp.dif.name}) + +                    subprocess.check_call(cmd.split()) + +                    cmd = ('sudo ip link set _%(node)s.%(ifname)s ' +                           'netns %(pid)i ' +                           'name %(ifname)s' +                           % {'node': node.name, +                              'pid': pid, +                              'ifname': ipcp.ifname}) +                    subprocess.check_call(cmd.split()) + +                    cmd = ('sudo ip link set dev %(node)s.%(ifname)s up' +                           % {'node': node.name, 'ifname': ipcp.ifname}) +                    subprocess.check_call(cmd.split()) + +                    cmd = ('sudo ip netns exec %(pid)i ip link set dev ' +                           '%(ifname)s up' +                           % {'pid': pid, 'ifname': ipcp.ifname}) +                    subprocess.check_call(cmd.split()) + +                    self.active_ipcps.add(ipcp) + +        logger.info("Experiment swapped in") + +    def swap_out(self, experiment): +        for name, container in self.running_containers.items(): +            container.remove(force=True) + +        for shim in experiment.dif_ordering: +            if isinstance(shim, mod.ShimEthDIF) and shim.name in self.active_bridges: +                cmd = "" +                if self.use_ovs: +                    cmd += 'sudo ovs-vsctl del-br %(shim)s' +                else: +                    cmd += 'sudo ip link del %(shim)s' +                cmd = cmd % {'shim': shim.name} + +                subprocess.check_call(cmd.split()) + +                self.active_bridges.remove(shim.name) + +        for name, container in self.running_containers.items(): +            pid = container.attrs["State"]["Pid"] + +            cmd = 'sudo rm /var/run/netns/%(pid)i' % {'pid': pid} +            subprocess.check_call(cmd.split()) + +        self.running_containers = {} + +        logger.info("Experiment swapped out")
\ No newline at end of file diff --git a/rumba/testbeds/emulab.py b/rumba/testbeds/emulab.py index 4f8b023..06e0e5c 100644 --- a/rumba/testbeds/emulab.py +++ b/rumba/testbeds/emulab.py @@ -34,6 +34,8 @@ import rumba.ssh_support as ssh  import rumba.model as mod  import rumba.log as log +from rumba.executors.ssh import SSHExecutor +  logger = log.get_logger(__name__) @@ -56,6 +58,8 @@ class Testbed(mod.Testbed):          self.ip = dict()          self.ops_ssh_config = mod.SSHConfig(self.ops_server()) +        self.executor = SSHExecutor +          if "wall" in url:              self.http_proxy="https://proxy.atlantis.ugent.be:8080" @@ -237,7 +241,7 @@ class Testbed(mod.Testbed):              node.ssh_config.set_password(self.password)          cmd = 'cat /var/emulab/boot/topomap' -        topomap = ssh.execute_command(self, experiment.nodes[0].ssh_config, cmd) +        topomap = node.execute_command(cmd)          # Almost as ugly as yo momma          index = topomap.rfind("# lans")          topo_array = topomap[:index].split('\n')[1:-1] @@ -258,7 +262,7 @@ class Testbed(mod.Testbed):          for node in experiment.nodes:              cmd = 'cat /var/emulab/boot/ifmap' -            output = ssh.execute_command(self, node.ssh_config, cmd) +            output = node.execute_command(cmd)              output = re.split('\n', output)              for item in output:                  item = item.split() @@ -268,6 +272,8 @@ class Testbed(mod.Testbed):                              ipcp.ifname = item[0]      def swap_in(self, experiment): +        mod.Testbed.swap_in(self, experiment) +          self._create_experiment(experiment)          wait = self.swap_exp_in()          if wait: diff --git a/rumba/testbeds/jfed.py b/rumba/testbeds/jfed.py index 3cfc82b..db9dd14 100644 --- a/rumba/testbeds/jfed.py +++ b/rumba/testbeds/jfed.py @@ -32,6 +32,8 @@ import time  import tarfile  import sys +from rumba.executors.ssh import SSHExecutor +  if sys.version_info[0] >= 3:      from urllib.request import urlretrieve  else: @@ -65,6 +67,7 @@ class Testbed(mod.Testbed):          self.manifest = os.path.join(mod.tmp_dir, self.exp_name + ".rrspec")          self.jfed_jar = os.path.join(mod.cache_dir,                                       'jfed_cli/experimenter-cli.jar') +        self.executor = SSHExecutor(self)          if "exogeni" in authority:              self.authority = "urn:publicid:IDN+" + authority + "+authority+am" @@ -204,6 +207,8 @@ class Testbed(mod.Testbed):              raise      def swap_in(self, experiment): +        mod.Testbed.swap_in(self, experiment) +          for node in experiment.nodes:              node.ssh_config.set_http_proxy(self.http_proxy)          self.create_rspec(experiment) diff --git a/rumba/testbeds/faketestbed.py b/rumba/testbeds/local.py index b021aca..77aed82 100644 --- a/rumba/testbeds/faketestbed.py +++ b/rumba/testbeds/local.py @@ -27,17 +27,22 @@  import rumba.model as mod  import rumba.log as log +from rumba.executors.local import LocalExecutor  logger = log.get_logger(__name__) -# Fake testbed, useful for testing +# Local testbed, useful for testing  class Testbed(mod.Testbed):      def __init__(self, exp_name, username, proj_name="ARCFIRE", password=""):          mod.Testbed.__init__(self, exp_name, username, password, proj_name) +        self.executor = LocalExecutor(self) +      def swap_in(self, experiment): +        mod.Testbed.swap_in(self, experiment) +          logger.info("Experiment swapped in")      def swap_out(self, experiment): diff --git a/rumba/testbeds/qemu.py b/rumba/testbeds/qemu.py index 4321fc8..f3daefd 100644 --- a/rumba/testbeds/qemu.py +++ b/rumba/testbeds/qemu.py @@ -32,9 +32,10 @@ from subprocess import CalledProcessError  import rumba.model as mod  import rumba.log as log -import rumba.ssh_support as ssh_support  import rumba.multiprocess as m_processing +from rumba.executors.ssh import SSHExecutor +  if sys.version_info[0] >= 3:      from urllib.request import urlretrieve  else: @@ -59,6 +60,8 @@ class Testbed(mod.Testbed):          self.initramfs_path = initramfs_path          self.multiproc_manager = None +        self.executor = SSHExecutor(self); +      # Prepend sudo to all commands if the user is not 'root'      def may_sudo(self, cmds):          if os.geteuid() != 0: @@ -116,13 +119,9 @@ class Testbed(mod.Testbed):                      mac = '00:0a:0a:0a:%02x:%02x' % (vm_id, port_id)                      logger.info('Recovering ifname for port: %s.',                                  port['tap_id']) -                    output = ssh_support.execute_command( -                        self, -                        node.ssh_config, -                        'mac2ifname ' + mac) +                    output = node.execute_command('mac2ifname ' + mac)                      ipcp.ifname = output -                    ssh_support.execute_command( -                        self, node.ssh_config, +                    node.execute_command(                          "ip link set dev %(ifname)s up"                          % {'ifname': ipcp.ifname}) @@ -131,6 +130,9 @@ class Testbed(mod.Testbed):          :type experiment mod.Experiment          :param experiment: The experiment running          """ + +        mod.Testbed.swap_in(self, experiment) +          if os.geteuid() != 0:              try:                  subprocess.check_call(["sudo", "-v"]) @@ -12,9 +12,10 @@ setuptools.setup(      author_email='sander.vrijders@ugent.be',      license='LGPL',      description='Rumba measurement framework for RINA', -    packages=['rumba', 'rumba.testbeds', 'rumba.prototypes'], +    packages=['rumba', 'rumba.testbeds', 'rumba.prototypes', 'rumba.executors'],      install_requires=[          'paramiko', +        'docker',          'repoze.lru; python_version<"3.2"',          'contextlib2; python_version<"3.0"',          'enum34; python_version<"3.0"' diff --git a/tools/democonf2rumba.py b/tools/democonf2rumba.py index dc2f0a4..e398308 100755 --- a/tools/democonf2rumba.py +++ b/tools/democonf2rumba.py @@ -241,9 +241,9 @@ if __name__ == '__main__':                          for a in qemu_p._actions                          if a.dest != 'help'                          and getattr(args, a.dest) is not None} -    elif args.testbed == 'fake': -        import rumba.testbeds.faketestbed as fake -        test_class = fake.Testbed +    elif args.testbed == 'local': +        import rumba.testbeds.local as local +        test_class = local.Testbed          testbed_args = {a.dest: getattr(args, a.dest)                          for a in fake_p._actions                          if a.dest != 'help'  | 
