-#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
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"
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
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)
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)
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]
# 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
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)
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)
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)
### 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)
app.stdout = "stdout" in traces
app.stderr = "stderr" in traces
app.buildlog = "buildlog" in traces
+ app.outout = "output" in traces
app.start()
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]
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]
# 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]
"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",
"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 = [
}),
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({
"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"),
"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"),
"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"),
"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({
"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.",
"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",
"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",
"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",
"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({
"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,
"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 "
"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
"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,
"flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
"validation_function": validation.is_string
}),
+ "ccnsources": dict({
+ "name": "ccnsources",
+ "help": "Path to local tar with ccnx sources."
+ "Default source is http://www.ccnx.org/releases/ccnx-0.5.1.tar.gz",
+ "type": Attribute.STRING,
+ "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,
+ }),
"build": dict({
"name": "build",
"help": "Build commands to execute after deploying the sources. "
"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",
"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({
"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, Parallel(NODE), NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(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, Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NODE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(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({
"max_reliability",
"min_bandwidth",
"max_bandwidth",
+ "min_load",
+ "max_load",
+ "min_cpu",
+ "max_cpu",
# NEPI-in-NEPI attributes
ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
"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({
"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> ::= <CLASS> ":" CLASSLIST\n'
+ '\t | <CLASS>\n'
+ '\t <CLASS> ::= <PROTOLIST> "*" <PRIORITYSPEC>\n'
+ '\t | <DFLTCLASS>\n'
+ '\t <DFLTCLASS> ::= "*" <PRIORITYSPEC>\n'
+ '\t <PROTOLIST> ::= <PROTO> "." <PROTOLIST>\n'
+ '\t | <PROTO>\n'
+ '\t <PROTO> ::= <NAME> | <NUMBER>\n'
+ '\t <NAME> ::= --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 <NUMBER> ::= [0-9]+\n'
+ '\t <PRIORITYSPEC> ::= <THOUGHPUT> [ "#" <SIZE> ] [ "p" <PRIORITY> ]\n'
+ '\t <THOUGHPUT> ::= NUMBER -- default 1\n'
+ '\t <PRIORITY> ::= NUMBER -- default 0\n'
+ '\t <SIZE> ::= 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,
"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", "ccnsources", "build",
+ "install", "ccnxversion", "sources"],
+ "connector_types": ["node"],
+ "traces": ["stdout", "stderr", "buildlog", "output"],
"tags": [tags.APPLICATION],
}),
DEPENDENCY: 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,
})
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",
"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):
def testbed_version(self):
return TESTBED_VERSION
+ @property
+ def supported_recovery_policies(self):
+ return supported_recovery_policies
+
+