X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=src%2Fnepi%2Ftestbeds%2Fplanetlab%2Fmetadata_v01.py;h=396c920c1ddb0b5d33649c831ba337aafd6effe8;hb=00f2c243566b08387658112d684c31371286ffcc;hp=74b22bc95ccc3f728efb9f14e69f45fb3f99540f;hpb=345e9ad422fb51b47fe1f10c420a4392d1738c9b;p=nepi.git diff --git a/src/nepi/testbeds/planetlab/metadata_v01.py b/src/nepi/testbeds/planetlab/metadata_v01.py index 74b22bc9..396c920c 100644 --- a/src/nepi/testbeds/planetlab/metadata_v01.py +++ b/src/nepi/testbeds/planetlab/metadata_v01.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import time + from constants import TESTBED_ID from nepi.core import metadata from nepi.core.attributes import Attribute @@ -8,86 +10,327 @@ from nepi.util import validation from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \ STATUS_FINISHED +import functools +import os +import os.path + NODE = "Node" NODEIFACE = "NodeInterface" +TUNIFACE = "TunInterface" APPLICATION = "Application" +DEPENDENCY = "Dependency" +NEPIDEPENDENCY = "NepiDependency" +INTERNET = "Internet" +NETPIPE = "NetPipe" PL_TESTBED_ID = "planetlab" + +### Custom validation functions ### +def is_addrlist(attribute, value): + if not validation.is_string(attribute, value): + return False + + if not value: + # No empty strings + return False + + components = value.split(',') + + for component in components: + if '/' in component: + addr, mask = component.split('/',1) + else: + addr, mask = component, 32 + + if mask is not None and not (mask and mask.isdigit()): + # No empty or nonnumeric masks + return False + + if not validation.is_ip4_address(attribute, value): + # Address part must be ipv4 + return False + + return True + +def is_portlist(attribute, value): + if not validation.is_string(attribute, value): + return False + + if not value: + # No empty strings + return False + + components = value.split(',') + + for component in components: + if '-' in component: + pfrom, pto = component.split('-',1) + else: + pfrom = pto = component + + if not pfrom or not pto or not pfrom.isdigit() or not pto.isdigit(): + # No empty or nonnumeric ports + return False + + return True + + ### Connection functions #### +def connect_node_iface_node(testbed_instance, node_guid, iface_guid): + node = testbed_instance._elements[node_guid] + iface = testbed_instance._elements[iface_guid] + iface.node = node + +def connect_node_iface_inet(testbed_instance, iface_guid, inet_guid): + iface = testbed_instance._elements[iface_guid] + iface.has_internet = True + +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')) + +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 + iface.peer_proto = \ + iface.tun_proto = proto + +def connect_dep(testbed_instance, node_guid, app_guid): + node = testbed_instance._elements[node_guid] + app = 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) + +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 + + ### Creation functions ### def create_node(testbed_instance, guid): parameters = testbed_instance._get_parameters(guid) - element = testbed_instance.pl.Node() + + # create element with basic attributes + element = testbed_instance._make_node(parameters) + + # add constraint on number of (real) interfaces + # by counting connected devices + dev_guids = testbed_instance.get_connected(guid, "node", "devs") + num_open_ifaces = sum( # count True values + NODEIFACE == testbed_instance._get_factory_id(guid) + for guid in dev_guids ) + element.min_num_external_ifaces = num_open_ifaces + testbed_instance.elements[guid] = element def create_nodeiface(testbed_instance, guid): parameters = testbed_instance._get_parameters(guid) - element = testbed_instance.pl.Iface() + element = testbed_instance._make_node_iface(parameters) + testbed_instance.elements[guid] = element + +def create_tuniface(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_tun_iface(parameters) testbed_instance.elements[guid] = element def create_application(testbed_instance, guid): - testbed_instance.elements[guid] = None # Delayed construction + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_application(parameters) + + # Just inject configuration stuff + element.home_path = "nepi-app-%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) + + # Just inject configuration stuff + element.home_path = "nepi-dep-%s" % (guid,) + + testbed_instance.elements[guid] = element + +def create_nepi_dependency(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_nepi_dependency(parameters) + + # Just inject configuration stuff + element.home_path = "nepi-nepi-%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) + testbed_instance.elements[guid] = element + +def create_netpipe(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + element = testbed_instance._make_netpipe(parameters) + testbed_instance.elements[guid] = element ### Start/Stop functions ### def start_application(testbed_instance, guid): parameters = testbed_instance._get_parameters(guid) traces = testbed_instance._get_traces(guid) - user = parameters["user"] - command = parameters["command"] - stdout = stderr = None - if "stdout" in traces: - filename = testbed_instance.trace_filename(guid, "stdout") - stdout = open(filename, "wb") - testbed_instance.follow_trace("stdout", stdout) - if "stderr" in traces: - filename = testbed_instance.trace_filename(guid, "stderr") - stderr = open(filename, "wb") - testbed_instance.follow_trace("stderr", stderr) - - node_guid = testbed_instance.get_connected(guid, "node", "apps") - if len(node_guid) == 0: - raise RuntimeError("Can't instantiate interface %d outside netns \ - node" % guid) - node = testbed_instance.elements[node_guid[0]] - element = node.Popen(command, shell = True, stdout = stdout, - stderr = stderr, user = user) - testbed_instance.elements[guid] = element + app = testbed_instance.elements[guid] + + app.stdout = "stdout" in traces + app.stderr = "stderr" in traces + app.buildlog = "buildlog" in traces + + app.start() + +def stop_application(testbed_instance, guid): + app = testbed_instance.elements[guid] + app.kill() ### Status functions ### def status_application(testbed_instance, guid): if guid not in testbed_instance.elements.keys(): return STATUS_NOT_STARTED + app = testbed_instance.elements[guid] - if app.poll() == None: - return STATUS_RUNNING - return STATUS_FINISHED + return app.status() ### Configure functions ### -def configure_device(testbed_instance, guid): +def configure_nodeiface(testbed_instance, guid): + element = testbed_instance._elements[guid] + + # Cannot explicitly configure addresses + if guid in testbed_instance._add_address: + raise ValueError, "Cannot explicitly set address of public PlanetLab interface" + + # Get siblings + node_guid = testbed_instance.get_connected(guid, "node", "devs")[0] + dev_guids = testbed_instance.get_connected(node_guid, "node", "devs") + siblings = [ self._element[dev_guid] + for dev_guid in dev_guids + if dev_guid != guid ] + + # Fetch address from PLC api + element.pick_iface(siblings) + + # Do some validations + element.validate() + +def preconfigure_tuniface(testbed_instance, guid): element = testbed_instance._elements[guid] - if not guid in testbed_instance._add_address: - return - addresses = testbed_instance._add_address[guid] - for address in addresses: - (address, netprefix, broadcast) = address - # TODO: Decide if we should add a ipv4 or ipv6 address - element.add_v4_address(address, netprefix) + + # Set custom addresses if any + if guid in testbed_instance._add_address: + addresses = testbed_instance._add_address[guid] + for address in addresses: + (address, netprefix, broadcast) = address + element.add_address(address, netprefix, broadcast) + + # Link to external interface, if any + for iface in testbed_instance._elements.itervalues(): + if isinstance(iface, testbed_instance._interfaces.NodeIface) and iface.node is element.node and iface.has_internet: + element.external_iface = iface + break + + # Set standard TUN attributes + element.tun_addr = element.external_iface.address + element.tun_port = 15000 + int(guid) + + # Set enabled traces + traces = testbed_instance._get_traces(guid) + element.capture = 'packets' in traces + + # Do some validations + element.validate() + + # First-phase setup + element.prepare( + 'tun-%s' % (guid,), + id(element) < id(element.peer_iface) ) + +def postconfigure_tuniface(testbed_instance, guid): + element = testbed_instance._elements[guid] + + # Second-phase setup + element.setup() + def configure_node(testbed_instance, guid): - element = testbed_instance._elements[guid] - if not guid in testbed_instance._add_route: - return - routes = testbed_instance._add_route[guid] - for route in routes: - (destination, netprefix, nexthop) = route - element.add_route(prefix = destination, prefix_len = netprefix, - nexthop = nexthop) + node = testbed_instance._elements[guid] + + # Just inject configuration stuff + node.home_path = "nepi-node-%s" % (guid,) + node.ident_path = testbed_instance.sliceSSHKey + node.slicename = testbed_instance.slicename + + # Do some validations + node.validate() + + # recently provisioned nodes may not be up yet + sleeptime = 1.0 + while not node.is_alive(): + time.sleep(sleeptime) + sleeptime = min(30.0, sleeptime*1.5) + + # this will be done in parallel in all nodes + # this call only spawns the process + node.install_dependencies() + +def configure_application(testbed_instance, guid): + app = testbed_instance._elements[guid] + + # Do some validations + app.validate() + + # Wait for dependencies + app.node.wait_dependencies() + + # Install stuff + app.setup() + +def configure_dependency(testbed_instance, guid): + dep = testbed_instance._elements[guid] + + # Do some validations + dep.validate() + + # Wait for dependencies + dep.node.wait_dependencies() + + # Install stuff + dep.setup() + +def configure_netpipe(testbed_instance, guid): + netpipe = testbed_instance._elements[guid] + + # Do some validations + netpipe.validate() + + # Wait for dependencies + netpipe.node.wait_dependencies() + + # Install rules + netpipe.configure() ### Factory information ### @@ -104,75 +347,101 @@ connector_types = dict({ "max": -1, "min": 0 }), + "deps": dict({ + "help": "Connector from node to application dependencies " + "(packages and applications that need to be installed)", + "name": "deps", + "max": -1, + "min": 0 + }), + "inet": dict({ + "help": "Connector from network interfaces to the internet", + "name": "inet", + "max": 1, + "min": 1 + }), "node": dict({ "help": "Connector to a Node", "name": "node", "max": 1, "min": 1 }), - "p2p": dict({ - "help": "Connector to a P2PInterface", - "name": "p2p", - "max": 1, + "pipes": dict({ + "help": "Connector to a NetPipe", + "name": "pipes", + "max": 2, "min": 0 }), - "fd": dict({ - "help": "Connector to a network interface that can receive a file descriptor", - "name": "fd", + + "tcp": dict({ + "help": "ip-ip tunneling over TCP link", + "name": "tcp", "max": 1, "min": 0 }), - "switch": dict({ - "help": "Connector to a switch", - "name": "switch", + "udp": dict({ + "help": "ip-ip tunneling over UDP datagrams", + "name": "udp", "max": 1, "min": 0 - }) + }), }) connections = [ dict({ "from": (TESTBED_ID, NODE, "devs"), - "to": (TESTBED_ID, P2PIFACE, "node"), - "code": None, + "to": (TESTBED_ID, NODEIFACE, "node"), + "init_code": connect_node_iface_node, "can_cross": False }), dict({ "from": (TESTBED_ID, NODE, "devs"), - "to": (TESTBED_ID, TAPIFACE, "node"), - "code": None, + "to": (TESTBED_ID, TUNIFACE, "node"), + "init_code": connect_tun_iface_node, "can_cross": False }), dict({ - "from": (TESTBED_ID, NODE, "devs"), - "to": (TESTBED_ID, NODEIFACE, "node"), - "code": None, + "from": (TESTBED_ID, NODEIFACE, "inet"), + "to": (TESTBED_ID, INTERNET, "devs"), + "init_code": connect_node_iface_inet, "can_cross": False }), dict({ - "from": (TESTBED_ID, P2PIFACE, "p2p"), - "to": (TESTBED_ID, P2PIFACE, "p2p"), - "code": None, + "from": (TESTBED_ID, NODE, "apps"), + "to": (TESTBED_ID, APPLICATION, "node"), + "init_code": connect_dep, "can_cross": False }), dict({ - "from": (TESTBED_ID, TAPIFACE, "fd"), - "to": (NS3_TESTBED_ID, FDNETDEV, "fd"), - "code": connect_fd_local, - "can_cross": True + "from": (TESTBED_ID, NODE, "deps"), + "to": (TESTBED_ID, DEPENDENCY, "node"), + "init_code": connect_dep, + "can_cross": False }), - dict({ - "from": (TESTBED_ID, SWITCH, "devs"), - "to": (TESTBED_ID, NODEIFACE, "switch"), - "code": connect_switch, + dict({ + "from": (TESTBED_ID, NODE, "deps"), + "to": (TESTBED_ID, NEPIDEPENDENCY, "node"), + "init_code": connect_dep, "can_cross": False }), dict({ - "from": (TESTBED_ID, NODE, "apps"), - "to": (TESTBED_ID, APPLICATION, "node"), - "code": None, + "from": (TESTBED_ID, NODE, "pipes"), + "to": (TESTBED_ID, NETPIPE, "node"), + "init_code": connect_node_netpipe, "can_cross": False - }) + }), + dict({ + "from": (TESTBED_ID, TUNIFACE, "tcp"), + "to": (TESTBED_ID, TUNIFACE, "tcp"), + "init_code": functools.partial(connect_tun_iface_peer,"tcp"), + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TUNIFACE, "udp"), + "to": (TESTBED_ID, TUNIFACE, "udp"), + "init_code": functools.partial(connect_tun_iface_peer,"udp"), + "can_cross": False + }), ] attributes = dict({ @@ -182,15 +451,87 @@ attributes = dict({ "type": Attribute.BOOL, "value": False, "flags": Attribute.DesignOnly, - "validation_function": validation.is_bool + "validation_function": validation.is_bool, }), - "lladdr": dict({ - "name": "lladdr", - "help": "Mac address", - "type": Attribute.STRING, + "hostname": dict({ + "name": "hostname", + "help": "Constrain hostname during resource discovery. May use wildcards.", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly, + "validation_function": validation.is_string, + }), + "architecture": dict({ + "name": "architecture", + "help": "Constrain architexture during resource discovery.", + "type": Attribute.ENUM, "flags": Attribute.DesignOnly, - "validation_function": validation.is_mac_address + "allowed": ["x86_64", + "i386"], + "validation_function": validation.is_enum, }), + "operating_system": dict({ + "name": "operatingSystem", + "help": "Constrain operating system during resource discovery.", + "type": Attribute.ENUM, + "flags": Attribute.DesignOnly, + "allowed": ["f8", + "f12", + "f14", + "centos", + "other"], + "validation_function": validation.is_enum, + }), + "site": dict({ + "name": "site", + "help": "Constrain the PlanetLab site this node should reside on.", + "type": Attribute.ENUM, + "flags": Attribute.DesignOnly, + "allowed": ["PLE", + "PLC", + "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.DesignOnly, + "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.DesignOnly, + "validation_function": validation.is_double, + }), + "max_reliability": dict({ + "name": "maxReliability", + "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.", + "type": Attribute.DOUBLE, + "range": (0,100), + "flags": Attribute.DesignOnly, + "validation_function": validation.is_double, + }), + "min_bandwidth": dict({ + "name": "minBandwidth", + "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.", + "type": Attribute.DOUBLE, + "range": (0,2**31), + "flags": Attribute.DesignOnly, + "validation_function": validation.is_double, + }), + "max_bandwidth": dict({ + "name": "maxBandwidth", + "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.", + "type": Attribute.DOUBLE, + "range": (0,2**31), + "flags": Attribute.DesignOnly, + "validation_function": validation.is_double, + }), + "up": dict({ "name": "up", "help": "Link up", @@ -198,6 +539,13 @@ attributes = dict({ "value": False, "validation_function": validation.is_bool }), + "primary": dict({ + "name": "primary", + "help": "This is the primary interface for the attached node", + "type": Attribute.BOOL, + "value": True, + "validation_function": validation.is_bool + }), "device_name": dict({ "name": "name", "help": "Device name", @@ -209,28 +557,31 @@ attributes = dict({ "name": "mtu", "help": "Maximum transmition unit for device", "type": Attribute.INTEGER, - "validation_function": validation.is_integer + "range": (0,1500), + "validation_function": validation.is_integer_range(0,1500) }), - "broadcast": dict({ - "name": "broadcast", - "help": "Broadcast address", - "type": Attribute.STRING, - "validation_function": validation.is_string # TODO: should be is address! + "mask": dict({ + "name": "mask", + "help": "Network mask for the device (eg: 24 for /24 network)", + "type": Attribute.INTEGER, + "validation_function": validation.is_integer_range(8,24) }), - "multicast": dict({ - "name": "multicast", - "help": "Multicast enabled", + "snat": dict({ + "name": "snat", + "help": "Enable SNAT (source NAT to the internet) no this device", "type": Attribute.BOOL, "value": False, "validation_function": validation.is_bool }), - "arp": dict({ - "name": "arp", - "help": "ARP enabled", - "type": Attribute.BOOL, - "value": False, - "validation_function": validation.is_bool + "txqueuelen": dict({ + "name": "mask", + "help": "Transmission queue length (in packets)", + "type": Attribute.INTEGER, + "flags": Attribute.DesignOnly, + "range" : (1,10000), + "validation_function": validation.is_integer }), + "command": dict({ "name": "command", "help": "Command line string", @@ -238,12 +589,13 @@ attributes = dict({ "flags": Attribute.DesignOnly, "validation_function": validation.is_string }), - "user": dict({ - "name": "user", - "help": "System user", - "type": Attribute.STRING, + "sudo": dict({ + "name": "sudo", + "help": "Run with root privileges", + "type": Attribute.BOOL, "flags": Attribute.DesignOnly, - "validation_function": validation.is_string + "value": False, + "validation_function": validation.is_bool }), "stdin": dict({ "name": "stdin", @@ -252,6 +604,124 @@ attributes = dict({ "flags": Attribute.DesignOnly, "validation_function": validation.is_string }), + + "depends": dict({ + "name": "depends", + "help": "Space-separated list of packages required to run the application", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly, + "validation_function": validation.is_string + }), + "build-depends": dict({ + "name": "buildDepends", + "help": "Space-separated list of packages required to build the application", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly, + "validation_function": validation.is_string + }), + "sources": dict({ + "name": "sources", + "help": "Space-separated list of regular files to be deployed in the working path prior to building. " + "Archives won't be expanded automatically.", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly, + "validation_function": validation.is_string + }), + "build": dict({ + "name": "build", + "help": "Build commands to execute after deploying the sources. " + "Sources will be in the ${SOURCES} folder. " + "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n" + "Try to make the commands return with a nonzero exit code on error.\n" + "Also, do not install any programs here, use the 'install' attribute. This will " + "help keep the built files constrained to the build folder (which may " + "not be the home folder), and will result in faster deployment. Also, " + "make sure to clean up temporary files, to reduce bandwidth usage between " + "nodes when transferring built packages.", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly, + "validation_function": validation.is_string + }), + "install": dict({ + "name": "install", + "help": "Commands to transfer built files to their final destinations. " + "Sources will be in the initial working folder, and a special " + "tag ${SOURCES} can be used to reference the experiment's " + "home folder (where the application commands will run).\n" + "ALL sources and targets needed for execution must be copied there, " + "if building has been enabled.\n" + "That is, 'slave' nodes will not automatically get any source files. " + "'slave' nodes don't get build dependencies either, so if you need " + "make and other tools to install, be sure to provide them as " + "actual dependencies instead.", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly, + "validation_function": validation.is_string + }), + + "netpipe_mode": dict({ + "name": "mode", + "help": "Link mode:\n" + " * SERVER: applies to incoming connections\n" + " * CLIENT: applies to outgoing connections\n" + " * SERVICE: applies to both", + "type": Attribute.ENUM, + "flags": Attribute.DesignOnly, + "allowed": ["SERVER", + "CLIENT", + "SERVICE"], + "validation_function": validation.is_enum, + }), + "port_list": dict({ + "name": "portList", + "help": "Port list or range. Eg: '22', '22,23,27', '20-2000'", + "type": Attribute.STRING, + "validation_function": is_portlist, + }), + "addr_list": dict({ + "name": "addrList", + "help": "Address list or range. Eg: '127.0.0.1', '127.0.0.1,127.0.1.1', '127.0.0.1/8'", + "type": Attribute.STRING, + "validation_function": is_addrlist, + }), + "bw_in": dict({ + "name": "bwIn", + "help": "Inbound bandwidth limit (in Mbit/s)", + "type": Attribute.DOUBLE, + "validation_function": validation.is_double, + }), + "bw_out": dict({ + "name": "bwOut", + "help": "Outbound bandwidth limit (in Mbit/s)", + "type": Attribute.DOUBLE, + "validation_function": validation.is_double, + }), + "plr_in": dict({ + "name": "plrIn", + "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)", + "type": Attribute.DOUBLE, + "validation_function": validation.is_double, + }), + "plr_out": dict({ + "name": "plrOut", + "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)", + "type": Attribute.DOUBLE, + "validation_function": validation.is_double, + }), + "delay_in": dict({ + "name": "delayIn", + "help": "Inbound packet delay (in milliseconds)", + "type": Attribute.INTEGER, + "range": (0,60000), + "validation_function": validation.is_integer, + }), + "delay_out": dict({ + "name": "delayOut", + "help": "Outbound packet delay (in milliseconds)", + "type": Attribute.INTEGER, + "range": (0,60000), + "validation_function": validation.is_integer, + }), }) traces = dict({ @@ -262,67 +732,70 @@ traces = dict({ "stderr": dict({ "name": "stderr", "help": "Application standard error", - }) + }), + "buildlog": dict({ + "name": "buildlog", + "help": "Output of the build process", + }), + + "netpipe_stats": dict({ + "name": "netpipeStats", + "help": "Information about rule match counters, packets dropped, etc.", + }), + + "packets": dict({ + "name": "packets", + "help": "Detailled log of all packets going through the interface", + }), }) -create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, SWITCH, - APPLICATION ] +create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, DEPENDENCY, APPLICATION ] -configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE, SWITCH, NODE, - APPLICATION ] +configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, DEPENDENCY, APPLICATION ] factories_info = dict({ NODE: dict({ - "allow_routes": True, - "help": "Emulated Node with virtualized network stack", + "allow_routes": False, + "help": "Virtualized Node (V-Server style)", "category": "topology", "create_function": create_node, - "configure_function": configure_node, - "box_attributes": ["forward_X11"], - "connector_types": ["devs", "apps"] - }), - P2PIFACE: dict({ - "allow_addresses": True, - "help": "Point to point network interface", - "category": "devices", - "create_function": create_p2piface, - "configure_function": configure_device, - "box_attributes": ["lladdr", "up", "device_name", "mtu", - "multicast", "broadcast", "arp"], - "connector_types": ["node", "p2p"] + "preconfigure_function": configure_node, + "box_attributes": [ + "forward_X11", + "hostname", + "architecture", + "operating_system", + "site", + "emulation", + "min_reliability", + "max_reliability", + "min_bandwidth", + "max_bandwidth", + ], + "connector_types": ["devs", "apps", "pipes", "deps"] }), - TAPIFACE: dict({ - "allow_addresses": True, - "help": "Tap device network interface", - "category": "devices", - "create_function": create_tapiface, - "configure_function": configure_device, - "box_attributes": ["lladdr", "up", "device_name", "mtu", - "multicast", "broadcast", "arp"], - "connector_types": ["node", "fd"] - }), NODEIFACE: dict({ - "allow_addresses": True, - "help": "Node network interface", + "has_addresses": True, + "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.", "category": "devices", "create_function": create_nodeiface, - "configure_function": configure_device, - "box_attributes": ["lladdr", "up", "device_name", "mtu", - "multicast", "broadcast", "arp"], - "connector_types": ["node", "switch"] + "preconfigure_function": configure_nodeiface, + "box_attributes": [ ], + "connector_types": ["node", "inet"] }), - SWITCH: dict({ - "display_name": "Switch", - "help": "Switch interface", + TUNIFACE: dict({ + "allow_addresses": True, + "help": "Virtual TUN network interface", "category": "devices", - "create_function": create_switch, - "box_attributes": ["up", "device_name", "mtu", "multicast"], - #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function), - #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function), - #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function), - #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function), - #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function) - "connector_types": ["devs"] + "create_function": create_tuniface, + "preconfigure_function": preconfigure_tuniface, + "configure_function": postconfigure_tuniface, + "box_attributes": [ + "up", "device_name", "mtu", "snat", + "txqueuelen" + ], + "traces": ["packets"], + "connector_types": ["node","udp","tcp"] }), APPLICATION: dict({ "help": "Generic executable command line application", @@ -330,20 +803,86 @@ factories_info = dict({ "create_function": create_application, "start_function": start_application, "status_function": status_application, - "box_attributes": ["command", "user"], + "stop_function": stop_application, + "configure_function": configure_application, + "box_attributes": ["command", "sudo", "stdin", + "depends", "build-depends", "build", "install", + "sources" ], "connector_types": ["node"], - "traces": ["stdout", "stderr"] + "traces": ["stdout", "stderr", "buildlog"] + }), + DEPENDENCY: dict({ + "help": "Requirement for package or application to be installed on some node", + "category": "applications", + "create_function": create_dependency, + "configure_function": configure_dependency, + "box_attributes": ["depends", "build-depends", "build", "install", + "sources" ], + "connector_types": ["node"], + "traces": ["buildlog"] + }), + NEPIDEPENDENCY: dict({ + "help": "Requirement for NEPI inside NEPI - required to run testbed instances inside a node", + "category": "applications", + "create_function": create_nepi_dependency, + "configure_function": configure_dependency, + "box_attributes": [ ], + "connector_types": ["node"], + "traces": ["buildlog"] + }), + INTERNET: dict({ + "help": "Internet routing", + "category": "topology", + "create_function": create_internet, + "connector_types": ["devs"], + }), + NETPIPE: dict({ + "help": "Link emulation", + "category": "topology", + "create_function": create_netpipe, + "configure_function": configure_netpipe, + "box_attributes": ["netpipe_mode", + "addr_list", "port_list", + "bw_in","plr_in","delay_in", + "bw_out","plr_out","delay_out"], + "connector_types": ["node"], + "traces": ["netpipe_stats"] }), }) testbed_attributes = dict({ - "enable_debug": dict({ - "name": "enableDebug", - "help": "Enable netns debug output", - "type": Attribute.BOOL, - "value": False, - "validation_function": validation.is_bool - }), + "slice": dict({ + "name": "slice", + "help": "The name of the PlanetLab slice to use", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue, + "validation_function": validation.is_string + }), + "auth_user": dict({ + "name": "authUser", + "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue, + "validation_function": validation.is_string + }), + "auth_pass": dict({ + "name": "authPass", + "help": "The PlanetLab user's password.", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue, + "validation_function": validation.is_string + }), + "slice_ssh_key": dict({ + "name": "sliceSSHKey", + "help": "The controller-local path to the slice user's ssh private key. " + "It is the user's responsability to deploy this file where the controller " + "will run, it won't be done automatically because it's sensitive information. " + "It is recommended that a NEPI-specific user be created for this purpose and " + "this purpose alone.", + "type": Attribute.STRING, + "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue, + "validation_function": validation.is_string + }), }) class VersionedMetadataInfo(metadata.VersionedMetadataInfo):