X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=src%2Fnepi%2Ftestbeds%2Fplanetlab%2Fmetadata.py;h=e69f3e70ab98de61124e6b0a8e6562e57e0f2a99;hb=a1c0cc97b3fc6c5aba2e519cc846426a3d56cbfb;hp=e20d328b626f1b6369d7e482658a74e133b066cd;hpb=a88d5281ddf098d16eb223c7b95866cfdc186d38;p=nepi.git diff --git a/src/nepi/testbeds/planetlab/metadata.py b/src/nepi/testbeds/planetlab/metadata.py index e20d328b..e69f3e70 100644 --- a/src/nepi/testbeds/planetlab/metadata.py +++ b/src/nepi/testbeds/planetlab/metadata.py @@ -1,30 +1,43 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- import time from constants import TESTBED_ID, TESTBED_VERSION from nepi.core import metadata +from nepi.core.metadata import Parallel from nepi.core.attributes import Attribute from nepi.util import tags, validation from nepi.util.constants import ApplicationStatus as AS, \ FactoryCategories as FC, \ - ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP + ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, \ + DeploymentConfiguration as DC import functools import os import os.path +import weakref NODE = "Node" NODEIFACE = "NodeInterface" TUNIFACE = "TunInterface" TAPIFACE = "TapInterface" APPLICATION = "Application" +CCNXDAEMON = "CCNxDaemon" DEPENDENCY = "Dependency" NEPIDEPENDENCY = "NepiDependency" NS3DEPENDENCY = "NS3Dependency" INTERNET = "Internet" NETPIPE = "NetPipe" +TUNFILTER = "TunFilter" +CLASSQUEUEFILTER = "ClassQueueFilter" +TOSQUEUEFILTER = "TosQueueFilter" +MULTICASTFORWARDER = "MulticastForwarder" +MULTICASTANNOUNCER = "MulticastAnnouncer" +MULTICASTROUTER = "MulticastRouter" + +TUNFILTERS = (TUNFILTER, CLASSQUEUEFILTER, TOSQUEUEFILTER) +TAPFILTERS = (TUNFILTER, ) +ALLFILTERS = (TUNFILTER, CLASSQUEUEFILTER, TOSQUEUEFILTER) PL_TESTBED_ID = "planetlab" @@ -93,26 +106,62 @@ def connect_node_iface_inet(testbed_instance, iface_guid, inet_guid): def connect_tun_iface_node(testbed_instance, node_guid, iface_guid): node = testbed_instance._elements[node_guid] iface = testbed_instance._elements[iface_guid] - if not node.emulation: - raise RuntimeError, "Use of TUN interfaces requires emulation" iface.node = node - node.required_vsys.update(('fd_tuntap', 'vif_up')) + node.required_vsys.update(('fd_tuntap', 'vif_up', 'vif_down')) node.required_packages.update(('python', 'python-crypto', 'python-setuptools', 'gcc')) def connect_tun_iface_peer(proto, testbed_instance, iface_guid, peer_iface_guid): iface = testbed_instance._elements[iface_guid] peer_iface = testbed_instance._elements[peer_iface_guid] iface.peer_iface = peer_iface + peer_iface.peer_iface = iface iface.peer_proto = \ - iface.tun_proto = proto + iface.tun_proto = \ + peer_iface.peer_proto = \ + peer_iface.tun_proto = proto iface.tun_key = peer_iface.tun_key +def connect_tun_iface_filter(testbed_instance, iface_guid, filter_guid): + iface = testbed_instance._elements[iface_guid] + filt = testbed_instance._elements[filter_guid] + traces = testbed_instance._get_traces(filter_guid) + if 'dropped_stats' in traces: + args = filt.args if filt.args else "" + filt.args = ','.join(filt.args.split(',') + ["logdropped=true",]) + iface.filter_module = filt + filt.iface_guid = iface_guid + filt.iface = weakref.ref(iface) + + if filt.peer_guid: + connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid) + +def connect_filter_peer(proto, testbed_instance, filter_guid, peer_guid): + peer = testbed_instance._elements[peer_guid] + filt = testbed_instance._elements[filter_guid] + filt.peer_proto = proto + filt.peer_guid = peer_guid + if filt.iface_guid: + connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid) + +def connect_filter_filter(proto, testbed_instance, filter_guid, peer_guid): + peer = testbed_instance._elements[peer_guid] + filt = testbed_instance._elements[filter_guid] + filt.peer_proto = proto + peer.peer_proto = proto + if filt.iface_guid: + peer.peer_guid = filt.iface_guid + if peer.iface_guid: + filt.peer_guid = peer.iface_guid + if filt.iface_guid and filt.peer_guid: + connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid) + def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data): iface = testbed_instance._elements[iface_guid] iface.peer_iface = None iface.peer_addr = peer_iface_data.get("tun_addr") iface.peer_proto = peer_iface_data.get("tun_proto") or proto iface.peer_port = peer_iface_data.get("tun_port") + iface.peer_cipher = peer_iface_data.get("tun_cipher") iface.tun_key = min(iface.tun_key, peer_iface_data.get("tun_key")) iface.tun_proto = proto @@ -124,6 +173,7 @@ def crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_ iface.peer_addr = peer_iface_data.get("tun_addr") iface.peer_proto = peer_iface_data.get("tun_proto") or proto iface.peer_port = peer_iface_data.get("tun_port") + iface.peer_cipher = peer_iface_data.get("tun_cipher") postconfigure_tuniface(testbed_instance, iface_guid) @@ -131,15 +181,29 @@ def crossconnect_tun_iface_peer_both(proto, testbed_instance, iface_guid, peer_i crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data) crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data) -def connect_dep(testbed_instance, node_guid, app_guid): - node = testbed_instance._elements[node_guid] - app = testbed_instance._elements[app_guid] +def crossconnect_filter_peer_init(proto, testbed_instance, filter_guid, peer_data): + filt = testbed_instance._elements[filter_guid] + filt.peer_proto = proto + crossconnect_tun_iface_peer_init(filt.peer_proto, testbed_instance, filt.iface_guid, peer_data) + +def crossconnect_filter_peer_compl(proto, testbed_instance, filter_guid, peer_data): + filt = testbed_instance._elements[filter_guid] + filt.peer_proto = proto + crossconnect_tun_iface_peer_compl(filt.peer_proto, testbed_instance, filt.iface_guid, peer_data) + +def crossconnect_filter_peer_both(proto, testbed_instance, filter_guid, peer_data): + crossconnect_filter_peer_init(proto, testbed_instance, iface_guid, peer_iface_data) + crossconnect_filter_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data) + +def connect_dep(testbed_instance, node_guid, app_guid, node=None, app=None): + node = node or testbed_instance._elements[node_guid] + app = app or testbed_instance._elements[app_guid] app.node = node if app.depends: node.required_packages.update(set( app.depends.split() )) - + if app.add_to_path: if app.home_path and app.home_path not in node.pythonpath: node.pythonpath.append(app.home_path) @@ -152,12 +216,30 @@ def connect_dep(testbed_instance, node_guid, app_guid): if app.rpmFusion: node.rpmFusion = True +def connect_forwarder(testbed_instance, node_guid, fwd_guid): + node = testbed_instance._elements[node_guid] + fwd = testbed_instance._elements[fwd_guid] + node.multicast_forwarder = fwd + + if fwd.router: + connect_dep(testbed_instance, node_guid, None, app=fwd.router) + + connect_dep(testbed_instance, node_guid, fwd_guid) + +def connect_router(testbed_instance, fwd_guid, router_guid): + fwd = testbed_instance._elements[fwd_guid] + router = testbed_instance._elements[router_guid] + fwd.router = router + + if fwd.node: + connect_dep(testbed_instance, None, router_guid, node=fwd.node) + def connect_node_netpipe(testbed_instance, node_guid, netpipe_guid): node = testbed_instance._elements[node_guid] netpipe = testbed_instance._elements[netpipe_guid] - if not node.emulation: - raise RuntimeError, "Use of NetPipes requires emulation" netpipe.node = node + node.required_vsys.add('ipfw-be') + node.required_packages.add('ipfwslice') ### Creation functions ### @@ -179,7 +261,9 @@ def create_node(testbed_instance, guid): # require vroute vsys if we have routes to set up routes = testbed_instance._add_route.get(guid) if routes: - element.required_vsys.add("vroute") + vsys = element.routing_method(routes, + testbed_instance.vsys_vnet) + element.required_vsys.add(vsys) testbed_instance.elements[guid] = element @@ -216,6 +300,21 @@ def create_tapiface(testbed_instance, guid): testbed_instance.elements[guid] = element +def create_tunfilter(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_tun_filter(parameters) + testbed_instance.elements[guid] = element + +def create_classqueuefilter(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_class_queue_filter(parameters) + testbed_instance.elements[guid] = element + +def create_tosqueuefilter(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_tos_queue_filter(parameters) + testbed_instance.elements[guid] = element + def create_application(testbed_instance, guid): parameters = testbed_instance._get_parameters(guid) element = testbed_instance._make_application(parameters) @@ -225,6 +324,16 @@ def create_application(testbed_instance, guid): testbed_instance.elements[guid] = element +def create_ccnxdaemon(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_application(parameters, + clazz = testbed_instance._app.CCNxDaemon ) + + # Just inject configuration stuff + element.home_path = "nepi-ccnd-%s" % (guid,) + + testbed_instance.elements[guid] = element + def create_dependency(testbed_instance, guid): parameters = testbed_instance._get_parameters(guid) element = testbed_instance._make_dependency(parameters) @@ -252,6 +361,33 @@ def create_ns3_dependency(testbed_instance, guid): testbed_instance.elements[guid] = element +def create_multicast_forwarder(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_multicast_forwarder(parameters) + + # Just inject configuration stuff + element.home_path = "nepi-mcfwd-%s" % (guid,) + + testbed_instance.elements[guid] = element + +def create_multicast_announcer(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_multicast_announcer(parameters) + + # Just inject configuration stuff + element.home_path = "nepi-mcann-%s" % (guid,) + + testbed_instance.elements[guid] = element + +def create_multicast_router(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_multicast_router(parameters) + + # Just inject configuration stuff + element.home_path = "nepi-mcrt-%s" % (guid,) + + testbed_instance.elements[guid] = element + def create_internet(testbed_instance, guid): parameters = testbed_instance._get_parameters(guid) element = testbed_instance._make_internet(parameters) @@ -264,6 +400,15 @@ def create_netpipe(testbed_instance, guid): ### Start/Stop functions ### +def prestart_ccnxdaemon(testbed_instance, guid): + # ccnx daemon needs to start before the rest of the + # ccn applications + start_application(testbed_instance, guid) + +def stop_ccndaemon(testbed_instance, guid): + app = testbed_instance.elements[guid] + app.kill() + def start_application(testbed_instance, guid): parameters = testbed_instance._get_parameters(guid) traces = testbed_instance._get_traces(guid) @@ -272,6 +417,7 @@ def start_application(testbed_instance, guid): app.stdout = "stdout" in traces app.stderr = "stderr" in traces app.buildlog = "buildlog" in traces + app.outout = "output" in traces app.start() @@ -329,7 +475,7 @@ def preconfigure_tuniface(testbed_instance, guid): # Set standard TUN attributes if (not element.tun_addr or not element.tun_port) and element.external_iface: element.tun_addr = element.external_iface.address - element.tun_port = 15000 + int(guid) + element.tun_port = testbed_instance.tapPortBase + int(guid) # Set enabled traces traces = testbed_instance._get_traces(guid) @@ -344,36 +490,19 @@ def preconfigure_tuniface(testbed_instance, guid): element.validate() # First-phase setup - if element.peer_proto: - if element.peer_iface and isinstance(element.peer_iface, testbed_instance._interfaces.TunIface): - # intra tun - listening = id(element) < id(element.peer_iface) - else: - # cross tun - if not element.tun_addr or not element.tun_port: - listening = True - elif not element.peer_addr or not element.peer_port: - listening = True - else: - # both have addresses... - # ...the one with the lesser address listens - listening = element.tun_addr < element.peer_addr - element.prepare( - 'tun-%s' % (guid,), - listening) + element.prepare('tun-%s' % (guid,)) def postconfigure_tuniface(testbed_instance, guid): element = testbed_instance._elements[guid] # Second-phase setup - element.setup() + element.launch() -def wait_tuniface(testbed_instance, guid): +def prestart_tuniface(testbed_instance, guid): element = testbed_instance._elements[guid] # Second-phase setup - element.async_launch_wait() - + element.wait() def configure_node(testbed_instance, guid): node = testbed_instance._elements[guid] @@ -399,8 +528,10 @@ def configure_node_routes(testbed_instance, guid): for dev_guid in testbed_instance.get_connected(guid, "devs", "node") for dev in ( testbed_instance._elements.get(dev_guid) ,) if dev and isinstance(dev, testbed_instance._interfaces.TunIface) ] + + vsys = testbed_instance.vsys_vnet - node.configure_routes(routes, devs) + node.configure_routes(routes, devs, vsys) def configure_application(testbed_instance, guid): app = testbed_instance._elements[guid] @@ -426,6 +557,41 @@ def configure_dependency(testbed_instance, guid): # Install stuff dep.async_setup() +def configure_announcer(testbed_instance, guid): + # Link ifaces + fwd = testbed_instance._elements[guid] + fwd.ifaces = [ dev + for node_guid in testbed_instance.get_connected(guid, "node", "apps") + for dev_guid in testbed_instance.get_connected(node_guid, "devs", "node") + for dev in ( testbed_instance._elements.get(dev_guid) ,) + if dev and isinstance(dev, testbed_instance._interfaces.TunIface) + and dev.multicast ] + + # Install stuff + configure_dependency(testbed_instance, guid) + +def configure_forwarder(testbed_instance, guid): + configure_announcer(testbed_instance, guid) + + # Link ifaces to forwarder + fwd = testbed_instance._elements[guid] + for iface in fwd.ifaces: + iface.multicast_forwarder = '/var/run/mcastfwd' + +def configure_router(testbed_instance, guid): + # Link ifaces + rt = testbed_instance._elements[guid] + rt.nonifaces = [ dev + for fwd_guid in testbed_instance.get_connected(guid, "fwd", "router") + for node_guid in testbed_instance.get_connected(fwd_guid, "node", "apps") + for dev_guid in testbed_instance.get_connected(node_guid, "devs", "node") + for dev in ( testbed_instance._elements.get(dev_guid) ,) + if dev and isinstance(dev, testbed_instance._interfaces.TunIface) + and not dev.multicast ] + + # Install stuff + configure_dependency(testbed_instance, guid) + def configure_netpipe(testbed_instance, guid): netpipe = testbed_instance._elements[guid] @@ -472,6 +638,18 @@ connector_types = dict({ "max": 1, "min": 1 }), + "router": dict({ + "help": "Connector to a routing daemon", + "name": "router", + "max": 1, + "min": 0 + }), + "fwd": dict({ + "help": "Forwarder this routing daemon communicates with", + "name": "fwd", + "max": 1, + "min": 1 + }), "pipes": dict({ "help": "Connector to a NetPipe", "name": "pipes", @@ -491,12 +669,24 @@ connector_types = dict({ "max": 1, "min": 0 }), + "gre": dict({ + "help": "IP or Ethernet tunneling using the GRE protocol", + "name": "gre", + "max": 1, + "min": 0 + }), "fd->": dict({ "help": "TUN device file descriptor provider", "name": "fd->", "max": 1, "min": 0 }), + "->fd": dict({ + "help": "TUN device file descriptor slot", + "name": "->fd", + "max": 1, + "min": 0 + }), }) connections = [ @@ -526,32 +716,32 @@ connections = [ }), dict({ "from": (TESTBED_ID, NODE, "apps"), - "to": (TESTBED_ID, APPLICATION, "node"), + "to": (TESTBED_ID, (APPLICATION, CCNXDAEMON, MULTICASTANNOUNCER), "node"), "init_code": connect_dep, "can_cross": False }), dict({ "from": (TESTBED_ID, NODE, "deps"), - "to": (TESTBED_ID, DEPENDENCY, "node"), + "to": (TESTBED_ID, (DEPENDENCY, NEPIDEPENDENCY, NS3DEPENDENCY), "node"), "init_code": connect_dep, "can_cross": False }), dict({ - "from": (TESTBED_ID, NODE, "deps"), - "to": (TESTBED_ID, NEPIDEPENDENCY, "node"), - "init_code": connect_dep, + "from": (TESTBED_ID, NODE, "pipes"), + "to": (TESTBED_ID, NETPIPE, "node"), + "init_code": connect_node_netpipe, "can_cross": False }), dict({ - "from": (TESTBED_ID, NODE, "deps"), - "to": (TESTBED_ID, NS3DEPENDENCY, "node"), - "init_code": connect_dep, + "from": (TESTBED_ID, NODE, "apps"), + "to": (TESTBED_ID, MULTICASTFORWARDER, "node"), + "init_code": connect_forwarder, "can_cross": False }), dict({ - "from": (TESTBED_ID, NODE, "pipes"), - "to": (TESTBED_ID, NETPIPE, "node"), - "init_code": connect_node_netpipe, + "from": (TESTBED_ID, MULTICASTFORWARDER, "router"), + "to": (TESTBED_ID, MULTICASTROUTER, "fwd"), + "init_code": connect_router, "can_cross": False }), dict({ @@ -566,6 +756,30 @@ connections = [ "init_code": functools.partial(connect_tun_iface_peer,"udp"), "can_cross": False }), + dict({ + "from": (TESTBED_ID, TUNIFACE, "gre"), + "to": (TESTBED_ID, TUNIFACE, "gre"), + "init_code": functools.partial(connect_tun_iface_peer,"gre"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TUNIFACE, "fd->"), + "to": (TESTBED_ID, TUNFILTERS, "->fd"), + "init_code": connect_tun_iface_filter, + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TUNFILTERS, "tcp"), + "to": (TESTBED_ID, TUNIFACE, "tcp"), + "init_code": functools.partial(connect_filter_peer,"tcp"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TUNFILTERS, "udp"), + "to": (TESTBED_ID, TUNIFACE, "udp"), + "init_code": functools.partial(connect_filter_peer,"udp"), + "can_cross": False + }), dict({ "from": (TESTBED_ID, TAPIFACE, "tcp"), "to": (TESTBED_ID, TAPIFACE, "tcp"), @@ -578,6 +792,54 @@ connections = [ "init_code": functools.partial(connect_tun_iface_peer,"udp"), "can_cross": False }), + dict({ + "from": (TESTBED_ID, TAPIFACE, "gre"), + "to": (TESTBED_ID, TAPIFACE, "gre"), + "init_code": functools.partial(connect_tun_iface_peer,"gre"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TAPIFACE, "fd->"), + "to": (TESTBED_ID, TAPFILTERS, "->fd"), + "init_code": connect_tun_iface_filter, + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TAPFILTERS, "tcp"), + "to": (TESTBED_ID, TAPIFACE, "tcp"), + "init_code": functools.partial(connect_filter_peer,"tcp"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TAPFILTERS, "udp"), + "to": (TESTBED_ID, TAPIFACE, "udp"), + "init_code": functools.partial(connect_filter_peer,"udp"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TUNFILTERS, "tcp"), + "to": (TESTBED_ID, TUNFILTERS, "tcp"), + "init_code": functools.partial(connect_filter_filter,"tcp"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TUNFILTERS, "udp"), + "to": (TESTBED_ID, TUNFILTERS, "udp"), + "init_code": functools.partial(connect_filter_filter,"udp"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TAPFILTERS, "tcp"), + "to": (TESTBED_ID, TAPFILTERS, "tcp"), + "init_code": functools.partial(connect_filter_filter,"tcp"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TAPFILTERS, "udp"), + "to": (TESTBED_ID, TAPFILTERS, "udp"), + "init_code": functools.partial(connect_filter_filter,"udp"), + "can_cross": False + }), dict({ "from": (TESTBED_ID, TUNIFACE, "tcp"), "to": (None, None, "tcp"), @@ -598,6 +860,12 @@ connections = [ "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"), "can_cross": True }), + dict({ + "from": (TESTBED_ID, TUNIFACE, "gre"), + "to": (None, None, "gre"), + "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"), + "can_cross": True + }), dict({ "from": (TESTBED_ID, TAPIFACE, "tcp"), "to": (None, None, "tcp"), @@ -618,6 +886,28 @@ connections = [ "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"), "can_cross": True }), + # EGRE is an extension of PlanetLab, so we can't connect externally + # if the other testbed isn't another PlanetLab + dict({ + "from": (TESTBED_ID, TAPIFACE, "gre"), + "to": (TESTBED_ID, None, "gre"), + "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"), + "can_cross": True + }), + dict({ + "from": (TESTBED_ID, ALLFILTERS, "tcp"), + "to": (None, None, "tcp"), + "init_code": functools.partial(crossconnect_filter_peer_init,"tcp"), + "compl_code": functools.partial(crossconnect_filter_peer_compl,"tcp"), + "can_cross": True + }), + dict({ + "from": (TESTBED_ID, ALLFILTERS, "udp"), + "to": (None, None, "udp"), + "init_code": functools.partial(crossconnect_filter_peer_init,"udp"), + "compl_code": functools.partial(crossconnect_filter_peer_compl,"udp"), + "can_cross": True + }), ] attributes = dict({ @@ -636,6 +926,27 @@ attributes = dict({ "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, "validation_function": validation.is_string, }), + "city": dict({ + "name": "city", + "help": "Constrain location (city) during resource discovery. May use wildcards.", + "type": Attribute.STRING, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_string, + }), + "country": dict({ + "name": "hostname", + "help": "Constrain location (country) during resource discovery. May use wildcards.", + "type": Attribute.STRING, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_string, + }), + "region": dict({ + "name": "hostname", + "help": "Constrain location (region) during resource discovery. May use wildcards.", + "type": Attribute.STRING, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_string, + }), "architecture": dict({ "name": "architecture", "help": "Constrain architexture during resource discovery.", @@ -667,21 +978,13 @@ attributes = dict({ "PLJ"], "validation_function": validation.is_enum, }), - "emulation": dict({ - "name": "emulation", - "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.", - "type": Attribute.BOOL, - "value": False, - "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, - "validation_function": validation.is_bool, - }), "min_reliability": dict({ "name": "minReliability", "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.", "type": Attribute.DOUBLE, "range": (0,100), "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, - "validation_function": validation.is_double, + "validation_function": validation.is_number, }), "max_reliability": dict({ "name": "maxReliability", @@ -689,7 +992,7 @@ attributes = dict({ "type": Attribute.DOUBLE, "range": (0,100), "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, - "validation_function": validation.is_double, + "validation_function": validation.is_number, }), "min_bandwidth": dict({ "name": "minBandwidth", @@ -697,7 +1000,7 @@ attributes = dict({ "type": Attribute.DOUBLE, "range": (0,2**31), "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, - "validation_function": validation.is_double, + "validation_function": validation.is_number, }), "max_bandwidth": dict({ "name": "maxBandwidth", @@ -705,7 +1008,39 @@ attributes = dict({ "type": Attribute.DOUBLE, "range": (0,2**31), "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, - "validation_function": validation.is_double, + "validation_function": validation.is_number, + }), + "min_load": dict({ + "name": "minLoad", + "help": "Constrain node load average while picking PlanetLab nodes. Specifies a lower acceptable bound.", + "type": Attribute.DOUBLE, + "range": (0,2**31), + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_number, + }), + "max_load": dict({ + "name": "maxLoad", + "help": "Constrain node load average while picking PlanetLab nodes. Specifies an upper acceptable bound.", + "type": Attribute.DOUBLE, + "range": (0,2**31), + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_number, + }), + "min_cpu": dict({ + "name": "minCpu", + "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies a lower acceptable bound.", + "type": Attribute.DOUBLE, + "range": (0,100), + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_number, + }), + "max_cpu": dict({ + "name": "maxCpu", + "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies an upper acceptable bound.", + "type": Attribute.DOUBLE, + "range": (0,100), + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_number, }), "up": dict({ @@ -722,8 +1057,8 @@ attributes = dict({ "value": True, "validation_function": validation.is_bool }), - "device_name": dict({ - "name": "name", + "if_name": dict({ + "name": "if_name", "help": "Device name", "type": Attribute.STRING, "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, @@ -749,6 +1084,15 @@ attributes = dict({ "value": False, "validation_function": validation.is_bool }), + "multicast": dict({ + "name": "multicast", + "help": "Enable multicast forwarding on this device. " + "Note that you still need a multicast routing daemon " + "in the node.", + "type": Attribute.BOOL, + "value": False, + "validation_function": validation.is_bool + }), "pointopoint": dict({ "name": "pointopoint", "help": "If the interface is a P2P link, the remote endpoint's IP " @@ -757,10 +1101,19 @@ attributes = dict({ "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, "validation_function": validation.is_string }), + "bwlimit": dict({ + "name": "bwlimit", + "help": "Emulated transmission speed (in kbytes per second)", + "type": Attribute.INTEGER, + "range" : (1,10*2**20), + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_integer + }), "txqueuelen": dict({ - "name": "mask", + "name": "txqueuelen", "help": "Transmission queue length (in packets)", "type": Attribute.INTEGER, + "value": 1000, "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, "range" : (1,10000), "validation_function": validation.is_integer @@ -773,7 +1126,14 @@ attributes = dict({ "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, "validation_function": validation.is_string }), - "sudo": dict({ + "ccnroutes": dict({ + "name": "ccnRoutes", + "help": "Route can be static (e.g. udp ip) or multicast (e.g. udp 224.0.0.204 2869). To separate different route use '|' ", + "type": Attribute.STRING, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_string + }), + "sudo": dict({ "name": "sudo", "help": "Run with root privileges", "type": Attribute.BOOL, @@ -819,6 +1179,26 @@ attributes = dict({ "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, "validation_function": validation.is_string }), + "ccnxversion": dict({ + "name": "ccnxVersion", + "help": "Version of ccnx source code to install in the node.", + "type": Attribute.ENUM, + "value": "ccnx-0.6.0", + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "allowed": ["ccnx-0.6.0", + "ccnx-0.5.1"], + "validation_function": validation.is_enum, + }), + "ccnlocalport" : dict({ + "name" : "ccnLocalPort", + "help" : "Local port to bind the ccn daemon. (i.e. CCN_LOCAL_PORT=)", + "type" : Attribute.INTEGER, + "flags" : Attribute.DesignInvisible | \ + Attribute.ExecInvisible | \ + Attribute.ExecImmutable | \ + Attribute.Metadata, + "validation_function" : validation.is_integer, + }), "build": dict({ "name": "build", "help": "Build commands to execute after deploying the sources. " @@ -880,25 +1260,25 @@ attributes = dict({ "name": "bwIn", "help": "Inbound bandwidth limit (in Mbit/s)", "type": Attribute.DOUBLE, - "validation_function": validation.is_double, + "validation_function": validation.is_number, }), "bw_out": dict({ "name": "bwOut", "help": "Outbound bandwidth limit (in Mbit/s)", "type": Attribute.DOUBLE, - "validation_function": validation.is_double, + "validation_function": validation.is_number, }), "plr_in": dict({ "name": "plrIn", "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)", "type": Attribute.DOUBLE, - "validation_function": validation.is_double, + "validation_function": validation.is_number, }), "plr_out": dict({ "name": "plrOut", "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)", "type": Attribute.DOUBLE, - "validation_function": validation.is_double, + "validation_function": validation.is_number, }), "delay_in": dict({ "name": "delayIn", @@ -914,6 +1294,29 @@ attributes = dict({ "range": (0,60000), "validation_function": validation.is_integer, }), + "module": dict({ + "name": "module", + "help": "Path to a .c or .py source for a filter module, or a binary .so", + "type": Attribute.STRING, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_string + }), + "args": dict({ + "name": "args", + "help": "Module arguments - comma-separated list of name=value pairs", + "type": Attribute.STRING, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_string + }), + "routing_algorithm": dict({ + "name": "algorithm", + "help": "Routing algorithm.", + "value": "dvmrp", + "type": Attribute.ENUM, + "allowed": ["dvmrp"], + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_enum, + }), }) traces = dict({ @@ -943,14 +1346,47 @@ traces = dict({ "name": "pcap", "help": "PCAP trace of all packets going through the interface", }), + "output": dict({ + "name": "output", + "help": "Extra output trace for applications. When activated this trace can be referenced with wildcard a reference from an Application command line. Ex: command: 'tcpdump -w {#[elemet-label].trace[trace-id].[name|path]#}' ", + }), + "dropped_stats": dict({ + "name": "dropped_stats", + "help": "Information on dropped packets on a filer or queue associated to a network interface", + }), }) -create_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ] +create_order = [ + INTERNET, NODE, NODEIFACE, CLASSQUEUEFILTER, TOSQUEUEFILTER, + MULTICASTANNOUNCER, MULTICASTFORWARDER, MULTICASTROUTER, + TUNFILTER, TAPIFACE, TUNIFACE, NETPIPE, + NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, CCNXDAEMON, APPLICATION ] -configure_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ] +configure_order = [ + INTERNET, Parallel(NODE), + NODEIFACE, + Parallel(MULTICASTANNOUNCER), Parallel(MULTICASTFORWARDER), Parallel(MULTICASTROUTER), + Parallel(TAPIFACE), Parallel(TUNIFACE), NETPIPE, + Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(CCNXDAEMON), + Parallel(APPLICATION)] # Start (and prestart) node after ifaces, because the node needs the ifaces in order to set up routes -start_order = [ INTERNET, NODEIFACE, TAPIFACE, TUNIFACE, NODE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ] +start_order = [ INTERNET, + NODEIFACE, + Parallel(TAPIFACE), Parallel(TUNIFACE), + Parallel(NODE), NETPIPE, + Parallel(MULTICASTANNOUNCER), Parallel(MULTICASTFORWARDER), Parallel(MULTICASTROUTER), + Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(CCNXDAEMON), + Parallel(APPLICATION)] + +# cleanup order +shutdown_order = [ + Parallel(APPLICATION), + Parallel (CCNXDAEMON), + Parallel(MULTICASTROUTER), Parallel(MULTICASTFORWARDER), Parallel(MULTICASTANNOUNCER), + Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NETPIPE), + Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), + NODEIFACE, Parallel(NODE) ] factories_info = dict({ NODE: dict({ @@ -965,11 +1401,14 @@ factories_info = dict({ "architecture", "operating_system", "site", - "emulation", "min_reliability", "max_reliability", "min_bandwidth", "max_bandwidth", + "min_load", + "max_load", + "min_cpu", + "max_cpu", # NEPI-in-NEPI attributes ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, @@ -992,14 +1431,14 @@ factories_info = dict({ "create_function": create_tuniface, "preconfigure_function": preconfigure_tuniface, "configure_function": postconfigure_tuniface, - "prestart_function": wait_tuniface, + "prestart_function": prestart_tuniface, "box_attributes": [ - "up", "device_name", "mtu", "snat", "pointopoint", + "up", "if_name", "mtu", "snat", "pointopoint", "multicast", "bwlimit", "txqueuelen", - "tun_proto", "tun_addr", "tun_port", "tun_key" + "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher", ], "traces": ["packets", "pcap"], - "connector_types": ["node","udp","tcp","fd->"], + "connector_types": ["node","udp","tcp","fd->","gre"], "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES], }), TAPIFACE: dict({ @@ -1008,16 +1447,120 @@ factories_info = dict({ "create_function": create_tapiface, "preconfigure_function": preconfigure_tuniface, "configure_function": postconfigure_tuniface, - "prestart_function": wait_tuniface, + "prestart_function": prestart_tuniface, "box_attributes": [ - "up", "device_name", "mtu", "snat", "pointopoint", + "up", "if_name", "mtu", "snat", "pointopoint", "multicast", "bwlimit", "txqueuelen", - "tun_proto", "tun_addr", "tun_port", "tun_key" + "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher", ], "traces": ["packets", "pcap"], - "connector_types": ["node","udp","tcp","fd->"], + "connector_types": ["node","udp","tcp","fd->","gre"], "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES], }), + TUNFILTER: dict({ + "help": "TUN/TAP stream filter\n\n" + "If specified, it should be either a .py or .so module. " + "It will be loaded, and all incoming and outgoing packets " + "will be routed through it. The filter will not be responsible " + "for buffering, packet queueing is performed in tun_connect " + "already, so it should not concern itself with it. It should " + "not, however, block in one direction if the other is congested.\n" + "\n" + "Modules are expected to have the following methods:\n" + "\tinit(**args)\n" + "\t\tIf arguments are given, this method will be called with the\n" + "\t\tgiven arguments (as keyword args in python modules, or a single\n" + "\taccept_packet(packet, direction):\n" + "\t\tDecide whether to drop the packet. Direction is 0 for packets " + "coming from the local side to the remote, and 1 is for packets " + "coming from the remote side to the local. Return a boolean, " + "true if the packet is not to be dropped.\n" + "\tfilter_init():\n" + "\t\tInitializes a filtering pipe (filter_run). It should " + "return two file descriptors to use as a bidirectional " + "pipe: local and remote. 'local' is where packets from the " + "local side will be written to. After filtering, those packets " + "should be written to 'remote', where tun_connect will read " + "from, and it will forward them to the remote peer. " + "Packets from the remote peer will be written to 'remote', " + "where the filter is expected to read from, and eventually " + "forward them to the local side. If the file descriptors are " + "not nonblocking, they will be set to nonblocking. So it's " + "better to set them from the start like that.\n" + "\tfilter_run(local, remote):\n" + "\t\tIf filter_init is provided, it will be called repeatedly, " + "in a separate thread until the process is killed. It should " + "sleep at most for a second.\n" + "\tfilter_close(local, remote):\n" + "\t\tCalled then the process is killed, if filter_init was provided. " + "It should, among other things, close the file descriptors.\n" + "\n" + "Python modules are expected to return a tuple in filter_init, " + "either of file descriptors or file objects, while native ones " + "will receive two int*.\n" + "\n" + "Python modules can additionally contain a custom queue class " + "that will replace the FIFO used by default. The class should " + "be named 'queueclass' and contain an interface compatible with " + "collections.deque. That is, indexing (especiall for q[0]), " + "bool(q), popleft, appendleft, pop (right), append (right), " + "len(q) and clear.", + "category": FC.CATEGORY_CHANNELS, + "create_function": create_tunfilter, + "box_attributes": [ + "module", "args", + "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher", + ], + "connector_types": ["->fd","udp","tcp"], + }), + CLASSQUEUEFILTER : dict({ + "help": "TUN classfull queue, uses a separate queue for each user-definable class.\n\n" + "It takes two arguments, both of which have sensible defaults:\n" + "\tsize: the base size of each class' queue\n" + "\tclasses: the class definitions, which follow the following syntax:\n" + '\t ::= ":" CLASSLIST\n' + '\t | \n' + '\t ::= "*" \n' + '\t | \n' + '\t ::= "*" \n' + '\t ::= "." \n' + '\t | \n' + '\t ::= | \n' + '\t ::= --see http://en.wikipedia.org/wiki/List_of_IP_protocol_numbers --\n' + '\t --only in lowercase, with special characters removed--\n' + '\t --or see below--\n' + '\t ::= [0-9]+\n' + '\t ::= [ "#" ] [ "p" ]\n' + '\t ::= NUMBER -- default 1\n' + '\t ::= NUMBER -- default 0\n' + '\t ::= NUMBER -- default 1\n' + "\n" + "Size, thoughput and priority are all relative terms. " + "Sizes are multipliers for the size argument, thoughput " + "is applied relative to other classes and the same with " + "priority.", + "category": FC.CATEGORY_CHANNELS, + "create_function": create_classqueuefilter, + "box_attributes": [ + "args", + "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher", + ], + "connector_types": ["->fd","udp","tcp"], + "traces": ["dropped_stats"], + }), + TOSQUEUEFILTER : dict({ + "help": "TUN classfull queue that classifies according to the TOS (RFC 791) IP field.\n\n" + "It takes a size argument that specifies the size of each class. As TOS is a " + "subset of DiffServ, this queue half-implements DiffServ.", + "category": FC.CATEGORY_CHANNELS, + "create_function": create_tosqueuefilter, + "box_attributes": [ + "args", + "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher", + ], + "connector_types": ["->fd","udp","tcp"], + }), + APPLICATION: dict({ "help": "Generic executable command line application", "category": FC.CATEGORY_APPLICATIONS, @@ -1030,7 +1573,22 @@ factories_info = dict({ "depends", "build-depends", "build", "install", "sources", "rpm-fusion" ], "connector_types": ["node"], - "traces": ["stdout", "stderr", "buildlog"], + "traces": ["stdout", "stderr", "buildlog", "output"], + "tags": [tags.APPLICATION], + }), + + CCNXDAEMON: dict({ + "help": "CCNx daemon", + "category": FC.CATEGORY_APPLICATIONS, + "create_function": create_ccnxdaemon, + "prestart_function": prestart_ccnxdaemon, + "status_function": status_application, + "stop_function": stop_application, + "configure_function": configure_application, + "box_attributes": ["ccnroutes", "build", "ccnlocalport", + "install", "ccnxversion", "sources"], + "connector_types": ["node"], + "traces": ["stdout", "stderr", "buildlog", "output"], "tags": [tags.APPLICATION], }), DEPENDENCY: dict({ @@ -1061,6 +1619,51 @@ factories_info = dict({ "connector_types": ["node"], "traces": ["buildlog"], }), + MULTICASTFORWARDER: dict({ + "help": "This application installs a userspace packet forwarder " + "that, when connected to a node, filters all packets " + "flowing through multicast-capable virtual interfaces " + "and applies custom-specified routing policies.", + "category": FC.CATEGORY_APPLICATIONS, + "create_function": create_multicast_forwarder, + "preconfigure_function": configure_forwarder, + "start_function": start_application, + "status_function": status_application, + "stop_function": stop_application, + "box_attributes": [ ], + "connector_types": ["node","router"], + "traces": ["buildlog","stderr"], + }), + MULTICASTANNOUNCER: dict({ + "help": "This application installs a userspace daemon that " + "monitors multicast membership and announces it on all " + "multicast-capable interfaces.\n" + "This does not usually happen automatically on PlanetLab slivers.", + "category": FC.CATEGORY_APPLICATIONS, + "create_function": create_multicast_announcer, + "preconfigure_function": configure_announcer, + "start_function": start_application, + "status_function": status_application, + "stop_function": stop_application, + "box_attributes": [ ], + "connector_types": ["node"], + "traces": ["buildlog","stderr"], + }), + MULTICASTROUTER: dict({ + "help": "This application installs a userspace daemon that " + "monitors multicast membership and announces it on all " + "multicast-capable interfaces.\n" + "This does not usually happen automatically on PlanetLab slivers.", + "category": FC.CATEGORY_APPLICATIONS, + "create_function": create_multicast_router, + "preconfigure_function": configure_router, + "start_function": start_application, + "status_function": status_application, + "stop_function": stop_application, + "box_attributes": ["routing_algorithm"], + "connector_types": ["fwd"], + "traces": ["buildlog","stdout","stderr"], + }), INTERNET: dict({ "help": "Internet routing", "category": FC.CATEGORY_CHANNELS, @@ -1083,6 +1686,20 @@ factories_info = dict({ }) testbed_attributes = dict({ + "slice_hrn": dict({ + "name": "sliceHrn", + "help": "The hierarchical Resource Name (HRN) for the PlanetLab slice.", + "type": Attribute.STRING, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue, + "validation_function": validation.is_string + }), + "sfa": dict({ + "name": "sfa", + "help": "Activates the use of SFA for node reservation.", + "type": Attribute.BOOL, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue, + "validation_function": validation.is_bool + }), "slice": dict({ "name": "slice", "help": "The name of the PlanetLab slice to use", @@ -1120,6 +1737,23 @@ testbed_attributes = dict({ "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, "validation_function": validation.is_string }), + "p2p_deployment": dict({ + "name": "p2pDeployment", + "help": "Enable peer-to-peer deployment of applications and dependencies. " + "When enabled, dependency packages and applications are " + "deployed in a P2P fashion, picking a single node to do " + "the building or repo download, while all the others " + "cooperatively exchange resulting binaries or rpms. " + "When deploying to many nodes, this is a far more efficient " + "use of resources. It does require re-encrypting and distributing " + "the slice's private key. Though it is implemented in a secure " + "fashion, if they key's sole purpose is not PlanetLab, then this " + "feature should be disabled.", + "type": Attribute.BOOL, + "value": True, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_bool + }), "slice_ssh_key": dict({ "name": "sliceSSHKey", "help": "The controller-local path to the slice user's ssh private key. " @@ -1131,8 +1765,54 @@ testbed_attributes = dict({ "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue, "validation_function": validation.is_string }), + "pl_log_level": dict({ + "name": "plLogLevel", + "help": "Verbosity of logging of planetlab events.", + "value": "ERROR", + "type": Attribute.ENUM, + "allowed": ["DEBUG", + "INFO", + "WARNING", + "ERROR", + "CRITICAL"], + "validation_function": validation.is_enum, + }), + "tap_port_base": dict({ + "name": "tapPortBase", + "help": "Base port to use when connecting TUN/TAPs. Effective port will be BASE + GUID.", + "type": Attribute.INTEGER, + "value": 15000, + "range": (2000,30000), + "validation_function": validation.is_integer_range(2000,30000) + }), + "clean_proc": dict({ + "name": "cleanProc", + "help": "Set to True if the slice will be dedicated to this experiment. " + "NEPI will perform node and slice process cleanup, making sure slices are " + "in a clean, repeatable state before running the experiment.", + "type": Attribute.BOOL, + "value": False, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_bool + }), + "clean_home": dict({ + "name": "cleanHome", + "help": "Set to True all preexistent directories in the home " + "directory of each sliver will be removed before the " + "start of the experiment.", + "type": Attribute.BOOL, + "value": False, + "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable, + "validation_function": validation.is_bool + }), }) +supported_recovery_policies = [ + DC.POLICY_FAIL, + DC.POLICY_RESTART, + DC.POLICY_RECOVER, + ] + class MetadataInfo(metadata.MetadataInfo): @property def connector_types(self): @@ -1182,3 +1862,8 @@ class MetadataInfo(metadata.MetadataInfo): def testbed_version(self): return TESTBED_VERSION + @property + def supported_recovery_policies(self): + return supported_recovery_policies + +