Ticket #45: spanning tree deployment
[nepi.git] / src / nepi / testbeds / planetlab / metadata_v01.py
index 81718da..85ae099 100644 (file)
@@ -43,13 +43,13 @@ def is_addrlist(attribute, value):
         if '/' in component:
             addr, mask = component.split('/',1)
         else:
-            addr, mask = component, 32
+            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):
+        if not validation.is_ip4_address(attribute, addr):
             # Address part must be ipv4
             return False
         
@@ -96,7 +96,7 @@ def connect_tun_iface_node(testbed_instance, node_guid, iface_guid):
         raise RuntimeError, "Use of TUN interfaces requires emulation"
     iface.node = node
     node.required_vsys.update(('fd_tuntap', 'vif_up'))
-    node.required_packages.add('python-crypto')
+    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]
@@ -110,7 +110,7 @@ def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_i
     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")
+    iface.peer_proto = peer_iface_data.get("tun_proto") or proto
     iface.peer_port = peer_iface_data.get("tun_port")
     iface.tun_key = min(iface.tun_key, peer_iface_data.get("tun_key"))
     iface.tun_proto = proto
@@ -118,8 +118,18 @@ def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_i
     preconfigure_tuniface(testbed_instance, iface_guid)
 
 def crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data):
+    # refresh (refreshable) attributes for second-phase
+    iface = testbed_instance._elements[iface_guid]
+    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")
+    
     postconfigure_tuniface(testbed_instance, iface_guid)
 
+def crossconnect_tun_iface_peer_both(proto, testbed_instance, iface_guid, peer_iface_data):
+    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]
@@ -156,12 +166,17 @@ def create_node(testbed_instance, guid):
     
     # add constraint on number of (real) interfaces
     # by counting connected devices
-    dev_guids = testbed_instance.get_connected(guid, "node", "devs")
+    dev_guids = testbed_instance.get_connected(guid, "devs", "node")
     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
     
+    # require vroute vsys if we have routes to set up
+    routes = testbed_instance._add_route.get(guid)
+    if routes:
+        element.required_vsys.add("vroute")
+    
     testbed_instance.elements[guid] = element
 
 def create_nodeiface(testbed_instance, guid):
@@ -172,11 +187,29 @@ def create_nodeiface(testbed_instance, guid):
 def create_tuniface(testbed_instance, guid):
     parameters = testbed_instance._get_parameters(guid)
     element = testbed_instance._make_tun_iface(parameters)
+    
+    # Set custom addresses, if there are any already
+    # Setting this early helps set up P2P links
+    if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
+        addresses = testbed_instance._add_address[guid]
+        for address in addresses:
+            (address, netprefix, broadcast) = address
+            element.add_address(address, netprefix, broadcast)
+    
     testbed_instance.elements[guid] = element
 
 def create_tapiface(testbed_instance, guid):
     parameters = testbed_instance._get_parameters(guid)
     element = testbed_instance._make_tap_iface(parameters)
+    
+    # Set custom addresses, if there are any already
+    # Setting this early helps set up P2P links
+    if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
+        addresses = testbed_instance._add_address[guid]
+        for address in addresses:
+            (address, netprefix, broadcast) = address
+            element.add_address(address, netprefix, broadcast)
+    
     testbed_instance.elements[guid] = element
 
 def create_application(testbed_instance, guid):
@@ -276,8 +309,8 @@ def configure_nodeiface(testbed_instance, guid):
 def preconfigure_tuniface(testbed_instance, guid):
     element = testbed_instance._elements[guid]
     
-    # Set custom addresses if any
-    if guid in testbed_instance._add_address:
+    # Set custom addresses if any, and if not set already
+    if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
         addresses = testbed_instance._add_address[guid]
         for address in addresses:
             (address, netprefix, broadcast) = address
@@ -326,6 +359,12 @@ def postconfigure_tuniface(testbed_instance, guid):
     # Second-phase setup
     element.setup()
     
+def wait_tuniface(testbed_instance, guid):
+    element = testbed_instance._elements[guid]
+    
+    # Second-phase setup
+    element.async_launch_wait()
+    
 
 def configure_node(testbed_instance, guid):
     node = testbed_instance._elements[guid]
@@ -348,6 +387,18 @@ def configure_node(testbed_instance, guid):
     # this call only spawns the process
     node.install_dependencies()
 
+def configure_node_routes(testbed_instance, guid):
+    node = testbed_instance._elements[guid]
+    routes = testbed_instance._add_route.get(guid)
+    
+    if routes:
+        devs = [ dev
+            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) ]
+        
+        node.configure_routes(routes, devs)
+
 def configure_application(testbed_instance, guid):
     app = testbed_instance._elements[guid]
     
@@ -358,7 +409,7 @@ def configure_application(testbed_instance, guid):
     app.node.wait_dependencies()
     
     # Install stuff
-    app.setup()
+    app.async_setup()
 
 def configure_dependency(testbed_instance, guid):
     dep = testbed_instance._elements[guid]
@@ -370,7 +421,7 @@ def configure_dependency(testbed_instance, guid):
     dep.node.wait_dependencies()
     
     # Install stuff
-    dep.setup()
+    dep.async_setup()
 
 def configure_netpipe(testbed_instance, guid):
     netpipe = testbed_instance._elements[guid]
@@ -437,6 +488,12 @@ connector_types = dict({
                 "max": 1, 
                 "min": 0
             }),
+    "fd->": dict({
+                "help": "TUN device file descriptor provider", 
+                "name": "fd->",
+                "max": 1, 
+                "min": 0
+            }),
    })
 
 connections = [
@@ -532,6 +589,12 @@ connections = [
         "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
         "can_cross": True
     }),
+    dict({
+        "from": (TESTBED_ID, TUNIFACE, "fd->"),
+        "to":   (None, None, "->fd"),
+        "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
+        "can_cross": True
+    }),
     dict({
         "from": (TESTBED_ID, TAPIFACE, "tcp"),
         "to":   (None, None, "tcp"),
@@ -546,6 +609,12 @@ connections = [
         "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
         "can_cross": True
     }),
+    dict({
+        "from": (TESTBED_ID, TAPIFACE, "fd->"),
+        "to":   (None, None, "->fd"),
+        "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
+        "can_cross": True
+    }),
 ]
 
 attributes = dict({
@@ -677,6 +746,14 @@ attributes = dict({
                 "value": False,
                 "validation_function": validation.is_bool
             }),
+    "pointopoint":  dict({
+                "name": "pointopoint", 
+                "help": "If the interface is a P2P link, the remote endpoint's IP "
+                        "should be set on this attribute.",
+                "type": Attribute.STRING,
+                "flags": Attribute.DesignOnly,
+                "validation_function": validation.is_string
+            }),
     "txqueuelen":  dict({
                 "name": "mask", 
                 "help": "Transmission queue length (in packets)",
@@ -762,13 +839,6 @@ attributes = dict({
                 "flags": Attribute.DesignOnly,
                 "validation_function": validation.is_string
             }),
-    ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP: dict({
-                "name": ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
-                "help": "Commands to set up the environment needed to run NEPI testbeds",
-                "type": Attribute.STRING,
-                "flags": Attribute.Invisible | Attribute.ReadOnly,
-                "validation_function": validation.is_string
-            }),
     
     "netpipe_mode": dict({      
                 "name": "mode",
@@ -864,13 +934,17 @@ create_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEP
 
 configure_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, 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 ]
+
 factories_info = dict({
     NODE: dict({
-            "allow_routes": False,
+            "allow_routes": True,
             "help": "Virtualized Node (V-Server style)",
             "category": "topology",
             "create_function": create_node,
             "preconfigure_function": configure_node,
+            "prestart_function": configure_node_routes,
             "box_attributes": [
                 "forward_X11",
                 "hostname",
@@ -904,13 +978,14 @@ factories_info = dict({
             "create_function": create_tuniface,
             "preconfigure_function": preconfigure_tuniface,
             "configure_function": postconfigure_tuniface,
+            "prestart_function": wait_tuniface,
             "box_attributes": [
-                "up", "device_name", "mtu", "snat",
+                "up", "device_name", "mtu", "snat", "pointopoint",
                 "txqueuelen",
                 "tun_proto", "tun_addr", "tun_port", "tun_key"
             ],
             "traces": ["packets"],
-            "connector_types": ["node","udp","tcp"]
+            "connector_types": ["node","udp","tcp","fd->"]
         }),
     TAPIFACE: dict({
             "allow_addresses": True,
@@ -919,13 +994,14 @@ factories_info = dict({
             "create_function": create_tapiface,
             "preconfigure_function": preconfigure_tuniface,
             "configure_function": postconfigure_tuniface,
+            "prestart_function": wait_tuniface,
             "box_attributes": [
-                "up", "device_name", "mtu", "snat",
+                "up", "device_name", "mtu", "snat", "pointopoint",
                 "txqueuelen",
-                "tun_proto", "tun_addr", "tun_port"
+                "tun_proto", "tun_addr", "tun_port", "tun_key"
             ],
             "traces": ["packets"],
-            "connector_types": ["node","udp","tcp"]
+            "connector_types": ["node","udp","tcp","fd->"]
         }),
     APPLICATION: dict({
             "help": "Generic executable command line application",
@@ -945,7 +1021,7 @@ factories_info = dict({
             "help": "Requirement for package or application to be installed on some node",
             "category": "applications",
             "create_function": create_dependency,
-            "configure_function": configure_dependency,
+            "preconfigure_function": configure_dependency,
             "box_attributes": ["depends", "build-depends", "build", "install",
                                "sources" ],
             "connector_types": ["node"],
@@ -955,7 +1031,7 @@ factories_info = 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,
+            "preconfigure_function": configure_dependency,
             "box_attributes": [ ],
             "connector_types": ["node"],
             "traces": ["buildlog"]
@@ -964,7 +1040,7 @@ factories_info = dict({
             "help": "Requirement for NS3 inside NEPI - required to run NS3 testbed instances inside a node. It also needs NepiDependency.",
             "category": "applications",
             "create_function": create_ns3_dependency,
-            "configure_function": configure_dependency,
+            "preconfigure_function": configure_dependency,
             "box_attributes": [ ],
             "connector_types": ["node"],
             "traces": ["buildlog"]
@@ -1011,6 +1087,22 @@ testbed_attributes = dict({
             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
             "validation_function": validation.is_string
         }),
+        "plc_host": dict({
+            "name": "plcHost",
+            "help": "The PlanetLab PLC API host",
+            "type": Attribute.STRING,
+            "value": "www.planet-lab.eu",
+            "flags": Attribute.DesignOnly,
+            "validation_function": validation.is_string
+        }),
+        "plc_url": dict({
+            "name": "plcUrl",
+            "help": "The PlanetLab PLC API url pattern - %(hostname)s is replaced by plcHost.",
+            "type": Attribute.STRING,
+            "value": "https://%(hostname)s:443/PLCAPI/",
+            "flags": Attribute.DesignOnly,
+            "validation_function": validation.is_string
+        }),
         "slice_ssh_key": dict({
             "name": "sliceSSHKey",
             "help": "The controller-local path to the slice user's ssh private key. "
@@ -1049,6 +1141,14 @@ class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
     def configure_order(self):
         return configure_order
 
+    @property
+    def prestart_order(self):
+        return start_order
+
+    @property
+    def start_order(self):
+        return start_order
+
     @property
     def factories_info(self):
         return factories_info