added tags to boxes. For now only one tag: MOBILE
[nepi.git] / src / nepi / core / metadata.py
index fe92e05..c8015cd 100644 (file)
@@ -7,6 +7,9 @@ import getpass
 from nepi.util import validation
 from nepi.util.constants import ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, DeploymentConfiguration
 
+# Attribute categories
+CATEGORY_DEPLOYMENT = "Deployment"
+
 class VersionedMetadataInfo(object):
     @property
     def connector_types(self):
@@ -47,6 +50,7 @@ class VersionedMetadataInfo(object):
                 "allowed": array of posible values,
                 "flags": attributes flags,
                 "validation_function": validation function for the attribute
+                "category": category for the attribute
             })
         """
         raise NotImplementedError
@@ -84,6 +88,24 @@ class VersionedMetadataInfo(object):
         """
         return self.configure_order
 
+    @property
+    def prestart_order(self):
+        """ list of factory ids that indicates the order in which the elements
+        should be prestart-configured.
+        
+        Default: same as configure_order
+        """
+        return self.configure_order
+
+    @property
+    def start_order(self):
+        """ list of factory ids that indicates the order in which the elements
+        should be started.
+        
+        Default: same as configure_order
+        """
+        return self.configure_order
+
     @property
     def factories_info(self):
         """ dictionary of dictionaries of factory specific information
@@ -102,9 +124,17 @@ class VersionedMetadataInfo(object):
                     (just after connections are made, 
                     just before netrefs are resolved)
                 "configure_function": function for element configuration,
+                "prestart_function": function for pre-start
+                    element configuration (just before starting applications),
+                    useful for synchronization of background setup tasks or
+                    lazy instantiation or configuration of attributes
+                    that require connection/cross-connection state before
+                    being created.
+                    After this point, all applications should be able to run.
                 "factory_attributes": list of references to attribute_ids,
                 "box_attributes": list of regerences to attribute_ids,
                 "traces": list of references to trace_id
+                "tags": list of references to tag_id
                 "connector_types": list of references to connector_types
            })
         """
@@ -122,6 +152,7 @@ class VersionedMetadataInfo(object):
                 "allowed": array of posible values,
                 "flags": attributes flags,
                 "validation_function": validation function for the attribute
+                "category": category for the attribute
              })
             ]
         """
@@ -149,7 +180,7 @@ class Metadata(object):
             help = "Path to the directory where traces and other files will be stored",
             type = Attribute.STRING,
             value = "",
-            flags = Attribute.DesignOnly,
+            flags = Attribute.DesignOnly
         )),
     )
     
@@ -161,6 +192,7 @@ class Metadata(object):
                 help = "Shell commands to run before spawning TestbedController processes",
                 type = Attribute.STRING,
                 flags = Attribute.DesignOnly,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.DEPLOYMENT_MODE, dict(name = DC.DEPLOYMENT_MODE,
                 help = "Instance execution mode",
@@ -171,7 +203,8 @@ class Metadata(object):
                     DC.MODE_SINGLE_PROCESS
                 ],
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_enum
+                validation_function = validation.is_enum,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.DEPLOYMENT_COMMUNICATION, dict(name = DC.DEPLOYMENT_COMMUNICATION,
                 help = "Instance communication mode",
@@ -182,48 +215,55 @@ class Metadata(object):
                     DC.ACCESS_SSH
                 ],
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_enum
+                validation_function = validation.is_enum,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.DEPLOYMENT_HOST, dict(name = DC.DEPLOYMENT_HOST,
                 help = "Host where the testbed will be executed",
                 type = Attribute.STRING,
                 value = "localhost",
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_string
+                validation_function = validation.is_string,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.DEPLOYMENT_USER, dict(name = DC.DEPLOYMENT_USER,
                 help = "User on the Host to execute the testbed",
                 type = Attribute.STRING,
                 value = getpass.getuser(),
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_string
+                validation_function = validation.is_string,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.DEPLOYMENT_KEY, dict(name = DC.DEPLOYMENT_KEY,
                 help = "Path to SSH key to use for connecting",
                 type = Attribute.STRING,
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_string
+                validation_function = validation.is_string,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.DEPLOYMENT_PORT, dict(name = DC.DEPLOYMENT_PORT,
                 help = "Port on the Host",
                 type = Attribute.INTEGER,
                 value = 22,
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_integer
+                validation_function = validation.is_integer,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.ROOT_DIRECTORY, dict(name = DC.ROOT_DIRECTORY,
                 help = "Root directory for storing process files",
                 type = Attribute.STRING,
                 value = ".",
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_string # TODO: validation.is_path
+                validation_function = validation.is_string, # TODO: validation.is_path
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.USE_AGENT, dict(name = DC.USE_AGENT,
                 help = "Use -A option for forwarding of the authentication agent, if ssh access is used", 
                 type = Attribute.BOOL,
                 value = False,
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_bool
+                validation_function = validation.is_bool,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.LOG_LEVEL, dict(name = DC.LOG_LEVEL,
                 help = "Log level for instance",
@@ -234,14 +274,16 @@ class Metadata(object):
                     DC.DEBUG_LEVEL
                 ],
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_enum
+                validation_function = validation.is_enum,
+                category = CATEGORY_DEPLOYMENT,
         )),
         (DC.RECOVER, dict(name = DC.RECOVER,
                 help = "Do not intantiate testbeds, rather, reconnect to already-running instances. Used to recover from a dead controller.", 
                 type = Attribute.BOOL,
                 value = False,
                 flags = Attribute.DesignOnly,
-                validation_function = validation.is_bool
+                validation_function = validation.is_bool,
+                category = CATEGORY_DEPLOYMENT,
         )),
     )
     
@@ -249,6 +291,47 @@ class Metadata(object):
     
     del DC
     
+    
+    STANDARD_ATTRIBUTE_BUNDLES = {
+            "tun_proto" : dict({
+                "name": "tun_proto", 
+                "help": "TUNneling protocol used",
+                "type": Attribute.STRING,
+                "flags": Attribute.Invisible,
+                "validation_function": validation.is_string,
+            }),
+            "tun_key" : dict({
+                "name": "tun_key", 
+                "help": "Randomly selected TUNneling protocol cryptographic key. "
+                        "Endpoints must agree to use the minimum (in lexicographic order) "
+                        "of both the remote and local sides.",
+                "type": Attribute.STRING,
+                "flags": Attribute.Invisible,
+                "validation_function": validation.is_string,
+            }),
+            "tun_addr" : dict({
+                "name": "tun_addr", 
+                "help": "Address (IP, unix socket, whatever) of the tunnel endpoint",
+                "type": Attribute.STRING,
+                "flags": Attribute.Invisible,
+                "validation_function": validation.is_string,
+            }),
+            "tun_port" : dict({
+                "name": "tun_port", 
+                "help": "IP port of the tunnel endpoint",
+                "type": Attribute.INTEGER,
+                "flags": Attribute.Invisible,
+                "validation_function": validation.is_integer,
+            }),
+            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,
+                "validation_function": validation.is_string
+            }),
+    }
+    
 
     def __init__(self, testbed_id, version):
         self._version = version
@@ -268,6 +351,14 @@ class Metadata(object):
     def preconfigure_order(self):
         return self._metadata.preconfigure_order
 
+    @property
+    def prestart_order(self):
+        return self._metadata.prestart_order
+
+    @property
+    def start_order(self):
+        return self._metadata.start_order
+
     def testbed_attributes(self):
         attributes = AttributesMap()
 
@@ -286,8 +377,9 @@ class Metadata(object):
             flags =  attr_info["flags"] if "flags" in attr_info \
                     else Attribute.NoFlags
             validation_function = attr_info["validation_function"]
+            category = attr_info["category"] if "category" in attr_info else None
             attributes.add_attribute(name, help, type, value, 
-                    range, allowed, flags, validation_function)
+                    range, allowed, flags, validation_function, category)
         
         return attributes
 
@@ -315,6 +407,7 @@ class Metadata(object):
             self._add_attributes(factory, info, "box_attributes", True)
             
             self._add_design_traces(factory, info)
+            self._add_tags(factory, info)
             self._add_design_connector_types(factory, info)
             factories.append(factory)
         return factories
@@ -329,6 +422,7 @@ class Metadata(object):
             status_function = info.get("status_function")
             configure_function = info.get("configure_function")
             preconfigure_function = info.get("preconfigure_function")
+            prestart_function = info.get("prestart_function")
             allow_addresses = info.get("allow_addresses", False)
             allow_routes = info.get("allow_routes", False)
             has_addresses = info.get("has_addresses", False)
@@ -336,6 +430,7 @@ class Metadata(object):
             factory = Factory(factory_id, create_function, start_function,
                     stop_function, status_function, 
                     configure_function, preconfigure_function,
+                    prestart_function,
                     allow_addresses, has_addresses,
                     allow_routes, has_routes)
                     
@@ -348,6 +443,7 @@ class Metadata(object):
             self._add_attributes(factory, info, "box_attributes", True)
             
             self._add_execute_traces(factory, info)
+            self._add_tags(factory, info)
             self._add_execute_connector_types(factory, info)
             factories.append(factory)
         return factories
@@ -372,7 +468,9 @@ class Metadata(object):
 
     def _add_attributes(self, factory, info, attr_key, box_attributes = False, attr_bundle = ()):
         if not attr_bundle and info and attr_key in info:
-            attr_bundle = [ (attr_id, self._metadata.attributes[attr_id])
+            definitions = self.STANDARD_ATTRIBUTE_BUNDLES.copy()
+            definitions.update(self._metadata.attributes)
+            attr_bundle = [ (attr_id, definitions[attr_id])
                             for attr_id in info[attr_key] ]
         for attr_id, attr_info in attr_bundle:
             name = attr_info["name"]
@@ -386,12 +484,13 @@ class Metadata(object):
                     and attr_info["flags"] != None \
                     else Attribute.NoFlags
             validation_function = attr_info["validation_function"]
+            category = attr_info["category"] if "category" in attr_info else None
             if box_attributes:
                 factory.add_box_attribute(name, help, type, value, range, 
-                        allowed, flags, validation_function)
+                        allowed, flags, validation_function, category)
             else:
                 factory.add_attribute(name, help, type, value, range, 
-                        allowed, flags, validation_function)
+                        allowed, flags, validation_function, category)
 
     def _add_design_traces(self, factory, info):
         if "traces" in info:
@@ -408,6 +507,11 @@ class Metadata(object):
                 trace_id = trace_info["name"]
                 factory.add_trace(trace_id)
 
+    def _add_tags(self, factory, info):
+        if "tags" in info:
+            for tag_id in info["tags"]:
+                factory.add_tag(tag_id)
+
     def _add_design_connector_types(self, factory, info):
         from nepi.core.design import ConnectorType
         if "connector_types" in info: