diff options
author | Marco Capitani <m.capitani@nextworks.it> | 2017-06-13 10:09:54 +0200 |
---|---|---|
committer | Marco Capitani <m.capitani@nextworks.it> | 2017-06-13 10:09:54 +0200 |
commit | 457977f337a47caddf8788e1d4e1d1736f2a6ccb (patch) | |
tree | 12fa2a2be0f57be3ea2acd3623b01f55c8de1092 | |
parent | 3081d070cda223afd548645143142e1104b07d83 (diff) | |
parent | 53602860e17d650f9ab850cf9a206de6a8712c15 (diff) | |
download | rumba-457977f337a47caddf8788e1d4e1d1736f2a6ccb.tar.gz rumba-457977f337a47caddf8788e1d4e1d1736f2a6ccb.zip |
Merge branch 'master' into policies
-rw-r--r-- | README.md | 85 | ||||
-rwxr-xr-x | examples/example.py | 20 | ||||
-rwxr-xr-x | examples/jfed-rlite.py | 54 | ||||
-rw-r--r-- | examples/mouse.py | 105 | ||||
-rwxr-xr-x | examples/two-layers.py | 10 | ||||
-rw-r--r-- | rumba/model.py | 234 | ||||
-rwxr-xr-x | rumba/prototypes/enroll.py | 8 | ||||
-rw-r--r-- | rumba/prototypes/irati.py | 52 | ||||
-rw-r--r-- | rumba/prototypes/ouroboros.py | 28 | ||||
-rw-r--r-- | rumba/prototypes/rlite.py | 35 | ||||
-rw-r--r-- | rumba/ssh_support.py | 63 | ||||
-rw-r--r-- | rumba/testbeds/jfed.py | 51 | ||||
-rw-r--r-- | rumba/testbeds/qemu.py | 31 | ||||
-rwxr-xr-x | setup.py | 5 | ||||
-rwxr-xr-x | tools/democonf2rumba.py | 19 | ||||
-rwxr-xr-x | tools/rumba-access | 40 |
16 files changed, 658 insertions, 182 deletions
@@ -3,17 +3,20 @@ Rumba is part of ARCFIRE 2020, Work Package 3. It is a framework in Python which allows user to write a Python script to define a RINA network. The physical graph needed for this RINA network is then -calculated and realised on one of the supported testbeds. Next, one of -the supported RINA prototypes is installed. After installation, the -network is bootstrapped. For an example of such a Python script, have -a look at the examples/ folder. +calculated and realised on one of the supported testbeds. Next, if the +user requests this, one of the supported RINA prototypes is +installed. The network is then bootstrapped on the available +nodes. Finally, the experiment can be swapped out of the testbed. For +an example of such a Python script, have a look at the examples/ +folder. ## Workflow, both external and internal: 1. User defines the network graph, creating instances of model.Node - and model.DIF classes + and model.DIF classes - 2. User creates an instance of a Testbed class + 2. User creates an instance of a Testbed class. See below for + testbed specific configuration 3. User creates an instance of prototype.Experiment class, passing the testbed instance and a list of Node instances @@ -23,26 +26,35 @@ a look at the examples/ folder. per-node IPCPs, registrations and enrollment, ready to be used by the plugins - 4. User calls run() on the prototype.Experiment instance: + 4. User calls methods on the prototype.Experiment instance: - 1. run() calls Testbed.swap_in(), passing the Experiment, and - filling in the missing information + 1. swap_in() swaps the experiment in on the testbed, and fills in + the missing information in the model. - 2. run() calls a prototype-specific setup function, to create the - required IPCPs, perform registrations, enrollments, etc. + 2. install_prototype() installs the chosen prototype on the + testbed. Currently an Ubuntu image is assumed. - 3. Perform tests (TODO) + 3. bootstrap_prototype() calls a prototype-specific setup function, + to create the required IPCPs, perform registrations, + enrollments, etc. - 4. run() calls Testbed.swap_out() + 4. swap_out() swaps the experiment out of the testbed. ## Installation + For Debian and Ubuntu, the following command will ensure that the + required dependencies are installed (replace python-dev with python3-dev + if using Python 3): + + # apt-get install build-essential libssl-dev libffi-dev python-dev + Rumba can be found on the [PyPi](https://pypi.python.org/pypi/Rumba) and can thus be installed through pip, by using `pip install rumba`. However, to install the latest version, after cloning the repository, a user can also issue `python setup.py install`. + ## Supported prototypes * [IRATI](https://github.com/IRATI/stack) is an open source @@ -62,10 +74,30 @@ a look at the examples/ folder. * [QEMU](http://wiki.qemu-project.org/Main_Page) is a generic and open source machine emulator and virtualizer. + A minimal QEMU testbed is defined as follows: + + tb = qemu.Testbed(exp_name = "twolayers", + username = "root", + password = "root") + + A user can optionally also specify the path to a bzImage and to an + initramfs. If they are not specified, the latest buildroot image + for the specific prototype will be downloaded. (Around 40 MB in + size) The login to those images is root/root. + * [Emulab](https://www.emulab.net/) is a network testbed, giving researchers a wide range of environments in which to develop, debug, and evaluate their systems. + An emulab testbed instance is defined as follows: + + tb = emulab.Testbed(exp_name = "rochefort10", + username = "ricksanchez") + + A password can also be provided but is not necessary when an SSH + key has been added. Optionally, a project name, a different testbed + URL and a custom image can be specified. + * [jFed](http://jfed.iminds.be/) is a Java-based framework for testbed federation. @@ -82,16 +114,25 @@ a look at the examples/ folder. Here the experiment name is rochefort10, the user's name is ricksanchez, and the certificate can be found in - /home/morty/cert.pem. Please use an absolute path for cert_file for - now. + /home/morty/cert.pem. An absolute path must be used for + cert_file. Optionally a custom image can be selected. - Before running the experiment it is wise to use an SSH agent to - avoid having to enter the passphrase for every login to a node by - the framework if you are not on an IPv6 enabled network. (Apart - from asking for the passphrase to login to the nodes, the framework + Before running the rumba you must run an SSH agent in same terminal. + This will also avoid you having to enter the passphrase for every + login to a node by the framework if you are not on an IPv6 enabled network. + (Apart from asking for the passphrase to login to the nodes, the framework will always ask for the passphrase since it is needed by the jFed CLI as well.) In order to start an SSH agent and to add the - certificate, simply perform the following commands: + certificate, type the following commands: + + $ eval `ssh-agent` + $ ssh-add /home/morty/cert.pem + +## Accessing nodes after swap-in + + To access a node once the experiment swapped in, use the following + command (in the same terminal where ssh-agent was run in case of jFed): + + $ rumba-access $NODE_NAME - eval `ssh-agent` - ssh-add /home/morty/cert.pem + Where $NODE_NAME is the name of the node to access.
\ No newline at end of file diff --git a/examples/example.py b/examples/example.py index c884f3e..8a68aab 100755 --- a/examples/example.py +++ b/examples/example.py @@ -17,7 +17,6 @@ import rumba.prototypes.irati as irati import rumba.log as log - log.set_logging_level('DEBUG') @@ -28,19 +27,16 @@ e1 = ShimEthDIF("e1") a = Node("a", difs = [n1, e1], - dif_registrations = {n1 : [e1]}, - registrations = {"a.thing" : [n1]}, - bindings = {"a.thing" : "/usr/bin/thing"}) + dif_registrations = {n1 : [e1]}) b = Node("b", difs = [e1, n1], - dif_registrations = {n1 : [e1]}) + dif_registrations = {n1 : [e1]}, + client = True) -tb = qemu.Testbed(exp_name = "example1", - username = "root", - password = "root", - bzimage = '/home/vmaffione/git/rlite/demo/buildroot/bzImage', - initramfs = '/home/vmaffione/git/rlite/demo/buildroot/rootfs.cpio') +tb = jfed.Testbed(exp_name = "example1", + username = "user1", + cert_file = "/home/user1/cert.pem") exp = rl.Experiment(tb, nodes = [a, b]) @@ -49,5 +45,9 @@ print(exp) try: exp.swap_in() exp.bootstrap_prototype() + c1 = Client("rinaperf", options ="-t perf -s 1000 -c 10000") + s1 = Server("rinaperf", arrival_rate=2, mean_duration=5, options = "-l", nodes = [a], clients = [c1]) + sb = StoryBoard(exp, 3600, servers = [s1]) + sb.start() finally: exp.swap_out() diff --git a/examples/jfed-rlite.py b/examples/jfed-rlite.py new file mode 100755 index 0000000..d80b56e --- /dev/null +++ b/examples/jfed-rlite.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +from rumba.model import * + +import rumba.testbeds.jfed as jfed +import rumba.prototypes.rlite as rlite + +import rumba.log as log + +import argparse + + +description = "Script to run rlite on jfed" +epilog = "2017 H2020 ARCFIRE" + +argparser = argparse.ArgumentParser(description = description, + epilog = epilog) +argparser.add_argument('--user', type = str, default = 'vmaffio', + help = "jFed username") +argparser.add_argument('--cert', type = str, + help = "Absolute path to certificate (.pem) file" + " to be used with jFed", + default = '/home/vmaffione/Downloads/vmaffio-jfed.pem') +argparser.add_argument('--expname', type = str, default = 'pinocchio', + help = "Name of the experiment within the jFed testbed") + +args = argparser.parse_args() + +log.set_logging_level('DEBUG') + +n1 = NormalDIF("n1") + +e1 = ShimEthDIF("e1") + +a = Node("a", + difs = [n1, e1], + dif_registrations = {n1 : [e1]}) + +b = Node("b", + difs = [e1, n1], + dif_registrations = {n1 : [e1]}) + +tb = jfed.Testbed(exp_name = args.expname, + cert_file = args.cert, + username = args.user) + +exp = rlite.Experiment(tb, nodes = [a, b]) + +try: + exp.swap_in() + exp.install_prototype() + exp.bootstrap_prototype() +finally: + exp.swap_out() diff --git a/examples/mouse.py b/examples/mouse.py new file mode 100644 index 0000000..25e2487 --- /dev/null +++ b/examples/mouse.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +# An example script using the rumba package + +from rumba.model 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.qemu as qemu + +# import prototype plugins +import rumba.prototypes.ouroboros as our +import rumba.prototypes.rlite as rl +import rumba.prototypes.irati as irati + +n01 = NormalDIF("n01") + +e01 = ShimEthDIF("e01") +e02 = ShimEthDIF("e02") +e03 = ShimEthDIF("e03") +e04 = ShimEthDIF("e04") +e05 = ShimEthDIF("e05") +e06 = ShimEthDIF("e06") +e07 = ShimEthDIF("e07") +e08 = ShimEthDIF("e08") +e09 = ShimEthDIF("e09") +e10 = ShimEthDIF("e10") +e11 = ShimEthDIF("e11") +e12 = ShimEthDIF("e12") +e13 = ShimEthDIF("e13") +e14 = ShimEthDIF("e14") +e15 = ShimEthDIF("e15") +e16 = ShimEthDIF("e16") +e17 = ShimEthDIF("e17") + + +a = Node("a", + difs = [n01, e01, e06, e13 ], + dif_registrations = {n01 : [e01, e06, e13]}) + +b = Node("b", + difs = [n01, e01, e02, e04], + dif_registrations = {n01 : [e01, e02, e04]}) + +c = Node("c", + difs = [n01, e02, e03], + dif_registrations = {n01 : [e02, e03]}) + +d = Node("d", + difs = [n01, e03, e04, e05], + dif_registrations = {n01 : [e03, e04, e05]}) + +e = Node("e", + difs = [n01, e05, e06, e07], + dif_registrations = {n01 : [e05, e06, e07]}) + +f = Node("f", + difs = [n01, e07, e08], + dif_registrations = {n01 : [e07, e08]}) + +g = Node("g", + difs = [n01, e08, e09, e14], + dif_registrations = {n01 : [e08, e09, e14]}) + +h = Node("h", + difs = [n01, e09, e10, e15], + dif_registrations = {n01 : [e09, e10, e15]}) + +i = Node("i", + difs = [n01, e10, e11, e16], + dif_registrations = {n01 : [e10, e11, e16]}) + +j = Node("j", + difs = [n01, e11, e12], + dif_registrations = {n01 : [e11, e12]}) + +k = Node("k", + difs = [n01, e12, e13], + dif_registrations = {n01 : [e12, e13]}) + +l = Node("l", + difs = [n01, e14, e15], + dif_registrations = {n01 : [e14, e15]}) + +m = Node("m", + difs = [n01, e16, e17], + dif_registrations = {n01 : [e16, e17]}) + +n = Node("n", + difs = [n01, e17], + dif_registrations = {n01 : [e17]}) + +tb = jfed.Testbed(exp_name = "mouse2", + cert_file = "/home/sander/cert.pem", + username = "sander") + +exp = rl.Experiment(tb, nodes = [a, b, c, d, e, f, g, h, i, j, k, l, m, n]) + +print(exp) + +exp.swap_in() +exp.install_prototype() +exp.bootstrap_prototype() diff --git a/examples/two-layers.py b/examples/two-layers.py index 9d1a6b3..b3622fd 100755 --- a/examples/two-layers.py +++ b/examples/two-layers.py @@ -32,9 +32,7 @@ e3 = ShimEthDIF("e3") a = Node("a", difs = [n3, n4, n1, e1], - dif_registrations = {n4: [n1], n3: [n1], n1 : [e1]}, - registrations = {"rinaperf.server" : [n3]}, - bindings = {"rinaperf.server" : "/usr/bin/rinaperf"}) + dif_registrations = {n4: [n1], n3: [n1], n1 : [e1]}) b = Node("b", difs = [n1, e1, e2], @@ -50,11 +48,9 @@ d = Node("d", tb = qemu.Testbed(exp_name = "twolayers", username = "root", - password = "root", - bzimage = '/home/vmaffione/git/rlite/demo/buildroot/bzImage', - initramfs = '/home/vmaffione/git/rlite/demo/buildroot/rootfs.cpio') + password = "root") -exp = rl.Experiment(tb, nodes = [a, b, c, d]) +exp = irati.Experiment(tb, nodes = [a, b, c, d]) print(exp) diff --git a/rumba/model.py b/rumba/model.py index 442933c..e0f1dcc 100644 --- a/rumba/model.py +++ b/rumba/model.py @@ -20,6 +20,9 @@ # MA 02110-1301 USA import abc +import random + +import time import rumba.log as log @@ -35,11 +38,18 @@ logger = log.get_logger(__name__) # @exp_name [string] experiment name # class Testbed: - def __init__(self, exp_name, username, password, proj_name): + def __init__(self, + exp_name, + username, + password, + proj_name, + http_proxy=None): self.username = username self.password = password self.proj_name = proj_name self.exp_name = exp_name + self.http_proxy = http_proxy + self.flags = {'no_vlan_offload': False} @abc.abstractmethod def swap_in(self, experiment): @@ -152,13 +162,12 @@ class SSHConfig: # # @difs: DIFs the node will have an IPCP in # @dif_registrations: Which DIF is registered in which DIF -# @registrations: Registrations of names in DIFs -# @bindings: Binding of names on the processing system # @policies: dict of dif -> policy dict to apply for that dif in this node # +# class Node: def __init__(self, name, difs=None, dif_registrations=None, - registrations=None, bindings=None, policies=None): + client=False, policies=None): self.name = name if difs is None: difs = list() @@ -168,12 +177,6 @@ class Node: if dif_registrations is None: dif_registrations = dict() self.dif_registrations = dif_registrations - if registrations is None: - registrations = dict() - self.registrations = registrations - if bindings is None: - bindings = dict() - self.bindings = bindings self.ssh_config = SSHConfig(name) self.ipcps = [] if policies is None: @@ -183,6 +186,7 @@ class Node: if hasattr(dif, 'policy'): self.policies[dif] = \ Policy(dif, self, policies.get(dif.name, {})) + self.client = client self._validate() @@ -197,18 +201,14 @@ class Node: "to be part of DIF %s" % (self.name, dif.name)) def _validate(self): - # Check that DIFs referenced in self.dif_registrations and - # in self.registrations are part of self.difs + # Check that DIFs referenced in self.dif_registrations + # are part of self.difs for upper in self.dif_registrations: self._undeclared_dif(upper) for lower in self.dif_registrations[upper]: self._undeclared_dif(lower) - for appl in self.registrations: - for dif in self.registrations[appl]: - self._undeclared_dif(dif) - - def __repr__(self): # TODO add policies in repr? + def __repr__(self): # TODO add policies in repr s = "Node " + self.name + ":\n" s += " DIFs: [ " @@ -226,19 +226,6 @@ class Node: s += ", ".join(rl) s += " ]\n" - s += " Name registrations: [ " - for name in self.registrations: - difs = self.registrations[name] - s += "%s => [ " % name - s += ", ".join([dif.name for dif in difs]) - s += " ]" - s += " ]\n" - - s += " Bindings: [ " - s += ", ".join(["'%s' => '%s'" % (ap, self.bindings[ap]) - for ap in self.bindings]) - s += " ]\n" - return s def __hash__(self): @@ -275,22 +262,6 @@ class Node: self.dif_registrations[upper].remove(lower) self._validate() - def add_registration(self, name, dif): - self.dif_registrations[name].append(dif) - self._validate() - - def del_registration(self, name, dif): - self.dif_registrations[name].remove(dif) - self._validate() - - def add_binding(self, name, ap): - self.bindings[name] = ap - self._validate() - - def del_binding(self, name): - del self.bindings[name] - self._validate() - def add_policy(self, dif, component_name, policy_name, **parameters): self.policies[dif].add_policy(component_name, policy_name, **parameters) @@ -311,7 +282,7 @@ class IPCP: self.dif = dif self.registrations = [] - # Is this node the first in the DIF, so that it does not need + # Is this IPCP the first in its DIF, so that it does not need # to enroll to anyone ? self.dif_bootstrapper = False @@ -424,6 +395,10 @@ class Experiment: difsdeps_inc = dict() for node in self.nodes: + for dif in node.difs: + if dif not in difsdeps_adj: + difsdeps_adj[dif] = set() + for upper in node.dif_registrations: for lower in node.dif_registrations[upper]: if upper not in difsdeps_inc: @@ -445,6 +420,14 @@ class Experiment: difsdeps_inc_cnt[dif] = len(difsdeps_inc[dif]) del difsdeps_inc + # Init difsdeps_inc_cnt for those DIFs that do not + # act as lower IPCPs nor upper IPCPs for registration + # operations + for node in self.nodes: + for dif in node.difs: + if dif not in difsdeps_inc_cnt: + difsdeps_inc_cnt[dif] = 0 + # Run Kahn's algorithm to compute topological # ordering on the DIFs graph. frontier = set() @@ -576,6 +559,7 @@ class Experiment: if dif not in node.difs: continue + # Create an instance of the required IPCP class ipcp = dif.get_ipcp_class()( name='%s.%s' % (dif.name, node.name), node=node, dif=dif) @@ -584,23 +568,34 @@ class Experiment: for lower in node.dif_registrations[dif]: ipcp.registrations.append(lower) + node.ipcps.append(ipcp) + dif.ipcps.append(ipcp) + + def compute_bootstrappers(self): + for node in self.nodes: + for ipcp in node.ipcps: ipcp.dif_bootstrapper = True for el in self.enrollments: for e in el: - if e['dif'] != dif: + if e['dif'] != ipcp.dif: # Skip this DIF break - if e['enrollee'] == node: + if e['enrollee'] == ipcp: ipcp.dif_bootstrapper = False # Exit the loops break if not ipcp.dif_bootstrapper: break - node.ipcps.append(ipcp) - dif.ipcps.append(ipcp) - - logger.info("IPCP for node %s: %s", node.name, node.ipcps) + def dump_ssh_info(self): + f = open('ssh_info', 'w') + for node in self.nodes: + f.write("%s;%s;%s;%s;%s\n" % (node.name, + self.testbed.username, + node.ssh_config.hostname, + node.ssh_config.port, + node.ssh_config.proxycommand)) + f.close() # Examine the nodes and DIFs, compute the registration and enrollment # order, the list of IPCPs to create, registrations, ... @@ -608,19 +603,148 @@ class Experiment: self.compute_dif_ordering() self.compute_ipcps() self.compute_enrollments() + self.compute_bootstrappers() + for node in self.nodes: + logger.info("IPCPs for node %s: %s", node.name, node.ipcps) @abc.abstractmethod def install_prototype(self): - raise Exception('run_prototype() method not implemented') + raise Exception('install_prototype() method not implemented') @abc.abstractmethod def bootstrap_prototype(self): - raise Exception('run_prototype() method not implemented') + raise Exception('bootstrap_prototype() method not implemented') + + @abc.abstractmethod + def prototype_name(self): + raise Exception('prototype_name() method not implemented') def swap_in(self): # Realize the experiment testbed (testbed-specific) self.testbed.swap_in(self) + self.dump_ssh_info() def swap_out(self): # Undo the testbed (testbed-specific) self.testbed.swap_out(self) + + +# Base class for client programs +# +# @ap: Application Process binary +# @options: Options to pass to the binary +# +class Client(object): + def __init__(self, ap, options=None): + self.ap = ap + self.options = options + + def start_process(self, node, duration, start_time): + return ClientProcess(self.ap, node, duration, start_time, self.options) + + +# Base class for client processes +# +# @ap: Application Process binary +# @node: The node on which this process should run +# @duration: The time (in seconds) this process should run +# @start_time: The time at which this process is started. +# @options: Options to pass to the binary +# +class ClientProcess(Client): + def __init__(self, ap, node, duration, start_time, options=None): + super(ClientProcess, self).__init__(ap, options=options) + self.node = node + self.duration = duration + self.start_time = start_time + self.run() + self.running = True + + def run(self): + pass # TODO to be implemented + + def stop(self): + pass # TODO to be implemented + + def check(self, now): + if not self.running: + return + if now - self.start_time >= self.duration: + self.stop() + + +# Base class for server programs +# +# @ap: Application Process binary +# @arrival_rate: Average requests/s to be received by this server +# @mean_duration: Average duration of a client connection (in seconds) +# @options: Options to pass to the binary +# @max_clients: Maximum number of clients to serve +# @clients: Client binaries that will use this server +# @nodes: Specific nodes to start this server on +# +class Server: + def __init__(self, ap, arrival_rate, mean_duration, + options=None, max_clients=None, + clients=None, nodes=None): + self.ap = ap + self.options = options + self.max_clients = max_clients + if clients is None: + clients = list() + self.clients = clients + self.nodes = nodes + self.arrival_rate = arrival_rate # mean requests/s + self.mean_duration = mean_duration # in seconds + + def add_client(self, client): + self.clients.append(client) + + def del_client(self, client): + self.clients.remove(client) + + def add_node(self, node): + self.nodes.append(node) + + def del_node(self, node): + self.nodes.remove(node) + + def get_new_clients(self, interval): + """ + Returns a list of clients of size appropriate to the server's rate. + + The list's size should be a sample from Poisson(arrival_rate) over + interval seconds. + Hence, the average size should be interval * arrival_rate. + """ + pass + + def make_client_process(self): + """Returns a client of this server""" + if len(self.clients) == 0: + raise Exception("Server %s has empty client list," % (self,)) + pass # TODO should return a ClientProcess + + +# Base class for ARCFIRE storyboards +# +# @experiment: Experiment to use as input +# @duration: Duration of the whole storyboard +# @servers: App servers available in the network +# +class StoryBoard: + def __init__(self, experiment, duration, servers=None): + self.experiment = experiment + self.duration = duration + if servers is None: + servers = list() + self.servers = servers + + def add_server(self, server): + self.servers.append(server) + + def del_server(self, server): + self.servers.remove(server) + + def start(self): + pass diff --git a/rumba/prototypes/enroll.py b/rumba/prototypes/enroll.py index 458736a..99b49a6 100755 --- a/rumba/prototypes/enroll.py +++ b/rumba/prototypes/enroll.py @@ -78,8 +78,8 @@ if connected: get_response(s) # Send the IPCP list command - cmd = 'list-ipcps\n' - s.sendall(bytes(cmd, 'ascii')) + cmd = u'list-ipcps\n' + s.sendall(cmd.encode('ascii')) # Get the list of IPCPs and parse it to look for the enroller ID print('Looking up identifier for IPCP %s' % args.enrollee_name) @@ -98,11 +98,11 @@ if connected: raise Exception() # Send the enroll command - cmd = 'enroll-to-dif %s %s %s %s 1\n' \ + cmd = u'enroll-to-dif %s %s %s %s 1\n' \ % (enrollee_id, args.dif, args.lower_dif, args.enroller_name) print(cmd) - s.sendall(bytes(cmd, 'ascii')) + s.sendall(cmd.encode('ascii')) # Get the enroll command answer lines = get_response(s) diff --git a/rumba/prototypes/irati.py b/rumba/prototypes/irati.py index b2f54d9..42afe3b 100644 --- a/rumba/prototypes/irati.py +++ b/rumba/prototypes/irati.py @@ -21,8 +21,6 @@ import copy import json -import subprocess - import os import time @@ -38,6 +36,9 @@ logger = log.get_logger(__name__) # An experiment over the IRATI implementation class Experiment(mod.Experiment): + def prototype_name(self): + return 'irati' + @staticmethod def real_sudo(s): return 'sudo ' + s @@ -69,25 +70,24 @@ class Experiment(mod.Experiment): def install(self): """Installs IRATI on the nodes.""" - cmds = list() - - cmds.append("sudo apt-get update") - cmds.append("sudo apt-get install g++ gcc " - "protobuf-compiler libprotobuf-dev git --yes") - cmds.append("sudo rm -rf ~/irati") - cmds.append("cd && git clone https://github.com/IRATI/stack irati") - cmds.append("cd ~/irati && sudo ./install-from-scratch") + cmds = [self.sudo("apt-get update"), + "export https_proxy=\"https://proxy.atlantis.ugent.be:8080\"; " + + self.sudo("apt-get install g++ gcc " + "protobuf-compiler libprotobuf-dev git --yes " + "pkg-config " + "libnl-3-dev libnl-genl-3-dev"), + self.sudo("rm -rf ~/irati"), + "cd ~; " + "export https_proxy=\"https://proxy.atlantis.ugent.be:8080\"; " + + "git clone https://github.com/IRATI/stack irati", + "cd ~/irati && git checkout arcfire", + "cd ~/irati && " + + self.sudo("./install-from-scratch")] for node in self.nodes: - ssh.execute_commands(self.testbed, node.ssh_config, + ssh.execute_proxy_commands(self.testbed, node.ssh_config, cmds, time_out=None) - def setup(self): - for node in self.nodes: - ssh.execute_command(self.testbed, node.ssh_config, - "sudo nohup ipcm &> ipcm.log &", - time_out=None) - def bootstrap_network(self): """Creates the network by enrolling and configuring the nodes""" for node in self.nodes: @@ -101,8 +101,6 @@ class Experiment(mod.Experiment): def bootstrap_prototype(self): logger.info("setting up") - self.setup() - logger.info("software initialized on all nodes") self.conf_files = self.write_conf() logger.info("configuration files generated for all nodes") self.bootstrap_network() @@ -144,7 +142,6 @@ class Experiment(mod.Experiment): 'genfiles': gen_files, 'genfilesconf': ' '.join(gen_files_conf), 'genfilesbin': gen_files_bin, - 'installpath': '/usr', 'verb': 'DBG', 'ipcmcomps': ipcm_components} @@ -155,13 +152,16 @@ class Experiment(mod.Experiment): '') cmds = [self.sudo('hostname %(name)s' % format_args), + self.sudo('modprobe rina-irati-core'), self.sudo('chmod a+rw /dev/irati'), self.sudo('mv %(genfilesconf)s /etc' % format_args), self.sudo('mv %(genfilesbin)s /usr/bin') % format_args, self.sudo('chmod a+x /usr/bin/enroll.py') % format_args] cmds += [self.sudo('modprobe rina-default-plugin'), - self.sudo('%(installpath)s/bin/ipcm -a \"%(ipcmcomps)s\" ' + self.sudo('modprobe shim-eth-vlan'), + self.sudo('modprobe normal-ipcp'), + self.sudo('ipcm -a \"%(ipcmcomps)s\" ' '-c /etc/%(name)s.ipcm.conf -l %(verb)s &> log &' % format_args)] @@ -170,6 +170,8 @@ class Experiment(mod.Experiment): def enroll_nodes(self): """Runs the enrollments one by one, respecting dependencies""" + logger.info("Waiting 5 seconds for the ipcm to start.") + time.sleep(5) for enrollment_list in self.enrollments: for e in enrollment_list: logger.info( @@ -225,16 +227,10 @@ class Experiment(mod.Experiment): next_vlan += 10 self.shim2vlan[dif.name] = vlan - # TODO: what format are the mappings registered in? Is this ok? - app_mappings = [] - for node in self.nodes: - app_mappings += [{'name': app, 'dif': self.dif_name(dif)} - for app in node.registrations - for dif in node.registrations[app]] - # If some app directives were specified, use those to build da.map. # Otherwise, assume the standard applications are to be mapped in # the DIF with the highest rank. + app_mappings = [] if len(app_mappings) == 0: if len(self.dif_ordering) > 0: for adm in \ diff --git a/rumba/prototypes/ouroboros.py b/rumba/prototypes/ouroboros.py index 9c164e7..9ac1425 100644 --- a/rumba/prototypes/ouroboros.py +++ b/rumba/prototypes/ouroboros.py @@ -32,6 +32,9 @@ class Experiment(mod.Experiment): def __init__(self, testbed, nodes=None): mod.Experiment.__init__(self, testbed, nodes) + def prototype_name(self): + return 'ouroboros' + def setup_ouroboros(self): for node in self.nodes: ssh.execute_command(self.testbed, node.ssh_config, @@ -50,27 +53,6 @@ class Experiment(mod.Experiment): ssh.execute_commands(self.testbed, node.ssh_config, cmds, time_out=None) - def bind_names(self): - for node in self.nodes: - cmds = list() - for name, ap in node.bindings.items(): - cmds.append("irm b ap " + ap + " n " + name) - - ssh.execute_commands(self.testbed, node.ssh_config, - cmds, time_out=None) - - def reg_names(self): - for node in self.nodes: - cmds = list() - for name, difs in node.registrations.items(): - cmd = "irm r n " + name - for dif in difs: - cmd += " dif " + dif.name - cmds.append(cmd) - - ssh.execute_commands(self.testbed, node.ssh_config, cmds, - time_out=None) - def create_ipcps(self): for node in self.nodes: cmds = list() @@ -147,12 +129,8 @@ class Experiment(mod.Experiment): def bootstrap_prototype(self): logger.info("Starting IRMd on all nodes...") self.setup_ouroboros() - logger.info("Binding names...") - self.bind_names() logger.info("Creating IPCPs") self.create_ipcps() logger.info("Enrolling IPCPs...") self.enroll_ipcps() - logger.info("Registering names...") - self.reg_names() logger.info("All done, have fun!") diff --git a/rumba/prototypes/rlite.py b/rumba/prototypes/rlite.py index 8a06b44..cc38255 100644 --- a/rumba/prototypes/rlite.py +++ b/rumba/prototypes/rlite.py @@ -34,21 +34,32 @@ class Experiment(mod.Experiment): def __init__(self, testbed, nodes=None): mod.Experiment.__init__(self, testbed, nodes) + def prototype_name(self): + return 'rlite' + def execute_commands(self, node, cmds): ssh.execute_commands(self.testbed, node.ssh_config, cmds, time_out=None) + def execute_proxy_commands(self, node, cmds): + ssh.execute_proxy_commands(self.testbed, node.ssh_config, + cmds, time_out=None) + + # Prepend sudo to all commands if the user is not 'root' + def may_sudo(self, cmds): + if self.testbed.username != 'root': + for i in range(len(cmds)): + cmds[i] = "sudo %s" % cmds[i] + def init_nodes(self): + # Load kernel modules and start the uipcps daemon cmds = ["modprobe rlite", "modprobe rlite-normal", "modprobe rlite-shim-eth", "modprobe rlite-shim-udp4", "modprobe rlite-shim-loopback", "rlite-uipcps -v DBG -k 0 &> uipcp.log &"] - - # Load kernel modules - - # Start the uipcps daemon + self.may_sudo(cmds) for node in self.nodes: self.execute_commands(node, cmds) @@ -80,6 +91,7 @@ class Experiment(mod.Experiment): cmds.append("rlite-ctl ipcp-config %s netdev %s" % (ipcp.name, ipcp.ifname)) + self.may_sudo(cmds) self.execute_commands(node, cmds) def register_ipcps(self): @@ -91,6 +103,7 @@ class Experiment(mod.Experiment): cmds.append("rlite-ctl ipcp-register %s %s" % (ipcp.name, lower.name)) + self.may_sudo(cmds) self.execute_commands(node, cmds) def enroll_ipcps(self): @@ -103,21 +116,25 @@ class Experiment(mod.Experiment): } cmd = "rlite-ctl ipcp-enroll %(enrollee)s %(dif)s "\ "%(lower_dif)s %(enroller)s" % d - self.execute_commands(e['enrollee'].node, [cmd]) + cmds = [cmd] + self.may_sudo(cmds) + self.execute_commands(e['enrollee'].node, cmds) time.sleep(1) def install_prototype(self): logger.info("installing rlite on all nodes") - cmds = ["apt-get update", - "apt-get install g++ gcc cmake " + cmds = ["sudo apt-get update", + "sudo -E apt-get install g++ gcc cmake " "linux-headers-$(uname -r) " "protobuf-compiler libprotobuf-dev git --yes", "rm -rf ~/rlite", "cd ~; git clone https://github.com/vmaffione/rlite", - "cd ~/rlite && ./configure && make && sudo make install"] + "cd ~/rlite && ./configure && make && sudo make install", + "cd ~/rlite && sudo make depmod" + ] for node in self.nodes: - self.execute_commands(node, cmds) + self.execute_proxy_commands(node, cmds) logger.info("installation complete") def bootstrap_prototype(self): diff --git a/rumba/ssh_support.py b/rumba/ssh_support.py index 26d64fb..a1e1ba4 100644 --- a/rumba/ssh_support.py +++ b/rumba/ssh_support.py @@ -43,6 +43,51 @@ def _print_stream(stream): return o +def execute_proxy_commands(testbed, ssh_config, commands, time_out=3): + """ + Remote execution of a list of shell command on hostname, using the + http and https proxy specified by the testbed. By + default this function will exit (timeout) after 3 seconds. + + @param testbed: testbed info + @param ssh_config: ssh config of the node + @param commands: *nix shell command + @param time_out: time_out value in seconds, error will be generated if + no result received in given number of seconds, the value None can + be used when no timeout is needed + """ + new_commands = [] + for command in commands: + proxy = testbed.http_proxy + if proxy is not None: + proxy_command = 'export http_proxy=' + proxy + '; ' \ + + 'export https_proxy=' + proxy + ';' + new_commands.append(proxy_command + ' ' + command) + else: + new_commands.append(command) + return execute_commands(testbed, ssh_config, new_commands, time_out) + + +def execute_proxy_command(testbed, ssh_config, command, time_out=3): + """ + Remote execution of a list of shell command on hostname, using + a proxy http and https. + By default this function will exit (timeout) after 3 seconds. + + @param testbed: testbed info + @param ssh_config: ssh config of the node + @param command: *nix shell command + @param time_out: time_out value in seconds, error will be generated if + no result received in given number of seconds, the value None can + be used when no timeout is needed + + @return: stdout resulting from the command + """ + o = execute_proxy_commands(testbed, ssh_config, [command], time_out) + if o is not None: + return o + + def execute_commands(testbed, ssh_config, commands, time_out=3): """ Remote execution of a list of shell command on hostname. By @@ -160,6 +205,11 @@ def copy_paths_to_testbed(testbed, ssh_config, paths, destination): """ ssh_client = get_ssh_client() + if ssh_config.proxycommand is not None: + proxy = paramiko.ProxyCommand(ssh_config.proxycommand) + else: + proxy = None + if destination is not '' and not destination.endswith('/'): destination = destination + '/' @@ -167,7 +217,8 @@ def copy_paths_to_testbed(testbed, ssh_config, paths, destination): ssh_client.connect(ssh_config.hostname, ssh_config.port, testbed.username, testbed.password, - look_for_keys=True) + look_for_keys=True, + sock=proxy) sftp_client = ssh_client.open_sftp() @@ -224,9 +275,9 @@ def setup_vlan(testbed, node, vlan_id, int_name): % args), sudo("ifconfig %(ifname)s.%(vlan)s up" % args)] - # TODO: is ethtool needed? Should install or check if it is present. - # cmds += [sudo("ethtool -K %(ifname)s rxvlan off" - # % args), - # sudo("ethtool -K %(ifname)s txvlan off" - # % args)] + if testbed.flags['no_vlan_offload']: + cmds += [sudo("ethtool -K %(ifname)s rxvlan off" + % args), + sudo("ethtool -K %(ifname)s txvlan off" + % args)] execute_commands(testbed, node.ssh_config, cmds) diff --git a/rumba/testbeds/jfed.py b/rumba/testbeds/jfed.py index 5394146..83fbce7 100644 --- a/rumba/testbeds/jfed.py +++ b/rumba/testbeds/jfed.py @@ -27,7 +27,7 @@ import tarfile import rumba.model as mod import rumba.log as log - +from rumba import ssh_support logger = log.get_logger(__name__) @@ -38,7 +38,12 @@ class Testbed(mod.Testbed): proj_name="ARCFIRE", authority="wall2.ilabt.iminds.be", image=None): passwd = getpass.getpass(prompt="Password for certificate file: ") - mod.Testbed.__init__(self, exp_name, username, passwd, proj_name) + mod.Testbed.__init__(self, + exp_name, + username, + passwd, + proj_name, + http_proxy="https://proxy.atlantis.ugent.be:8080") self.authority = "urn:publicid:IDN+" + authority + "+authority+cm" self.auth_name = authority self.cert_file = cert_file @@ -49,7 +54,7 @@ class Testbed(mod.Testbed): self.jfed_jar = "jfed_cli/experimenter-cli.jar" if image is not None: self.image = "urn:publicid:IDN+" + authority + \ - "+image+GeniSlices:" + image + "+image+wall2-ilabt-iminds-be:" + image else: self.image = None @@ -63,6 +68,7 @@ class Testbed(mod.Testbed): tar.close() logger.info("Extracted in current directory") os.remove(tarball) + self.flags['no_vlan_offload'] = True def create_rspec(self, experiment): impl = xml.getDOMImplementation() @@ -164,22 +170,55 @@ class Testbed(mod.Testbed): rspec = xml.parse(self.manifest) xml_nodes = rspec.getElementsByTagName("node") + dir_path = os.path.dirname(os.path.abspath(__file__)) + # Complete details of the nodes after swapin for xml_node in xml_nodes: n_name = xml_node.getAttribute("client_id") intfs = xml_node.getElementsByTagName("interface") + got = False for node in experiment.nodes: if node.name == n_name: node_n = node + got = True + if not got: + logger.error("Not found node %s", n_name) for intf in intfs: + aux_mac_address = intf.getAttribute("mac_address") + mac = ":".join( + [aux_mac_address[i:i+2] for i in range(0, 12, 2)] + ) + command = ( + 'echo "mac=\\"\$1\\"; cd / && ./sbin/ifconfig -a | ' + 'awk \'/^[a-z]/ { if ( \\"\'\\"\$mac\\"\'\\" == \$5 )' + ' print \$1}\'" > mac2ifname.sh') + ssh_support.execute_command(self, node_n.ssh_config, command) + + # ssh_support.copy_path_to_testbed( + # self, + # node_n.ssh_config, + # os.path.join(dir_path, 'mac2ifname.sh'), + # '') + ssh_support.execute_command( + self, + node_n.ssh_config, + 'cd ~ && chmod a+x mac2ifname.sh') + ifname = ssh_support.execute_command( + self, + node_n.ssh_config, + './mac2ifname.sh ' + mac + ) i_name = intf.getAttribute("client_id") for ipcp in node_n.ipcps: if isinstance(ipcp, mod.ShimEthIPCP): if self.if_id[ipcp] == i_name: - comp_id = intf.getAttribute("component_id") - comp_arr = comp_id.split(":") - ipcp.ifname = comp_arr[-1] + ipcp.ifname = ifname + logger.debug("Node %s interface %s has name %s." + % (node_n.name, mac, ifname)) + # comp_id = intf.getAttribute("component_id") + # comp_arr = comp_id.split(":") + # ipcp.ifname = comp_arr[-1] # xml_ip = intf.getElementsByTagName("ip") # interface.ip = xml_ip[0].getAttribute("address") diff --git a/rumba/testbeds/qemu.py b/rumba/testbeds/qemu.py index a916525..df02ab6 100644 --- a/rumba/testbeds/qemu.py +++ b/rumba/testbeds/qemu.py @@ -26,24 +26,25 @@ import os import rumba.model as mod import rumba.log as log import rumba.ssh_support as ssh_support +import wget logger = log.get_logger(__name__) class Testbed(mod.Testbed): - def __init__(self, exp_name, bzimage, initramfs, proj_name="ARCFIRE", + def __init__(self, exp_name, bzimage=None, initramfs=None, proj_name="ARCFIRE", password="root", username="root", use_vhost=True, qemu_logs_dir=None): mod.Testbed.__init__(self, exp_name, username, password, proj_name) self.vms = {} self.shims = [] - self.bzimage = bzimage - self.initramfs = initramfs self.vhost = use_vhost self.qemu_logs_dir = os.getcwd() if qemu_logs_dir is None \ else qemu_logs_dir self.boot_processes = [] + self.bzimage = bzimage + self.initramfs = initramfs @staticmethod def _run_command_chain(commands, results_queue, @@ -110,10 +111,34 @@ class Testbed(mod.Testbed): if os.geteuid() != 0: try: subprocess.check_call(["sudo", "-v"]) + if not os.access("/dev/vhost-net", os.R_OK) \ + or not os.access("/dev/vhost-net", os.W_OK) \ + or not os.access("/dev/kvm", os.R_OK) \ + or not os.access("/dev/kvm", os.W_OK): + raise Exception('Cannot open vhost device. Make sure it is' + 'available and you have rw permissions ' + 'on /dev/vhost-net') except subprocess.CalledProcessError: raise Exception('Not authenticated') logger.info("swapping in") + + # Download the proper buildroot images, if the user did not specify + # local images + url_prefix = "https://bitbucket.org/vmaffione/rina-images/downloads/" + if not self.bzimage: + self.bzimage = '%s.bzImage' % (experiment.prototype_name()) + if not os.path.exists(self.bzimage): + logger.info("Downloading %s" % (url_prefix + self.bzimage)) + wget.download(url_prefix + self.bzimage) + print("\n") + if not self.initramfs: + self.initramfs = '%s.rootfs.cpio' % (experiment.prototype_name()) + if not os.path.exists(self.initramfs): + logger.info("Downloading %s" % (url_prefix + self.initramfs)) + wget.download(url_prefix + self.initramfs) + print("\n") + logger.info('Setting up interfaces.') # Building bridges and taps @@ -11,7 +11,7 @@ with open(path.join(here, 'README.md'), encoding='utf-8') as f: setup( name="Rumba", - version="0.1", + version="0.3", url="https://gitlab.com/arcfire/rumba", keywords="rina measurement testbed", author="Sander Vrijders", @@ -20,5 +20,6 @@ setup( description="Rumba measurement framework for RINA", long_description=long_description, packages=["rumba", "rumba.testbeds", "rumba.prototypes"], - install_requires=["paramiko", "wheel", "wget"] + install_requires=["paramiko", "wheel", "wget"], + scripts = ['tools/rumba-access'] ) diff --git a/tools/democonf2rumba.py b/tools/democonf2rumba.py index a73d6b7..dc2f0a4 100755 --- a/tools/democonf2rumba.py +++ b/tools/democonf2rumba.py @@ -24,6 +24,8 @@ def make_experiment(filename, experiment_class, experiment_kwargs, difs = {} print('Reading file %s.' % (filename,)) + print('+++++++++++++++++++') + print() with open(filename, 'r') as conf: @@ -120,6 +122,12 @@ def make_experiment(filename, experiment_class, experiment_kwargs, parsed_nodes.append(mod.Node(name, difs, dif_registrations)) log.set_logging_level(verbosity) + print() + print('++++++++++++++++++++') + print('Calling constructor of testbed %s with args %s.' + % (testbed_class, testbed_kwargs)) + print('++++++++++++++++++++') + print() testbed = testbed_class(**testbed_kwargs) @@ -130,6 +138,7 @@ def make_experiment(filename, experiment_class, experiment_kwargs, try: exp.swap_in() exp.bootstrap_prototype() + input("Press ENTER to quit") finally: exp.swap_out() @@ -200,14 +209,14 @@ if __name__ == '__main__': jfed_p.add_argument('-C', '--cert_file', metavar='CERT', type=str, required=True, help='Certificate file') - jfed_p.add_argument('-J', '--jar', metavar='JAR', type=str, - required=True, - help='Jfed jar') - jfed_p.add_argument('-H', '--exp_hours', metavar='HOURS', type=int, - default=2, help='Experiment hours') + jfed_p.add_argument('-H', '--exp_hours', metavar='HOURS', type=str, + default="2", help='Experiment hours') jfed_p.add_argument('-A', '--authority', metavar='AUTH', type=str, default="wall2.ilabt.iminds.be", help='Authority') + jfed_p.add_argument('-I', '--image', metavar='IMAGE', type=str, + default=None, + help='Image to be used') args = parser.parse_args() diff --git a/tools/rumba-access b/tools/rumba-access new file mode 100755 index 0000000..1cf382e --- /dev/null +++ b/tools/rumba-access @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +FILE=ssh_info + +MACHINE_ID=$1 +if [ "$MACHINE_ID" == "" ]; then + echo "usage: $0 NODE_NAME" + exit 255 +fi + +USER=$(grep "\<${MACHINE_ID}\>" ${FILE} | awk -F';' '{print $2}') +if [ "$USER" == "" ]; then + echo "Error: Node ${MACHINE_ID} unknown" + exit 255 +fi + +HOST=$(grep "\<${MACHINE_ID}\>" ${FILE} | awk -F';' '{print $3}') +if [ "$HOST" == "" ]; then + echo "Error: Node ${MACHINE_ID} unknown" + exit 255 +fi + +SSH_PORT=$(grep "\<${MACHINE_ID}\>" ${FILE} | awk -F';' '{print $4}') +if [ "$SSH_PORT" == "" ]; then + echo "Error: Node ${MACHINE_ID} unknown" + exit 255 +fi + +PROXY_CMD=$(grep "\<${MACHINE_ID}\>" ${FILE} | awk -F';' '{print $5}') +if [ "$PROXY_CMD" == "" ]; then + echo "Error: Node ${MACHINE_ID} unknown" + exit 255 +fi + +echo "Accessing Rumba node ${MACHINE_ID}" +if [[ $PROXY_CMD = "None" ]]; then + ssh -A -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p ${SSH_PORT} ${USER}@${HOST} +else + ssh -A -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -oProxyCommand="$PROXY_CMD" -p ${SSH_PORT} ${USER}@${HOST} +fi |