X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=src%2Fnepi%2Ftestbeds%2Fnetns%2Fmetadata_v01.py;h=539175fb3e211bb4af7e82e8b6bf32d85053324c;hb=6af8ccce0bbd4675d3d0c12d781fa9550e797cd9;hp=b5a836955cafee4c16d0e7fd7bf8b79cb845c2da;hpb=80a061920a3ae669cfbc37e0366d40ce30a43ac1;p=nepi.git diff --git a/src/nepi/testbeds/netns/metadata_v01.py b/src/nepi/testbeds/netns/metadata_v01.py index b5a83695..539175fb 100644 --- a/src/nepi/testbeds/netns/metadata_v01.py +++ b/src/nepi/testbeds/netns/metadata_v01.py @@ -4,40 +4,109 @@ from constants import TESTBED_ID from nepi.core import metadata from nepi.core.attributes import Attribute -from nepi.util import validation -from nepi.util.constants import AF_INET, STATUS_NOT_STARTED, STATUS_RUNNING, \ - STATUS_FINISHED +from nepi.util import tags, validation +from nepi.util.constants import ApplicationStatus as AS, \ + FactoryCategories as FC +from nepi.util.tunchannel_impl import \ + preconfigure_tunchannel, postconfigure_tunchannel, \ + wait_tunchannel, create_tunchannel, \ + crossconnect_tunchannel_peer_init, \ + crossconnect_tunchannel_peer_compl + +import functools + +# Factories NODE = "Node" P2PIFACE = "P2PNodeInterface" TAPIFACE = "TapNodeInterface" NODEIFACE = "NodeInterface" SWITCH = "Switch" APPLICATION = "Application" +TUNCHANNEL = "TunChannel" NS3_TESTBED_ID = "ns3" FDNETDEV = "ns3::FileDescriptorNetDevice" +def _follow_trace(testbed_instance, guid, trace_id, filename): + filepath = testbed_instance.trace_filepath(guid, trace_id, filename) + trace = open(filepath, "wb") + testbed_instance.follow_trace(guid, trace_id, trace, filename) + return trace + ### Connection functions #### -def connect_switch(switch, interface): +def connect_switch(testbed_instance, switch_guid, interface_guid): + switch = testbed_instance._elements[switch_guid] + interface = testbed_instance._elements[interface_guid] switch.connect(interface) -#XXX: This connection function cannot be use to transfer a file descriptor -# to a remote tap device -def connect_fd_local(tap, fdnd): +def connect_fd(testbed_instance, tap_guid, cross_data): import passfd import socket - fd = tap.file_descriptor - address = fdnd.socket_address + tap = testbed_instance._elements[tap_guid] + address = cross_data["tun_addr"] sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) sock.connect(address) - passfd.sendfd(sock, fd, '0') + passfd.sendfd(sock, tap.fd, '0') # TODO: after succesful transfer, the tap device should close the fd +def connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid): + tap = testbed_instance._elements[tap_guid] + chan = testbed_instance._elements[chan_guid] + + # Create a file object for the tap's interface device + # and send it to the channel. It should comply with all the + # requirements for the channel's tun_socket. + import os + chan.tun_socket = os.fdopen(tap.fd) + + # Set the channel to ethernet mode (it's a tap) + chan.ethernet_mode = True + + # Check to see if the device uses PI headers + # It's normally so + with_pi = True + try: + import fcntl + import struct + TUNGETIFF = 0x800454d2 + IFF_NO_PI = 0x00001000 + struct_ifreq = "x"*16+"H"+"x"*22 + flags = struct.unpack(struct_ifreq, + fcntl.ioctl(tap.fd, TUNGETIFF, struct.pack(struct_ifreq,0)) ) + with_pi = (0 == (flags & IFF_NO_PI)) + except: + # maybe the kernel doesn't support the IOCTL, + # in which case, we assume it uses PI headers (as is usual) + pass + chan.with_pi = with_pi + +### Trace functions ### + +def nodepcap_trace(testbed_instance, guid, trace_id): + node = testbed_instance._elements[guid] + parameters = testbed_instance._get_parameters(guid) + filename = "%d-pcap.stdout" % guid + stdout = _follow_trace(testbed_instance, guid, "pcap_stdout", filename) + filename = "%d-pcap.stderr" % guid + stderr = _follow_trace(testbed_instance, guid, "pcap_stderr", filename) + filename = "%d-node.pcap" % guid + filepath = testbed_instance.trace_filepath(guid, trace_id, filename) + command = "tcpdump -i 'any' -w %s" % filepath + user = "root" + trace = node.Popen(command, shell = True, stdout = stdout, + stderr = stderr, user = user) + testbed_instance.follow_trace(guid, trace_id, trace, filename) + +trace_functions = dict({ + "pcap": nodepcap_trace, + }) + ### Creation functions ### -def create_node(testbed_instance, guid, parameters): +def create_node(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) forward_X11 = False if "forward_X11" in parameters: forward_X11 = parameters["forward_X11"] @@ -45,7 +114,7 @@ def create_node(testbed_instance, guid, parameters): element = testbed_instance.netns.Node(forward_X11 = forward_X11) testbed_instance.elements[guid] = element -def create_p2piface(testbed_instance, guid, parameters): +def create_p2piface(testbed_instance, guid): if guid in testbed_instance.elements: # The interface pair was already instantiated return @@ -71,7 +140,7 @@ def create_p2piface(testbed_instance, guid, parameters): testbed_instance.elements[guid] = element1 testbed_instance.elements[guid2] = element2 -def create_tapiface(testbed_instance, guid, parameters): +def create_tapiface(testbed_instance, guid): node_guid = testbed_instance.get_connected(guid, "node", "devs") if len(node_guid) == 0: raise RuntimeError("Can't instantiate interface %d outside netns \ @@ -80,7 +149,7 @@ def create_tapiface(testbed_instance, guid, parameters): element = node.add_tap() testbed_instance.elements[guid] = element -def create_nodeiface(testbed_instance, guid, parameters): +def create_nodeiface(testbed_instance, guid): node_guid = testbed_instance.get_connected(guid, "node", "devs") if len(node_guid) == 0: raise RuntimeError("Can't instantiate interface %d outside netns \ @@ -89,28 +158,29 @@ def create_nodeiface(testbed_instance, guid, parameters): element = node.add_if() testbed_instance.elements[guid] = element -def create_switch(testbed_instance, guid, parameters): +def create_switch(testbed_instance, guid): element = testbed_instance.netns.Switch() testbed_instance.elements[guid] = element -def create_application(testbed_instance, guid, parameters): +def create_application(testbed_instance, guid): testbed_instance.elements[guid] = None # Delayed construction ### Start/Stop functions ### -def start_application(testbed_instance, guid, parameters, traces): - user = parameters["user"] +def start_application(testbed_instance, guid): + parameters = testbed_instance._get_parameters(guid) + traces = testbed_instance._get_traces(guid) command = parameters["command"] + user = None + if "user" in parameters: + user = parameters["user"] stdout = stderr = None if "stdout" in traces: - filename = testbed_instance.trace_filename(guid, "stdout") - stdout = open(filename, "wb") - testbed_instance.follow_trace("stdout", stdout) + filename = "%d-stdout.trace" % guid + stdout = _follow_trace(testbed_instance, guid, "stdout", filename) if "stderr" in traces: - filename = testbed_instance.trace_filename(guid, "stderr") - stderr = open(filename, "wb") - testbed_instance.follow_trace("stderr", stderr) - + filename = "%d-stderr.trace" % guid + stderr = _follow_trace(testbed_instance, guid, "stderr", filename) node_guid = testbed_instance.get_connected(guid, "node", "apps") if len(node_guid) == 0: raise RuntimeError("Can't instantiate interface %d outside netns \ @@ -120,15 +190,52 @@ def start_application(testbed_instance, guid, parameters, traces): stderr = stderr, user = user) testbed_instance.elements[guid] = element +def stop_application(testbed_instance, guid): + #app = testbed_instance.elements[guid] + #app.signal() + pass + ### Status functions ### def status_application(testbed_instance, guid): if guid not in testbed_instance.elements.keys(): - return STATUS_NOT_STARTED + return AS.STATUS_NOT_STARTED app = testbed_instance.elements[guid] if app.poll() == None: - return STATUS_RUNNING - return STATUS_FINISHED + return AS.STATUS_RUNNING + return AS.STATUS_FINISHED + +### Configure functions ### + +def configure_traces(testbed_instance, guid): + traces = testbed_instance._get_traces(guid) + for trace_id in traces: + if trace_id not in trace_functions: + continue + trace_func = trace_functions[trace_id] + trace_func(testbed_instance, guid, trace_id) + +def configure_device(testbed_instance, guid): + configure_traces(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) + +def configure_node(testbed_instance, guid): + configure_traces(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, metric) = route + element.add_route(prefix = destination, prefix_len = netprefix, + nexthop = nexthop, metric = metric) ### Factory information ### @@ -157,10 +264,16 @@ connector_types = dict({ "max": 1, "min": 0 }), - "fd": dict({ - "help": "Connector to a network interface that can receive a file descriptor", - "name": "fd", - "max": 1, + "->fd": dict({ + "help": "File descriptor receptor for devices with file descriptors", + "name": "->fd", + "max": 1, + "min": 0 + }), + "fd->": dict({ + "help": "File descriptor provider for devices with file descriptors", + "name": "fd->", + "max": 1, "min": 0 }), "switch": dict({ @@ -168,52 +281,79 @@ connector_types = dict({ "name": "switch", "max": 1, "min": 0 - }) + }), + "tcp": dict({ + "help": "ip-ip tunneling over TCP link", + "name": "tcp", + "max": 1, + "min": 0 + }), + "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, "can_cross": False }), dict({ "from": (TESTBED_ID, NODE, "devs"), "to": (TESTBED_ID, TAPIFACE, "node"), - "code": None, "can_cross": False }), dict({ "from": (TESTBED_ID, NODE, "devs"), "to": (TESTBED_ID, NODEIFACE, "node"), - "code": None, "can_cross": False }), dict({ "from": (TESTBED_ID, P2PIFACE, "p2p"), "to": (TESTBED_ID, P2PIFACE, "p2p"), - "code": None, "can_cross": False }), dict({ - "from": (TESTBED_ID, TAPIFACE, "fd"), - "to": (NS3_TESTBED_ID, FDNETDEV, "fd"), - "code": connect_fd_local, + "from": (TESTBED_ID, TAPIFACE, "fd->"), + "to": (None, None, "->fd"), + "compl_code": connect_fd, "can_cross": True }), dict({ "from": (TESTBED_ID, SWITCH, "devs"), "to": (TESTBED_ID, NODEIFACE, "switch"), - "code": connect_switch, + "init_code": connect_switch, "can_cross": False }), dict({ "from": (TESTBED_ID, NODE, "apps"), "to": (TESTBED_ID, APPLICATION, "node"), - "code": None, "can_cross": False - }) + }), + dict({ + "from": (TESTBED_ID, TUNCHANNEL, "->fd" ), + "to": (TESTBED_ID, TAPIFACE, "fd->" ), + "init_code": connect_tunchannel_tap, + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TUNCHANNEL, "tcp"), + "to": (None, None, "tcp"), + "init_code": functools.partial(crossconnect_tunchannel_peer_init,"tcp"), + "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"tcp"), + "can_cross": True + }), + dict({ + "from": (TESTBED_ID, TUNCHANNEL, "udp"), + "to": (None, None, "udp"), + "init_code": functools.partial(crossconnect_tunchannel_peer_init,"udp"), + "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"udp"), + "can_cross": True + }), ] attributes = dict({ @@ -222,21 +362,14 @@ attributes = dict({ "help": "Forward x11 from main namespace to the node", "type": Attribute.BOOL, "value": False, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, + "flags": Attribute.DesignOnly, "validation_function": validation.is_bool }), "lladdr": dict({ "name": "lladdr", "help": "Mac address", "type": Attribute.STRING, - "value": None, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, + "flags": Attribute.DesignOnly, "validation_function": validation.is_mac_address }), "up": dict({ @@ -244,43 +377,25 @@ attributes = dict({ "help": "Link up", "type": Attribute.BOOL, "value": False, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, "validation_function": validation.is_bool }), "device_name": dict({ "name": "name", "help": "Device name", "type": Attribute.STRING, - "value": None, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, + "flags": Attribute.DesignOnly, "validation_function": validation.is_string }), "mtu": dict({ "name": "mtu", "help": "Maximum transmition unit for device", "type": Attribute.INTEGER, - "value": None, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, "validation_function": validation.is_integer }), "broadcast": dict({ "name": "broadcast", "help": "Broadcast address", "type": Attribute.STRING, - "value": None, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, "validation_function": validation.is_string # TODO: should be is address! }), "multicast": dict({ @@ -288,10 +403,6 @@ attributes = dict({ "help": "Multicast enabled", "type": Attribute.BOOL, "value": False, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, "validation_function": validation.is_bool }), "arp": dict({ @@ -299,68 +410,29 @@ attributes = dict({ "help": "ARP enabled", "type": Attribute.BOOL, "value": False, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, "validation_function": validation.is_bool }), "command": dict({ "name": "command", "help": "Command line string", "type": Attribute.STRING, - "value": None, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, + "flags": Attribute.DesignOnly, "validation_function": validation.is_string }), "user": dict({ "name": "user", "help": "System user", "type": Attribute.STRING, - "value": None, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, + "flags": Attribute.DesignOnly, "validation_function": validation.is_string }), "stdin": dict({ "name": "stdin", "help": "Standard input", "type": Attribute.STRING, - "value": None, - "range": None, - "allowed": None, - "readonly": False, - "visible": True, + "flags": Attribute.DesignOnly, "validation_function": validation.is_string }), - "max_addresses": dict({ - "name": "MaxAddresses", - "help": "Maximum number of addresses allowed by the device", - "type": Attribute.INTEGER, - "value": None, - "range": None, - "allowed": None, - "readonly": True, - "visible": False, - "validation_function": validation.is_integer - }), - "family": dict({ - "name": "Family", - "help": "IP address family", - "type": Attribute.INTEGER, - "value": AF_INET, - "range": None, - "allowed": None, - "readonly": True, - "visible": False, - "validation_function": validation.is_integer - }), - }) traces = dict({ @@ -371,96 +443,117 @@ traces = dict({ "stderr": dict({ "name": "stderr", "help": "Application standard error", + }), + "node_pcap": dict({ + "name": "pcap", + "help": "tcpdump at all node interfaces", }) }) -factories_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, SWITCH, +create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, + TUNCHANNEL, SWITCH, APPLICATION ] +configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE, + TUNCHANNEL, SWITCH, + NODE, APPLICATION ] + factories_info = dict({ NODE: dict({ - "allow_routes": True, "help": "Emulated Node with virtualized network stack", - "category": "topology", + "category": FC.CATEGORY_NODES, "create_function": create_node, - "start_function": None, - "stop_function": None, - "status_function": None, + "configure_function": configure_node, "box_attributes": ["forward_X11"], - "connector_types": ["devs", "apps"] + "connector_types": ["devs", "apps"], + "traces": ["node_pcap"], + "tags": [tags.NODE, tags.ALLOW_ROUTES], }), P2PIFACE: dict({ - "allow_addresses": True, "help": "Point to point network interface", - "category": "devices", + "category": FC.CATEGORY_DEVICES, "create_function": create_p2piface, - "start_function": None, - "stop_function": None, - "status_function": None, - "factory_attributes": ["family", "max_addresses"], + "configure_function": configure_device, "box_attributes": ["lladdr", "up", "device_name", "mtu", "multicast", "broadcast", "arp"], - "connector_types": ["node", "p2p"] + "connector_types": ["node", "p2p"], + "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES], }), TAPIFACE: dict({ - "allow_addresses": True, "help": "Tap device network interface", - "category": "devices", + "category": FC.CATEGORY_DEVICES, "create_function": create_tapiface, - "start_function": None, - "stop_function": None, - "status_function": None, - "factory_attributes": ["family", "max_addresses"], + "configure_function": configure_device, "box_attributes": ["lladdr", "up", "device_name", "mtu", "multicast", "broadcast", "arp"], - "connector_types": ["node", "fd"] + "connector_types": ["node", "fd->"], + "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES], }), NODEIFACE: dict({ - "allow_addresses": True, "help": "Node network interface", - "category": "devices", + "category": FC.CATEGORY_DEVICES, "create_function": create_nodeiface, - "start_function": None, - "stop_function": None, - "status_function": None, - "factory_attributes": ["family", "max_addresses"], + "configure_function": configure_device, "box_attributes": ["lladdr", "up", "device_name", "mtu", "multicast", "broadcast", "arp"], - "connector_types": ["node", "switch"] + "connector_types": ["node", "switch"], + "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES], }), SWITCH: dict({ "display_name": "Switch", "help": "Switch interface", - "category": "devices", + "category": FC.CATEGORY_DEVICES, "create_function": create_switch, - "start_function": None, - "stop_function": None, - "status_function": None, "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"] + "connector_types": ["devs"], + "tags": [tags.SWITCH], }), APPLICATION: dict({ "help": "Generic executable command line application", - "category": "applications", + "category": FC.CATEGORY_APPLICATIONS, "create_function": create_application, "start_function": start_application, - "stop_function": None, + "stop_function": stop_application, "status_function": status_application, "box_attributes": ["command", "user"], "connector_types": ["node"], - "traces": ["stdout", "stderr"] + "traces": ["stdout", "stderr"], + "tags": [tags.APPLICATION], }), + TUNCHANNEL : dict({ + "category": FC.CATEGORY_TUNNELS, + "create_function": create_tunchannel, + "preconfigure_function": preconfigure_tunchannel, + "configure_function": postconfigure_tunchannel, + "prestart_function": wait_tunchannel, + "help": "Channel to forward "+TAPIFACE+" data to " + "other TAP interfaces supporting the NEPI tunneling protocol.", + "connector_types": ["->fd", "udp", "tcp"], + "allow_addresses": False, + "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key"], + "tags": [tags.TUNNEL], + }), }) +testbed_attributes = dict({ + "enable_debug": dict({ + "name": "enableDebug", + "help": "Enable netns debug output", + "type": Attribute.BOOL, + "value": False, + "validation_function": validation.is_bool + }), + }) + class VersionedMetadataInfo(metadata.VersionedMetadataInfo): @property - def connections_types(self): - return connection_types + def connector_types(self): + return connector_types @property def connections(self): @@ -475,10 +568,18 @@ class VersionedMetadataInfo(metadata.VersionedMetadataInfo): return traces @property - def factories_order(self): - return factories_order + def create_order(self): + return create_order + + @property + def configure_order(self): + return configure_order @property def factories_info(self): return factories_info + @property + def testbed_attributes(self): + return testbed_attributes +