Merge with head
authorClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Wed, 6 Jul 2011 15:22:54 +0000 (17:22 +0200)
committerClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Wed, 6 Jul 2011 15:22:54 +0000 (17:22 +0200)
18 files changed:
src/nepi/core/attributes.py
src/nepi/core/connector.py
src/nepi/core/design.py
src/nepi/core/execute.py
src/nepi/core/factory.py [new file with mode: 0644]
src/nepi/core/metadata.py
src/nepi/core/testbed_impl.py
src/nepi/testbeds/netns/execute.py
src/nepi/testbeds/netns/metadata_v01.py
src/nepi/testbeds/ns3/factories_metadata_v3_9.py
src/nepi/testbeds/planetlab/metadata_v01.py
src/nepi/util/parser/base.py
src/nepi/util/proxy.py
src/nepi/util/tags.py
test/core/design.py
test/core/execute.py
test/lib/mock/metadata_v01.py
test/lib/mock2/metadata_v01.py

index 151cab0..272cbef 100644 (file)
@@ -39,7 +39,7 @@ class Attribute(object):
     HasNoDefaultValue = 0x08
 
     def __init__(self, name, help, type, value = None, range = None,
-        allowed = None, flags = NoFlags, validation_function = None, 
+        allowed = None, flags = None, validation_function = None, 
         category = None):
         if not type in Attribute.types:
             raise AttributeError("invalid type %s " % type)
@@ -47,7 +47,7 @@ class Attribute(object):
         self._type = type
         self._help = help
         self._value = value
-        self._flags = flags
+        self._flags = flags if flags != None else Attribute.NoFlags
         # range: max and min possible values
         self._range = range
         # list of possible values
@@ -143,15 +143,21 @@ class AttributesMap(object):
     are going to be manipulated by the end-user in a script or GUI.
     """
     def __init__(self):
+        super(AttributesMap, self).__init__()
         self._attributes = dict()
 
     @property
     def attributes(self):
         return self._attributes.values()
 
-    @property
-    def attributes_list(self):
-        return self._attributes.keys()
+    def get_attribute_list(self, filter_flags = None):
+        attributes = self._attributes
+        if filter_flags != None:
+            def filter_attrs(attr):
+                (attr_id, attr_data) = attr
+                return not ((attr_data.flags & filter_flags) == filter_flags)
+            attributes = dict(filter(filter_attrs, attributes.iteritems()))
+        return attributes.keys()
 
     def set_attribute_value(self, name, value):
         self._attributes[name].value = value
index e85d2bf..2fb450f 100644 (file)
@@ -2,31 +2,49 @@
 # -*- coding: utf-8 -*-
 
 """
-Common connector base classes
+Common connector class
 """
 
 import sys
 
-class ConnectorTypeBase(object):
-    def __init__(self, testbed_id, factory_id, name, max = -1, min = 0):
-        super(ConnectorTypeBase, self).__init__()
+class ConnectorType(object):
+    def __init__(self, testbed_id, factory_id, name, help, max = -1, min = 0):
+        super(ConnectorType, self).__init__()
+
         if max == -1:
             max = sys.maxint
         elif max <= 0:
             raise RuntimeError, "The maximum number of connections allowed need to be more than 0"
         if min < 0:
             raise RuntimeError, "The minimum number of connections allowed needs to be at least 0"
+        # max -- maximum amount of connections that this type support, 
+        # -1 for no limit
+        self._max = max
+        # min -- minimum amount of connections required by this type of connector
+        self._min = min
+       
         # connector_type_id -- univoquely identifies a connector type 
         # across testbeds
         self._connector_type_id = self.make_connector_type_id(
             testbed_id, factory_id, name)
+        
         # name -- display name for the connector type
         self._name = name
-        # max -- maximum amount of connections that this type support, 
-        # -1 for no limit
-        self._max = max
-        # min -- minimum amount of connections required by this type of connector
-        self._min = min
+
+        # help -- help text
+        self._help = help
+
+        # from_connections -- connections where the other connector is the "From"
+        # to_connections -- connections where the other connector is the "To"
+        # keys in the dictionary correspond to the 
+        # connector_type_id for possible connections. The value is a tuple:
+        # (can_cross, connect)
+        # can_cross: indicates if the connection is allowed accros different
+        #    testbed instances
+        # code: is the connection function to be invoked when the elements
+        #    are connected
+        self._from_connections = dict()
+        self._to_connections = dict()
 
     def __str__(self):
         return "ConnectorType%r" % (self._connector_type_id,)
@@ -39,6 +57,10 @@ class ConnectorTypeBase(object):
     def name(self):
         return self._name
 
+    @property
+    def help(self):
+        return self._help
+
     @property
     def max(self):
         return self._max
@@ -69,4 +91,44 @@ class ConnectorTypeBase(object):
         if (None, None, name) != connector_type_id:
             yield (None, None, name)
 
+    def add_from_connection(self, testbed_id, factory_id, name, can_cross, 
+            init_code, compl_code):
+        type_id = self.make_connector_type_id(testbed_id, factory_id, name)
+        self._from_connections[type_id] = (can_cross, init_code, compl_code)
+
+    def add_to_connection(self, testbed_id, factory_id, name, can_cross, 
+            init_code, compl_code):
+        type_id = self.make_connector_type_id(testbed_id, factory_id, name)
+        self._to_connections[type_id] = (can_cross, init_code, compl_code)
+
+    def connect_to_init_code(self, testbed_id, factory_id, name, must_cross):
+        return self._connect_to_code(testbed_id, factory_id, name, must_cross)[0]
+
+    def connect_to_compl_code(self, testbed_id, factory_id, name, must_cross):
+        return self._connect_to_code(testbed_id, factory_id, name, must_cross)[1]
+
+    def _connect_to_code(self, testbed_id, factory_id, name,
+            must_cross):
+        connector_type_id = self.make_connector_type_id(testbed_id, factory_id, name)
+        for lookup_type_id in self._type_resolution_order(connector_type_id):
+            if lookup_type_id in self._to_connections:
+                (can_cross, init_code, compl_code) = self._to_connections[lookup_type_id]
+                if must_cross == can_cross:
+                    return (init_code, compl_code)
+        else:
+            return (False, False)
+    def can_connect(self, testbed_id, factory_id, name, must_cross):
+        connector_type_id = self.make_connector_type_id(testbed_id, factory_id, name)
+        for lookup_type_id in self._type_resolution_order(connector_type_id):
+            if lookup_type_id in self._from_connections:
+                (can_cross, init_code, compl_code) = self._from_connections[lookup_type_id]
+            elif lookup_type_id in self._to_connections:
+                (can_cross, init_code, compl_code) = self._to_connections[lookup_type_id]
+            else:
+                # keep trying
+                continue
+            return not must_cross or can_cross
+        else:
+            return False
 
index 03d6319..602667c 100644 (file)
@@ -6,43 +6,14 @@ Experiment design API
 """
 
 from nepi.core.attributes import AttributesMap, Attribute
-from nepi.core.connector import ConnectorTypeBase
 from nepi.core.metadata import Metadata
 from nepi.util import validation
 from nepi.util.guid import GuidGenerator
 from nepi.util.graphical_info import GraphicalInfo
 from nepi.util.parser._xml import XmlExperimentParser
+from nepi.util.tags import Taggable
 import sys
 
-class ConnectorType(ConnectorTypeBase):
-    def __init__(self, testbed_id, factory_id, name, help, max = -1, min = 0):
-        super(ConnectorType, self).__init__(testbed_id, factory_id, name, max, min)
-        
-        # help -- help text
-        self._help = help
-        
-        # allowed_connections -- keys in the dictionary correspond to the 
-        # connector_type_id for possible connections. The value indicates if
-        # the connection is allowed accros different testbed instances
-        self._allowed_connections = dict()
-
-    @property
-    def help(self):
-        return self._help
-
-    def add_allowed_connection(self, testbed_id, factory_id, name, can_cross):
-        type_id = self.make_connector_type_id(testbed_id, factory_id, name)
-        self._allowed_connections[type_id] = can_cross
-
-    def can_connect(self, connector_type_id, testbed_guid1, testbed_guid2):
-        for lookup_type_id in self._type_resolution_order(connector_type_id):
-            if lookup_type_id in self._allowed_connections:
-                can_cross = self._allowed_connections[lookup_type_id]
-                if can_cross or (testbed_guid1 == testbed_guid2):
-                    return True
-        else:
-            return False
-
 class Connector(object):
     """A Connector sepcifies the connection points in an Object"""
     def __init__(self, box, connector_type):
@@ -100,11 +71,12 @@ class Connector(object):
             return False
         if self.is_connected(connector):
             return False
-        connector_type_id = connector.connector_type.connector_type_id
+        (testbed_id, factory_id, name) = connector.connector_type.connector_type_id
         testbed_guid1 = self.box.testbed_guid
         testbed_guid2 = connector.box.testbed_guid
-        return self.connector_type.can_connect(connector_type_id, 
-                testbed_guid1, testbed_guid2)
+        must_cross = (testbed_guid1 != testbed_guid2)
+        return self.connector_type.can_connect(testbed_id, factory_id, name,
+                must_cross)
 
     def destroy(self):
         for connector in self.connections:
@@ -112,15 +84,15 @@ class Connector(object):
         self._box = self._connectors = None
 
 class Trace(AttributesMap):
-    def __init__(self, trace_id, help, enabled = False):
+    def __init__(self, name, help, enabled = False):
         super(Trace, self).__init__()
-        self._trace_id = trace_id
+        self._name = name
         self._help = help       
         self._enabled = enabled
     
     @property
-    def trace_id(self):
-        return self._trace_id
+    def name(self):
+        return self._name
 
     @property
     def help(self):
@@ -182,7 +154,7 @@ class Route(AttributesMap):
                 flags = Attribute.HasNoDefaultValue,
                 validation_function = validation.is_integer)
 
-class Box(AttributesMap):
+class Box(AttributesMap, Taggable):
     def __init__(self, guid, factory, testbed_guid, container = None):
         super(Box, self).__init__()
         # guid -- global unique identifier
@@ -195,8 +167,6 @@ class Box(AttributesMap):
         self._container = container
         # traces -- list of available traces for the box
         self._traces = dict()
-        # tags -- list of tags for the box
-        self._tags = list()
         # connectors -- list of available connectors for the box
         self._connectors = dict()
         # factory_attributes -- factory attributes for box construction
@@ -207,11 +177,11 @@ class Box(AttributesMap):
         for connector_type in factory.connector_types:
             connector = Connector(self, connector_type)
             self._connectors[connector_type.name] = connector
-        for trace in factory.traces:
-            tr = Trace(trace.trace_id, trace.help, trace.enabled)
-            self._traces[trace.trace_id] = tr
+        for (name, help, enabled) in factory.traces:
+            trace = Trace(name, help, enabled)
+            self._traces[name] = trace
         for tag_id in factory.tags:
-            self._tags.append(tag_id)
+            self.add_tag(tag_id)
         for attr in factory.box_attributes.attributes:
             self.add_attribute(attr.name, attr.help, attr.type, attr.value, 
                     attr.range, attr.allowed, attr.flags, 
@@ -248,17 +218,13 @@ class Box(AttributesMap):
         return self._traces.values()
 
     @property
-    def trace_names(self):
+    def traces_list(self):
         return self._traces.keys()
 
     @property
     def factory_attributes(self):
         return self._factory_attributes
 
-    @property
-    def tags(self):
-        return self._tags
-
     def trace_help(self, trace_id):
         return self._traces[trace_id].help
 
@@ -282,213 +248,6 @@ class Box(AttributesMap):
             t.destroy()
         self._connectors = self._traces = self._factory_attributes = None
 
-class AddressableMixin(object):
-    def __init__(self, guid, factory, testbed_guid, container = None):
-        super(AddressableMixin, self).__init__(guid, factory, testbed_guid, 
-                container)
-        max_addr =  self._factory_attributes["MaxAddresses"] \
-                if "MaxAddresses" in self._factory_attributes else 1
-        self._max_addresses = max_addr
-        self._addresses = list()
-
-    @property
-    def addresses(self):
-        return self._addresses
-
-    @property
-    def max_addresses(self):
-        return self._max_addresses
-
-class UserAddressableMixin(AddressableMixin):
-    def __init__(self, guid, factory, testbed_guid, container = None):
-        super(UserAddressableMixin, self).__init__(guid, factory, testbed_guid, 
-                container)
-
-    def add_address(self):
-        if len(self._addresses) == self.max_addresses:
-            raise RuntimeError("Maximun number of addresses for this box reached.")
-        address = Address()
-        self._addresses.append(address)
-        return address
-
-    def delete_address(self, address):
-        self._addresses.remove(address)
-        del address
-
-    def destroy(self):
-        super(UserAddressableMixin, self).destroy()
-        for address in list(self.addresses):
-            self.delete_address(address)
-        self._addresses = None
-
-class RoutableMixin(object):
-    def __init__(self, guid, factory, testbed_guid, container = None):
-        super(RoutableMixin, self).__init__(guid, factory, testbed_guid, 
-            container)
-        self._routes = list()
-
-    @property
-    def routes(self):
-        return self._routes
-
-class UserRoutableMixin(RoutableMixin):
-    def __init__(self, guid, factory, testbed_guid, container = None):
-        super(UserRoutableMixin, self).__init__(guid, factory, testbed_guid, 
-            container)
-
-    def add_route(self):
-        route = Route()
-        self._routes.append(route)
-        return route
-
-    def delete_route(self, route):
-        self._routes.remove(route)
-        del route
-
-    def destroy(self):
-        super(UserRoutableMixin, self).destroy()
-        for route in list(self.routes):
-            self.delete_route(route)
-        self._route = None
-
-def MixIn(MyClass, MixIn):
-    # Mixins are installed BEFORE "Box" because
-    # Box inherits from non-cooperative classes,
-    # so the MRO chain gets broken when it gets
-    # to Box.
-
-    # Install mixin
-    MyClass.__bases__ = (MixIn,) + MyClass.__bases__
-    
-    # Add properties
-    # Somehow it doesn't work automatically
-    for name in dir(MixIn):
-        prop = getattr(MixIn,name,None)
-        if isinstance(prop, property):
-            setattr(MyClass, name, prop)
-    
-    # Update name
-    MyClass.__name__ = MyClass.__name__.replace(
-        'Box',
-        MixIn.__name__.replace('MixIn','')+'Box',
-        1)
-
-class Factory(AttributesMap):
-    _box_class_cache = {}
-        
-    def __init__(self, factory_id, 
-            allow_addresses = False, has_addresses = False,
-            allow_routes = False, has_routes = False,
-            Help = None, category = None):
-        super(Factory, self).__init__()
-        self._factory_id = factory_id
-        self._allow_addresses = bool(allow_addresses)
-        self._allow_routes = bool(allow_routes)
-        self._has_addresses = bool(allow_addresses) or self._allow_addresses
-        self._has_routes = bool(allow_routes) or self._allow_routes
-        self._help = help
-        self._category = category
-        self._connector_types = list()
-        self._traces = list()
-        self._tags = list()
-        self._box_attributes = AttributesMap()
-        
-        if not self._has_addresses and not self._has_routes:
-            self._factory = Box
-        else:
-            addresses = 'w' if self._allow_addresses else ('r' if self._has_addresses else '-')
-            routes    = 'w' if self._allow_routes else ('r' if self._has_routes else '-')
-            key = addresses+routes
-            
-            if key in self._box_class_cache:
-                self._factory = self._box_class_cache[key]
-            else:
-                # Create base class
-                class _factory(Box):
-                    def __init__(self, guid, factory, testbed_guid, container = None):
-                        super(_factory, self).__init__(guid, factory, testbed_guid, container)
-                
-                # Add mixins, one by one
-                if allow_addresses:
-                    MixIn(_factory, UserAddressableMixin)
-                elif has_addresses:
-                    MixIn(_factory, AddressableMixin)
-                    
-                if allow_routes:
-                    MixIn(_factory, UserRoutableMixin)
-                elif has_routes:
-                    MixIn(_factory, RoutableMixin)
-                
-                # Put into cache
-                self._box_class_cache[key] = self._factory = _factory
-
-    @property
-    def factory_id(self):
-        return self._factory_id
-
-    @property
-    def allow_addresses(self):
-        return self._allow_addresses
-
-    @property
-    def allow_routes(self):
-        return self._allow_routes
-
-    @property
-    def has_addresses(self):
-        return self._has_addresses
-
-    @property
-    def has_routes(self):
-        return self._has_routes
-
-    @property
-    def help(self):
-        return self._help
-
-    @property
-    def category(self):
-        return self._category
-
-    @property
-    def connector_types(self):
-        return self._connector_types
-
-    @property
-    def traces(self):
-        return self._traces
-
-    @property
-    def tags(self):
-        return self._tags
-    
-    @property
-    def box_attributes(self):
-        return self._box_attributes
-
-    def add_connector_type(self, connector_type):
-        self._connector_types.append(connector_type)
-
-    def add_trace(self, trace_id, help, enabled = False):
-        trace = Trace(trace_id, help, enabled)
-        self._traces.append(trace)
-
-    def add_tag(self, tag_id):
-        self._tags.append(tag_id)
-
-    def add_box_attribute(self, name, help, type, value = None, range = None,
-        allowed = None, flags = Attribute.NoFlags, validation_function = None,
-        category = None):
-        self._box_attributes.add_attribute(name, help, type, value, range,
-                allowed, flags, validation_function, category)
-
-    def create(self, guid, testbed_description):
-        return self._factory(guid, self, testbed_description.guid)
-
-    def destroy(self):
-        super(Factory, self).destroy()
-        self._connector_types = None
-
 class FactoriesProvider(object):
     def __init__(self, testbed_id, testbed_version):
         super(FactoriesProvider, self).__init__()
@@ -497,7 +256,7 @@ class FactoriesProvider(object):
         self._factories = dict()
 
         metadata = Metadata(testbed_id, testbed_version) 
-        for factory in metadata.build_design_factories():
+        for factory in metadata.build_factories():
             self.add_factory(factory)
 
     @property
index 5a191ae..5e88e1c 100644 (file)
@@ -2,7 +2,6 @@
 # -*- coding: utf-8 -*-
 
 from nepi.core.attributes import Attribute, AttributesMap
-from nepi.core.connector import ConnectorTypeBase
 from nepi.util import validation
 from nepi.util.constants import ApplicationStatus as AS, TIME_NOW
 from nepi.util.parser._xml import XmlExperimentParser
@@ -18,166 +17,6 @@ ATTRIBUTE_PATTERN_BASE = re.compile(r"\{#\[(?P<label>[-a-zA-Z0-9._]*)\](?P<expr>
 ATTRIBUTE_PATTERN_GUID_SUB = r"{#[%(guid)s]%(expr)s#}"
 COMPONENT_PATTERN = re.compile(r"(?P<kind>[a-z]*)\[(?P<index>.*)\]")
 
-class ConnectorType(ConnectorTypeBase):
-    def __init__(self, testbed_id, factory_id, name, max = -1, min = 0):
-        super(ConnectorType, self).__init__(testbed_id, factory_id, name, max, min)
-        # from_connections -- connections where the other connector is the "From"
-        # to_connections -- connections where the other connector is the "To"
-        # keys in the dictionary correspond to the 
-        # connector_type_id for possible connections. The value is a tuple:
-        # (can_cross, connect)
-        # can_cross: indicates if the connection is allowed accros different
-        #    testbed instances
-        # code: is the connection function to be invoked when the elements
-        #    are connected
-        self._from_connections = dict()
-        self._to_connections = dict()
-
-    def add_from_connection(self, testbed_id, factory_id, name, can_cross, 
-            init_code, compl_code):
-        type_id = self.make_connector_type_id(testbed_id, factory_id, name)
-        self._from_connections[type_id] = (can_cross, init_code, compl_code)
-
-    def add_to_connection(self, testbed_id, factory_id, name, can_cross, 
-            init_code, compl_code):
-        type_id = self.make_connector_type_id(testbed_id, factory_id, name)
-        self._to_connections[type_id] = (can_cross, init_code, compl_code)
-
-    def can_connect(self, testbed_id, factory_id, name, count, 
-            must_cross):
-        connector_type_id = self.make_connector_type_id(testbed_id, factory_id, name)
-        for lookup_type_id in self._type_resolution_order(connector_type_id):
-            if lookup_type_id in self._from_connections:
-                (can_cross, init_code, compl_code) = self._from_connections[lookup_type_id]
-            elif lookup_type_id in self._to_connections:
-                (can_cross, init_code, compl_code) = self._to_connections[lookup_type_id]
-            else:
-                # keep trying
-                continue
-            return not must_cross or can_cross
-        else:
-            return False
-
-    def _connect_to_code(self, testbed_id, factory_id, name,
-            must_cross):
-        connector_type_id = self.make_connector_type_id(testbed_id, factory_id, name)
-        for lookup_type_id in self._type_resolution_order(connector_type_id):
-            if lookup_type_id in self._to_connections:
-                (can_cross, init_code, compl_code) = self._to_connections[lookup_type_id]
-                if not must_cross or can_cross:
-                    return (init_code, compl_code)
-        else:
-            return (False, False)
-    
-    def connect_to_init_code(self, testbed_id, factory_id, name, must_cross):
-        return self._connect_to_code(testbed_id, factory_id, name, must_cross)[0]
-
-    def connect_to_compl_code(self, testbed_id, factory_id, name, must_cross):
-        return self._connect_to_code(testbed_id, factory_id, name, must_cross)[1]
-
-class Factory(AttributesMap):
-    def __init__(self, factory_id, create_function, start_function, 
-            stop_function, status_function, 
-            configure_function, preconfigure_function,
-            prestart_function,
-            allow_addresses = False, has_addresses = False,
-            allow_routes = False, has_routes = False):
-        super(Factory, self).__init__()
-        self._factory_id = factory_id
-        self._allow_addresses = bool(allow_addresses)
-        self._allow_routes = bool(allow_routes)
-        self._has_addresses = bool(has_addresses) or self._allow_addresses
-        self._has_routes = bool(has_routes) or self._allow_routes
-        self._create_function = create_function
-        self._start_function = start_function
-        self._stop_function = stop_function
-        self._status_function = status_function
-        self._configure_function = configure_function
-        self._preconfigure_function = preconfigure_function
-        self._prestart_function = prestart_function
-        self._connector_types = dict()
-        self._traces = list()
-        self._tags = list()
-        self._box_attributes = AttributesMap()
-
-    @property
-    def factory_id(self):
-        return self._factory_id
-
-    @property
-    def allow_addresses(self):
-        return self._allow_addresses
-
-    @property
-    def allow_routes(self):
-        return self._allow_routes
-
-    @property
-    def has_addresses(self):
-        return self._has_addresses
-
-    @property
-    def has_routes(self):
-        return self._has_routes
-
-    @property
-    def box_attributes(self):
-        return self._box_attributes
-
-    @property
-    def create_function(self):
-        return self._create_function
-
-    @property
-    def prestart_function(self):
-        return self._prestart_function
-
-    @property
-    def start_function(self):
-        return self._start_function
-
-    @property
-    def stop_function(self):
-        return self._stop_function
-
-    @property
-    def status_function(self):
-        return self._status_function
-
-    @property
-    def configure_function(self):
-        return self._configure_function
-
-    @property
-    def preconfigure_function(self):
-        return self._preconfigure_function
-
-    @property
-    def traces(self):
-        return self._traces
-
-    @property
-    def tags(self):
-        return self._tags
-
-    def connector_type(self, name):
-        return self._connector_types[name]
-
-    def add_connector_type(self, connector_type):
-        self._connector_types[connector_type.name] = connector_type
-
-    def add_trace(self, trace_id):
-        self._traces.append(trace_id)
-
-    def add_tag(self, tag_id):
-        self._tags.append(tag_id)
-
-    def add_box_attribute(self, name, help, type, value = None, range = None,
-        allowed = None, flags = Attribute.NoFlags, validation_function = None,
-        category = None):
-        self._box_attributes.add_attribute(name, help, type, value, range, 
-                allowed, flags, validation_function, category)
-
 class TestbedController(object):
     def __init__(self, testbed_id, testbed_version):
         self._testbed_id = testbed_id
@@ -326,7 +165,7 @@ class TestbedController(object):
         """
         raise NotImplementedError
 
-    def get_attribute_list(self, guid):
+    def get_attribute_list(self, guid, filter_flags = None):
         raise NotImplementedError
 
     def get_factory_id(self, guid):
@@ -429,7 +268,6 @@ class ExperimentController(object):
     def start(self):
         parser = XmlExperimentParser()
         data = parser.from_xml_to_data(self._experiment_xml)
-        
         self._init_testbed_controllers(data)
         
         # persist testbed connection data, for potential recovery
@@ -529,7 +367,7 @@ class ExperimentController(object):
         for testbed_guid, testbed_config in self._deployment_config.iteritems():
             testbed_guid = str(testbed_guid)
             conf.add_section(testbed_guid)
-            for attr in testbed_config.attributes_list:
+            for attr in testbed_config.get_attribute_list():
                 if attr not in TRANSIENT:
                     conf.set(testbed_guid, attr, 
                         testbed_config.get_attribute_value(attr))
@@ -560,7 +398,7 @@ class ExperimentController(object):
                 
             testbed_guid = str(testbed_guid)
             conf.add_section(testbed_guid)
-            for attr in testbed_config.attributes_list:
+            for attr in testbed_config.get_attribute_list():
                 if attr not in TRANSIENT:
                     getter = getattr(conf, TYPEMAP.get(
                         testbed_config.get_attribute_type(attr),
@@ -933,8 +771,9 @@ class ExperimentController(object):
                     _testbed_id = cross_testbed.testbed_id,
                     _testbed_version = cross_testbed.testbed_version)
                 cross_data[cross_testbed_guid][cross_guid] = elem_cross_data
-                attributes_list = cross_testbed.get_attribute_list(cross_guid)
-                for attr_name in attributes_list:
+                attribute_list = cross_testbed.get_attribute_list(cross_guid,
+                        filter_flags = Attribute.DesignOnly)
+                for attr_name in attribute_list:
                     attr_value = cross_testbed.get(cross_guid, attr_name)
                     elem_cross_data[attr_name] = attr_value
         return cross_data
diff --git a/src/nepi/core/factory.py b/src/nepi/core/factory.py
new file mode 100644 (file)
index 0000000..7e3869d
--- /dev/null
@@ -0,0 +1,263 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from nepi.core.attributes import AttributesMap, Attribute
+from nepi.util import tags
+from nepi.util.tags import Taggable
+
+class AddressableMixin(object):
+    def __init__(self, guid, factory, testbed_guid, container = None):
+        super(AddressableMixin, self).__init__(guid, factory, testbed_guid, 
+                container)
+        max_addr = self._factory_attributes["maxAddresses"]
+        self._max_addresses = max_addr
+        self._addresses = list()
+
+    @property
+    def addresses(self):
+        return self._addresses
+
+    @property
+    def max_addresses(self):
+        return self._max_addresses
+
+class UserAddressableMixin(AddressableMixin):
+    def __init__(self, guid, factory, testbed_guid, container = None):
+        super(UserAddressableMixin, self).__init__(guid, factory, testbed_guid, 
+                container)
+
+    def add_address(self):
+        if len(self._addresses) == self.max_addresses:
+            raise RuntimeError("Maximun number of addresses for this box reached.")
+        from nepi.core.design import Address
+        address = Address()
+        self._addresses.append(address)
+        return address
+
+    def delete_address(self, address):
+        self._addresses.remove(address)
+        del address
+
+    def destroy(self):
+        super(UserAddressableMixin, self).destroy()
+        for address in list(self.addresses):
+            self.delete_address(address)
+        self._addresses = None
+
+class RoutableMixin(object):
+    def __init__(self, guid, factory, testbed_guid, container = None):
+        super(RoutableMixin, self).__init__(guid, factory, testbed_guid, 
+            container)
+        self._routes = list()
+
+    @property
+    def routes(self):
+        return self._routes
+
+class UserRoutableMixin(RoutableMixin):
+    def __init__(self, guid, factory, testbed_guid, container = None):
+        super(UserRoutableMixin, self).__init__(guid, factory, testbed_guid, 
+            container)
+
+    def add_route(self):
+        from nepi.core.design import Route
+        route = Route()
+        self._routes.append(route)
+        return route
+
+    def delete_route(self, route):
+        self._routes.remove(route)
+        del route
+
+    def destroy(self):
+        super(UserRoutableMixin, self).destroy()
+        for route in list(self.routes):
+            self.delete_route(route)
+        self._route = None
+
+def MixIn(MyClass, MixIn):
+    # Mixins are installed BEFORE "Box" because
+    # Box inherits from non-cooperative classes,
+    # so the MRO chain gets broken when it gets
+    # to Box.
+
+    # Install mixin
+    MyClass.__bases__ = (MixIn,) + MyClass.__bases__
+    
+    # Add properties
+    # Somehow it doesn't work automatically
+    for name in dir(MixIn):
+        prop = getattr(MixIn,name,None)
+        if isinstance(prop, property):
+            setattr(MyClass, name, prop)
+    
+    # Update name
+    MyClass.__name__ = MyClass.__name__.replace(
+        'Box',
+        MixIn.__name__.replace('MixIn','')+'Box',
+        1)
+
+class Factory(AttributesMap, Taggable):
+    _box_class_cache = {}
+
+    def __init__(self, factory_id,
+            create_function, 
+            start_function, 
+            stop_function, 
+            status_function, 
+            configure_function, 
+            preconfigure_function,
+            prestart_function,
+            help = None,
+            category = None):
+
+        super(Factory, self).__init__()
+
+        self._factory_id = factory_id
+        self._create_function = create_function
+        self._start_function = start_function
+        self._stop_function = stop_function
+        self._status_function = status_function
+        self._configure_function = configure_function
+        self._preconfigure_function = preconfigure_function
+        self._prestart_function = prestart_function
+        self._help = help
+        self._category = category
+        self._connector_types = dict()
+        self._traces = dict()
+        self._box_attributes = AttributesMap()
+        self._factory = None
+
+    @property
+    def factory(self):
+        if self._factory:
+            return self._factory
+
+        from nepi.core.design import Box
+
+        if not self.has_addresses and not self.has_routes:
+            self._factory = Box
+        else:
+            addresses = 'w' if self.allow_addresses else ('r' if self.has_addresses else '-')
+            routes    = 'w' if self.allow_routes else ('r' if self.has_routes else '-')
+            key = addresses+routes
+            
+            if key in self._box_class_cache:
+                self._factory = self._box_class_cache[key]
+            else:
+                # Create base class
+                class _factory(Box):
+                    def __init__(self, guid, factory, testbed_guid, container = None):
+                        super(_factory, self).__init__(guid, factory, testbed_guid, container)
+                
+                # Add mixins, one by one
+                if self.allow_addresses:
+                    MixIn(_factory, UserAddressableMixin)
+                elif self.has_addresses:
+                    MixIn(_factory, AddressableMixin)
+                    
+                if self.allow_routes:
+                    MixIn(_factory, UserRoutableMixin)
+                elif self.has_routes:
+                    MixIn(_factory, RoutableMixin)
+                
+                # Put into cache
+                self._box_class_cache[key] = self._factory = _factory
+        return self._factory
+
+    @property
+    def factory_id(self):
+        return self._factory_id
+
+    @property
+    def allow_addresses(self):
+        return self.has_tag(tags.ALLOW_ADDRESSES)
+
+    @property
+    def allow_routes(self):
+        return self.has_tag(tags.ALLOW_ROUTES)
+
+    @property
+    def has_addresses(self):
+        return self.has_tag(tags.HAS_ADDRESSES) or \
+                self.allow_addresses
+
+    @property
+    def has_routes(self):
+        return self.has_tag(tags.HAS_ROUTES) or \
+                self.allow_routes
+
+    @property
+    def help(self):
+        return self._help
+
+    @property
+    def category(self):
+        return self._category
+
+    @property
+    def connector_types(self):
+        return self._connector_types.values()
+
+    @property
+    def traces(self):
+        return self._traces.values()
+
+    @property
+    def traces_list(self):
+        return self._traces.keys()
+
+    @property
+    def box_attributes(self):
+        return self._box_attributes
+
+    @property
+    def create_function(self):
+        return self._create_function
+
+    @property
+    def prestart_function(self):
+        return self._prestart_function
+
+    @property
+    def start_function(self):
+        return self._start_function
+
+    @property
+    def stop_function(self):
+        return self._stop_function
+
+    @property
+    def status_function(self):
+        return self._status_function
+
+    @property
+    def configure_function(self):
+        return self._configure_function
+
+    @property
+    def preconfigure_function(self):
+        return self._preconfigure_function
+
+    def connector_type(self, name):
+        return self._connector_types[name]
+
+    def add_connector_type(self, connector_type):
+        self._connector_types[connector_type.name] = connector_type
+
+    def add_trace(self, name, help, enabled = False):
+        self._traces[name] = (name, help, enabled)
+
+    def add_box_attribute(self, name, help, type, value = None, range = None,
+        allowed = None, flags = Attribute.NoFlags, validation_function = None,
+        category = None):
+        self._box_attributes.add_attribute(name, help, type, value, range,
+                allowed, flags, validation_function, category)
+
+    def create(self, guid, testbed_description):
+        return self.factory(guid, self, testbed_description.guid)
+
+    def destroy(self):
+        super(Factory, self).destroy()
+        self._connector_types = None
+
index ca1be8d..cd7ea9a 100644 (file)
@@ -2,9 +2,11 @@
 # -*- coding: utf-8 -*-
 
 from nepi.core.attributes import Attribute, AttributesMap
+from nepi.core.connector import ConnectorType
+from nepi.core.factory import Factory
 import sys
 import getpass
-from nepi.util import validation
+from nepi.util import tags, validation
 from nepi.util.constants import ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, \
         DeploymentConfiguration as DC, \
         AttributeCategories as AC
@@ -109,10 +111,6 @@ class VersionedMetadataInfo(object):
     def factories_info(self):
         """ dictionary of dictionaries of factory specific information
             factory_id: dict({
-                "allow_addresses": whether the box allows adding IP addresses,
-                "allow_routes": wether the box allows adding routes,
-                "has_addresses": whether the box allows obtaining IP addresses,
-                "has_routes": wether the box allows obtaining routes,
                 "help": help text,
                 "category": category the element belongs to,
                 "create_function": function for element instantiation,
@@ -158,179 +156,210 @@ class VersionedMetadataInfo(object):
         raise NotImplementedError
 
 class Metadata(object):
-    STANDARD_BOX_ATTRIBUTES = (
-        ("label", dict(
-            name = "label",
-            validation_function = validation.is_string,
-            type = Attribute.STRING,
-            flags = Attribute.DesignOnly,
-            help = "A unique identifier for referring to this box",
-        )),
-    )
-    
-    STANDARD_TESTBED_ATTRIBUTES = (
-        ("home_directory", dict(
-            name = "homeDirectory",
-            validation_function = validation.is_string,
-            help = "Path to the directory where traces and other files will be stored",
-            type = Attribute.STRING,
-            value = "",
-            flags = Attribute.DesignOnly
-        )),
-        ("label", dict(
-            name = "label",
-            validation_function = validation.is_string,
-            type = Attribute.STRING,
-            flags = Attribute.DesignOnly,
-            help = "A unique identifier for referring to this testbed",
-        )),
-    )
+    # These attributes should be added to all boxes
+    STANDARD_BOX_ATTRIBUTES = dict({
+        "label" : dict({
+            "name" : "label",
+            "validation_function" : validation.is_string,
+            "type" : Attribute.STRING,
+            "flags" : Attribute.DesignOnly,
+            "help" : "A unique identifier for referring to this box",
+        }),
+     })
+
+    # These are the attribute definitions for tagged attributes
+    STANDARD_TAGGED_ATTRIBUTES_DEFINITIONS = dict({
+        "maxAddresses" : dict({
+            "name" : "maxAddresses",
+            "validation_function" : validation.is_integer,
+            "type" : Attribute.INTEGER,
+            "value" : 1,
+            "flags" : Attribute.Invisible,
+            "help" : "The maximum allowed number of addresses",
+            }),
+        })
+
+    # Attributes to be added to all boxes with specific tags
+    STANDARD_TAGGED_BOX_ATTRIBUTES = dict({
+        tags.ALLOW_ADDRESSES : ["maxAddresses"],
+        tags.HAS_ADDRESSES : ["maxAddresses"],
+    })
+
+    # These attributes should be added to all testbeds
+    STANDARD_TESTBED_ATTRIBUTES = dict({
+        "home_directory" : dict({
+            "name" : "homeDirectory",
+            "validation_function" : validation.is_string,
+            "help" : "Path to the directory where traces and other files will be stored",
+            "type" : Attribute.STRING,
+            "value" : "",
+            "flags" : Attribute.DesignOnly
+            }),
+        "label" : dict({
+            "name" : "label",
+            "validation_function" : validation.is_string,
+            "type" : Attribute.STRING,
+            "flags" : Attribute.DesignOnly,
+            "help" : "A unique identifier for referring to this testbed",
+            }),
+        })
     
-    DEPLOYMENT_ATTRIBUTES = (
+    # These attributes should be added to all testbeds
+    DEPLOYMENT_ATTRIBUTES = dict({
         # TESTBED DEPLOYMENT ATTRIBUTES
-        (DC.DEPLOYMENT_ENVIRONMENT_SETUP, dict(
-                name = DC.DEPLOYMENT_ENVIRONMENT_SETUP,
-                validation_function = validation.is_string,
-                help = "Shell commands to run before spawning TestbedController processes",
-                type = Attribute.STRING,
-                flags = Attribute.DesignOnly,
-                category = AC.CATEGORY_DEPLOYMENT,
-        )),
-        (DC.DEPLOYMENT_MODE, dict(name = DC.DEPLOYMENT_MODE,
-                help = "Instance execution mode",
-                type = Attribute.ENUM,
-                value = DC.MODE_SINGLE_PROCESS,
-                allowed = [
+        DC.DEPLOYMENT_ENVIRONMENT_SETUP : dict({
+            "name" : DC.DEPLOYMENT_ENVIRONMENT_SETUP,
+            "validation_function" : validation.is_string,
+            "help" : "Shell commands to run before spawning TestbedController processes",
+            "type" : Attribute.STRING,
+            "flags" : Attribute.DesignOnly,
+            "category" : AC.CATEGORY_DEPLOYMENT,
+        }),
+        DC.DEPLOYMENT_MODE: dict({
+            "name" : DC.DEPLOYMENT_MODE,
+            "help" : "Instance execution mode",
+            "type" : Attribute.ENUM,
+            "value" : DC.MODE_SINGLE_PROCESS,
+            "allowed" : [
                     DC.MODE_DAEMON,
                     DC.MODE_SINGLE_PROCESS
                 ],
-                flags = Attribute.DesignOnly,
-                validation_function = validation.is_enum,
-                category = AC.CATEGORY_DEPLOYMENT,
-        )),
-        (DC.DEPLOYMENT_COMMUNICATION, dict(name = DC.DEPLOYMENT_COMMUNICATION,
-                help = "Instance communication mode",
-                type = Attribute.ENUM,
-                value = DC.ACCESS_LOCAL,
-                allowed = [
+            "flags" : Attribute.DesignOnly,
+            "validation_function" : validation.is_enum,
+            "category" : AC.CATEGORY_DEPLOYMENT,
+            }),
+        DC.DEPLOYMENT_COMMUNICATION : dict({
+            "name" : DC.DEPLOYMENT_COMMUNICATION,
+            "help" : "Instance communication mode",
+            "type" : Attribute.ENUM,
+            "value" : DC.ACCESS_LOCAL,
+            "allowed" : [
                     DC.ACCESS_LOCAL,
                     DC.ACCESS_SSH
                 ],
-                flags = Attribute.DesignOnly,
-                validation_function = validation.is_enum,
-                category = AC.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,
-                category = AC.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,
-                category = AC.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,
-                category = AC.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,
-                category = AC.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
-                category = AC.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,
-                category = AC.CATEGORY_DEPLOYMENT,
-        )),
-        (DC.LOG_LEVEL, dict(name = DC.LOG_LEVEL,
-                help = "Log level for instance",
-                type = Attribute.ENUM,
-                value = DC.ERROR_LEVEL,
-                allowed = [
+            "flags" : Attribute.DesignOnly,
+            "validation_function" : validation.is_enum,
+            "category" : AC.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,
+            "category" : AC.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,
+            "category" : AC.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,
+            "category" : AC.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,
+            "category" : AC.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
+            "category" : AC.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,
+            "category" : AC.CATEGORY_DEPLOYMENT,
+            }),
+        DC.LOG_LEVEL : dict({
+            "name" : DC.LOG_LEVEL,
+            "help" : "Log level for instance",
+            "type" : Attribute.ENUM,
+            "value" : DC.ERROR_LEVEL,
+            "allowed" : [
                     DC.ERROR_LEVEL,
                     DC.DEBUG_LEVEL
                 ],
-                flags = Attribute.DesignOnly,
-                validation_function = validation.is_enum,
-                category = AC.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,
-                category = AC.CATEGORY_DEPLOYMENT,
-        )),
-    )
-    
-    STANDARD_TESTBED_ATTRIBUTES += DEPLOYMENT_ATTRIBUTES
-    
-    STANDARD_ATTRIBUTE_BUNDLES = {
-            "tun_proto" : dict({
-                "name": "tun_proto", 
-                "help": "TUNneling protocol used",
-                "type": Attribute.STRING,
-                "flags": Attribute.Invisible,
-                "validation_function": validation.is_string,
+            "flags" : Attribute.DesignOnly,
+            "validation_function" : validation.is_enum,
+            "category" : AC.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,
+            "category" : AC.CATEGORY_DEPLOYMENT,
+            }),
+        })
+  
+    # These attributes could appear in the boxes attribute list
+    STANDARD_BOX_ATTRIBUTE_DEFINITIONS = dict({
+        "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_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_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,
+        "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
+        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
             }),
-    }
+        })
     
+    STANDARD_TESTBED_ATTRIBUTES.update(DEPLOYMENT_ATTRIBUTES.copy())
 
     def __init__(self, testbed_id, version):
         self._version = version
@@ -360,59 +389,11 @@ class Metadata(object):
 
     def testbed_attributes(self):
         attributes = AttributesMap()
-
-        # standard attributes
-        self._add_standard_attributes(attributes, None, True, False,
-            self.STANDARD_TESTBED_ATTRIBUTES)
-        
-        # custom attributes - they override standard ones
-        for attr_info in self._metadata.testbed_attributes.values():
-            name = attr_info["name"]
-            help = attr_info["help"]
-            type = attr_info["type"] 
-            value = attr_info["value"] if "value" in attr_info else None
-            range = attr_info["range"] if "range" in attr_info else None
-            allowed = attr_info["allowed"] if "allowed" in attr_info else None
-            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, category)
-        
+        testbed_attributes = self._testbed_attributes()
+        self._add_attributes(attributes.add_attribute, testbed_attributes)
         return attributes
 
-    def build_design_factories(self):
-        from nepi.core.design import Factory
-        factories = list()
-        for factory_id, info in self._metadata.factories_info.iteritems():
-            help = info["help"]
-            category = info["category"]
-            allow_addresses = info.get("allow_addresses", False)
-            allow_routes = info.get("allow_routes", False)
-            has_addresses = info.get("has_addresses", False)
-            has_routes = info.get("has_routes", False)
-            factory = Factory(factory_id, 
-                    allow_addresses, has_addresses,
-                    allow_routes, has_routes,
-                    help, category)
-            
-            # standard attributes
-            self._add_standard_attributes(factory, info, True, True,
-                self.STANDARD_BOX_ATTRIBUTES)
-            
-            # custom attributes - they override standard ones
-            self._add_attributes(factory, info, "factory_attributes")
-            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
-
-    def build_execute_factories(self):
-        from nepi.core.execute import Factory
+    def build_factories(self):
         factories = list()
         for factory_id, info in self._metadata.factories_info.iteritems():
             create_function = info.get("create_function")
@@ -422,28 +403,27 @@ class Metadata(object):
             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)
-            has_routes = info.get("has_routes", False)
-            factory = Factory(factory_id, create_function, start_function,
-                    stop_function, status_function, 
-                    configure_function, preconfigure_function,
+            help = info["help"]
+            category = info["category"]
+            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)
+                    help,
+                    category)
                     
-            # standard attributes
-            self._add_standard_attributes(factory, info, False, True,
-                self.STANDARD_BOX_ATTRIBUTES)
-            
-            # custom attributes - they override standard ones
-            self._add_attributes(factory, info, "factory_attributes")
-            self._add_attributes(factory, info, "box_attributes", True)
+            factory_attributes = self._factory_attributes(info)
+            self._add_attributes(factory.add_attribute, factory_attributes)
+            box_attributes = self._box_attributes(info)
+            self._add_attributes(factory.add_box_attribute, box_attributes)
             
-            self._add_execute_traces(factory, info)
+            self._add_traces(factory, info)
             self._add_tags(factory, info)
-            self._add_execute_connector_types(factory, info)
+            self._add_connector_types(factory, info)
             factories.append(factory)
         return factories
 
@@ -454,97 +434,83 @@ class Metadata(object):
             __import__(mod_name)
         return sys.modules[mod_name]
 
-    def _add_standard_attributes(self, factory, info, design, box, STANDARD_ATTRIBUTES):
-        if design:
-            attr_bundle = STANDARD_ATTRIBUTES
+    def _testbed_attributes(self):
+        # standar attributes
+        attributes = self.STANDARD_TESTBED_ATTRIBUTES.copy()
+        # custom attributes
+        attributes.update(self._metadata.testbed_attributes.copy())
+        return attributes
+        
+    def _factory_attributes(self, info):
+        tagged_attributes = self._tagged_attributes(info)
+        if "factory_attributes" in info:
+            definitions = self._metadata.attributes.copy()
+            # filter attributes corresponding to the factory_id
+            factory_attributes = self._filter_attributes(info["factory_attributes"], 
+                definitions)
         else:
-            # Only add non-DesignOnly attributes
-            def nonDesign(attr_info):
-                return not (attr_info[1].get('flags',Attribute.NoFlags) & Attribute.DesignOnly)
-            attr_bundle = filter(nonDesign, STANDARD_ATTRIBUTES)
-        self._add_attributes(factory, info, None, box, 
-            attr_bundle = STANDARD_ATTRIBUTES)
-
-    def _add_attributes(self, factory, info, attr_key, box_attributes = False, attr_bundle = ()):
-        if not attr_bundle and info and attr_key in info:
-            definitions = self.STANDARD_ATTRIBUTE_BUNDLES.copy()
+            factory_attributes = dict()
+        attributes = dict(tagged_attributes.items() + \
+                factory_attributes.items())
+        return attributes
+
+    def _box_attributes(self, info):
+        tagged_attributes = self._tagged_attributes(info)
+        if "box_attributes" in info:
+            definitions = self.STANDARD_BOX_ATTRIBUTE_DEFINITIONS.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:
+            box_attributes = self._filter_attributes(info["box_attributes"], 
+                definitions)
+        else:
+            box_attributes = dict()
+        attributes = dict(tagged_attributes.items() + \
+                box_attributes.items())
+        attributes.update(self.STANDARD_BOX_ATTRIBUTES.copy())
+        return attributes
+
+    def _tagged_attributes(self, info):
+        tagged_attributes = dict()
+        for tag_id in info.get("tags", []):
+            if tag_id in self.STANDARD_TAGGED_BOX_ATTRIBUTES:
+                attr_list = self.STANDARD_TAGGED_BOX_ATTRIBUTES[tag_id]
+                attributes = self._filter_attributes(attr_list,
+                    self.STANDARD_TAGGED_ATTRIBUTES_DEFINITIONS)
+                tagged_attributes.update(attributes)
+        return tagged_attributes
+
+    def _filter_attributes(self, attr_list, definitions):
+        # filter attributes not corresponding to the factory
+        attributes = dict((attr_id, definitions[attr_id]) \
+           for attr_id in attr_list)
+        return attributes
+
+    def _add_attributes(self, add_attr_func, attributes):
+        for attr_id, attr_info in attributes.iteritems():
             name = attr_info["name"]
             help = attr_info["help"]
             type = attr_info["type"] 
-            value = attr_info["value"] if "value" in attr_info else None
-            range = attr_info["range"] if "range" in attr_info else None
-            allowed = attr_info["allowed"] if "allowed" in attr_info \
-                    else None
-            flags = attr_info["flags"] if "flags" in attr_info \
-                    and attr_info["flags"] != None \
-                    else Attribute.NoFlags
+            value = attr_info.get("value")
+            range = attr_info.get("range")
+            allowed = attr_info.get("allowed")
+            flags = attr_info.get("flags")
+            flags = Attribute.NoFlags if flags == None else flags
             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, category)
-            else:
-                factory.add_attribute(name, help, type, value, range, 
-                        allowed, flags, validation_function, category)
-
-    def _add_design_traces(self, factory, info):
-        if "traces" in info:
-            for trace in info["traces"]:
-                trace_info = self._metadata.traces[trace]
-                trace_id = trace_info["name"]
-                help = trace_info["help"]
-                factory.add_trace(trace_id, help)
-
-    def _add_execute_traces(self, factory, info):
-        if "traces" in info:
-            for trace in info["traces"]:
-                trace_info = self._metadata.traces[trace]
-                trace_id = trace_info["name"]
-                factory.add_trace(trace_id)
+            category = attr_info.get("category")
+            add_attr_func(name, help, type, value, range, allowed, flags, 
+                    validation_function, category)
 
-    def _add_tags(self, factory, info):
-        if "tags" in info:
-            for tag_id in info["tags"]:
-                factory.add_tag(tag_id)
+    def _add_traces(self, factory, info):
+        for trace_id in info.get("traces", []):
+            trace_info = self._metadata.traces[trace_id]
+            name = trace_info["name"]
+            help = trace_info["help"]
+            factory.add_trace(name, help)
 
-    def _add_design_connector_types(self, factory, info):
-        from nepi.core.design import ConnectorType
-        if "connector_types" in info:
-            connections = dict()
-            for connection in self._metadata.connections:
-                from_ = connection["from"]
-                to = connection["to"]
-                can_cross = connection["can_cross"]
-                if from_ not in connections:
-                    connections[from_] = list()
-                if to not in connections:
-                    connections[to] = list()
-                connections[from_].append((to, can_cross))
-                connections[to].append((from_, can_cross))
-            for connector_id in info["connector_types"]:
-                connector_type_info = self._metadata.connector_types[
-                        connector_id]
-                name = connector_type_info["name"]
-                help = connector_type_info["help"]
-                max = connector_type_info["max"]
-                min = connector_type_info["min"]
-                testbed_id = self._testbed_id
-                factory_id = factory.factory_id
-                connector_type = ConnectorType(testbed_id, factory_id, name, 
-                        help, max, min)
-                for (to, can_cross) in connections[(testbed_id, factory_id, 
-                        name)]:
-                    (testbed_id_to, factory_id_to, name_to) = to
-                    connector_type.add_allowed_connection(testbed_id_to, 
-                            factory_id_to, name_to, can_cross)
-                factory.add_connector_type(connector_type)
+    def _add_tags(self, factory, info):
+        for tag_id in info.get("tags", []):
+            factory.add_tag(tag_id)
 
-    def _add_execute_connector_types(self, factory, info):
-        from nepi.core.execute import ConnectorType
+    def _add_connector_types(self, factory, info):
         if "connector_types" in info:
             from_connections = dict()
             to_connections = dict()
@@ -552,10 +518,8 @@ class Metadata(object):
                 from_ = connection["from"]
                 to = connection["to"]
                 can_cross = connection["can_cross"]
-                init_code = connection["init_code"] \
-                        if "init_code" in connection else None
-                compl_code = connection["compl_code"] \
-                        if "compl_code" in connection else None
+                init_code = connection.get("init_code")
+                compl_code = connection.get("compl_code")
                 if from_ not in from_connections:
                     from_connections[from_] = list()
                 if to not in to_connections:
@@ -568,12 +532,13 @@ class Metadata(object):
                 connector_type_info = self._metadata.connector_types[
                         connector_id]
                 name = connector_type_info["name"]
+                help = connector_type_info["help"]
                 max = connector_type_info["max"]
                 min = connector_type_info["min"]
                 testbed_id = self._testbed_id
                 factory_id = factory.factory_id
                 connector_type = ConnectorType(testbed_id, factory_id, name, 
-                        max, min)
+                        help, max, min)
                 connector_key = (testbed_id, factory_id, name)
                 if connector_key in to_connections:
                     for (from_, can_cross, init_code, compl_code) in \
index 291a24b..0c19d62 100644 (file)
@@ -41,7 +41,7 @@ class TestbedController(execute.TestbedController):
         self._elements = dict()
 
         self._metadata = Metadata(self._testbed_id, self._testbed_version)
-        for factory in self._metadata.build_execute_factories():
+        for factory in self._metadata.build_factories():
             self._factories[factory.factory_id] = factory
         self._attributes = self._metadata.testbed_attributes()
         self._root_directory = None
@@ -110,10 +110,17 @@ class TestbedController(execute.TestbedController):
             connector_type_name2):
         factory1 = self._get_factory(guid1)
         factory_id2 = self._create[guid2]
-        count = self._get_connection_count(guid1, connector_type_name1)
+        # TODO VALIDATE!!!
+        #if self.box.guid == connector.box.guid:
+        #    return False
+        #if self.is_full() or connector.is_full():
+        #    return False
+        #if self.is_connected(connector):
+        #    return False
+        #count = self._get_connection_count(guid1, connector_type_name1)
         connector_type = factory1.connector_type(connector_type_name1)
         connector_type.can_connect(self._testbed_id, factory_id2, 
-                connector_type_name2, count, False)
+                connector_type_name2, False)
         if not guid1 in self._connect:
             self._connect[guid1] = dict()
         if not connector_type_name1 in self._connect[guid1]:
@@ -131,10 +138,17 @@ class TestbedController(execute.TestbedController):
             cross_testbed_guid, cross_testbed_id, cross_factory_id, 
             cross_connector_type_name):
         factory = self._get_factory(guid)
-        count = self._get_connection_count(guid, connector_type_name)
+        # TODO VALIDATE!!!
+        #if self.box.guid == connector.box.guid:
+        #    return False
+        #if self.is_full() or connector.is_full():
+        #    return False
+        #if self.is_connected(connector):
+        #    return False
+        #count = self._get_connection_count(guid, connector_type_name)
         connector_type = factory.connector_type(connector_type_name)
         connector_type.can_connect(cross_testbed_id, cross_factory_id, 
-                cross_connector_type_name, count, True)
+                cross_connector_type_name, True)
         if not guid in self._cross_connect:
             self._cross_connect[guid] = dict()
         if not connector_type_name in self._cross_connect[guid]:
@@ -143,16 +157,16 @@ class TestbedController(execute.TestbedController):
                 (cross_guid, cross_testbed_guid, cross_testbed_id, 
                 cross_factory_id, cross_connector_type_name)
 
-    def defer_add_trace(self, guid, trace_id):
+    def defer_add_trace(self, guid, trace_name):
         if not guid in self._create:
             raise RuntimeError("Element guid %d doesn't exist" % guid)
         factory = self._get_factory(guid)
-        if not trace_id in factory.traces:
+        if not trace_name in factory.traces_list:
             raise RuntimeError("Element type '%s' has no trace '%s'" %
-                    (factory.factory_id, trace_id))
+                    (factory.factory_id, trace_name))
         if not guid in self._add_trace:
             self._add_trace[guid] = list()
-        self._add_trace[guid].append(trace_id)
+        self._add_trace[guid].append(trace_name)
 
     def defer_add_address(self, guid, address, netprefix, broadcast):
         if not guid in self._create:
@@ -416,10 +430,10 @@ class TestbedController(execute.TestbedController):
         
         return addresses[index][attribute_index]
 
-    def get_attribute_list(self, guid):
+    def get_attribute_list(self, guid, filter_flags = None):
         factory = self._get_factory(guid)
         attribute_list = list()
-        return factory.box_attributes.attributes_list
+        return factory.box_attributes.get_attribute_list(filter_flags)
 
     def get_factory_id(self, guid):
         factory = self._get_factory(guid)
index 350c8f3..4bfff53 100644 (file)
@@ -86,7 +86,8 @@ class TestbedController(testbed_impl.TestbedController):
         # TODO: take on account schedule time for the task 
         factory_id = self._create[guid]
         factory = self._factories[factory_id]
-        if factory.box_attributes.is_attribute_design_only(name):
+        if factory.box_attributes.is_attribute_design_only(name) or \
+                factory.box_attributes.is_attribute_invisible(name):
             return
         element = self._elements.get(guid)
         if element:
@@ -97,7 +98,8 @@ class TestbedController(testbed_impl.TestbedController):
         # TODO: take on account schedule time for the task
         factory_id = self._create[guid]
         factory = self._factories[factory_id]
-        if factory.box_attributes.is_attribute_design_only(name):
+        if factory.box_attributes.is_attribute_design_only(name) or \
+                factory.box_attributes.is_attribute_invisible(name):
             return value
         element = self._elements.get(guid)
         try:
index 801f91b..539175f 100644 (file)
@@ -4,7 +4,7 @@
 from constants import TESTBED_ID
 from nepi.core import metadata
 from nepi.core.attributes import Attribute
-from nepi.util import validation
+from nepi.util import tags, validation
 from nepi.util.constants import ApplicationStatus as AS, \
         FactoryCategories as FC
 
@@ -460,44 +460,44 @@ configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE,
 
 factories_info = dict({
     NODE: dict({
-            "allow_routes": True,
             "help": "Emulated Node with virtualized network stack",
             "category": FC.CATEGORY_NODES,
             "create_function": create_node,
             "configure_function": configure_node,
             "box_attributes": ["forward_X11"],
             "connector_types": ["devs", "apps"],
-            "traces": ["node_pcap"]
+            "traces": ["node_pcap"],
+            "tags": [tags.NODE, tags.ALLOW_ROUTES],
        }),
     P2PIFACE: dict({
-            "allow_addresses": True,
             "help": "Point to point network interface",
             "category": FC.CATEGORY_DEVICES,
             "create_function": create_p2piface,
             "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": FC.CATEGORY_DEVICES,
             "create_function": create_tapiface,
             "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": FC.CATEGORY_DEVICES,
             "create_function": create_nodeiface,
             "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",
@@ -510,7 +510,8 @@ factories_info = dict({
              #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",
@@ -521,19 +522,21 @@ factories_info = dict({
             "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 "
+            "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"]
+            "connector_types": ["->fd", "udp", "tcp"],
+            "allow_addresses": False,
+            "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key"],
+            "tags": [tags.TUNNEL],
     }),
 })
 
index 6333437..2ba1336 100644 (file)
@@ -686,11 +686,11 @@ factories_info = dict({
         "category": FC.CATEGORY_APPLICATIONS,
         "create_function": create_element,
         "configure_function": configure_element,
-        "help": "",
-        "connector_types": [],
         "stop_function": stop_application,
         "start_function": start_application,
         "status_function": status_application,
+        "help": "",
+        "connector_types": [],
         "box_attributes": ["MaxPackets",
             "Interval",
             "RemoteIpv6",
@@ -698,6 +698,7 @@ factories_info = dict({
             "PacketSize",
             "StartTime",
             "StopTime"],
+        "tags": [tags.APPLICATION],
     }),
      "ns3::UdpL4Protocol": dict({
         "category": FC.CATEGORY_PROTOCOLS,
@@ -706,6 +707,7 @@ factories_info = dict({
         "help": "",
         "connector_types": ["node"],
         "box_attributes": ["ProtocolNumber"],
+        "tags": [tags.PROTOCOL],
     }),
      "ns3::RandomDiscPositionAllocator": dict({
         "category": FC.CATEGORY_MOBILITY_MODELS,
@@ -725,8 +727,7 @@ factories_info = dict({
         "configure_function": configure_node,
         "help": "",
         "connector_types": ["devs", "apps", "protos", "mobility"],
-        "allow_routes": True,
-        "box_attributes": [],
+        "tags": [tags.NODE, tags.ALLOW_ROUTES],
     }),
      "ns3::GridPositionAllocator": dict({
         "category": FC.CATEGORY_MOBILITY_MODELS,
@@ -740,7 +741,6 @@ factories_info = dict({
             "DeltaX",
             "DeltaY",
             "LayoutType"],
-        "tags": [tags.MOBILE],
     }),
      "ns3::TapBridge": dict({
         "category": FC.CATEGORY_DEVICES,
@@ -748,7 +748,6 @@ factories_info = dict({
         "configure_function": configure_element,
         "help": "",
         "connector_types": [],
-        "allow_addresses": True,
         "box_attributes": ["Mtu",
             "DeviceName",
             "Gateway",
@@ -757,9 +756,10 @@ factories_info = dict({
             "Netmask",
             "Start",
             "Stop"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::FlowMonitor": dict({
-        "category": "",
+        "category": FC.CATEGORY_SERVICE_FLOWS,
         "create_function": create_element,
         "configure_function": configure_element,
         "help": "",
@@ -797,6 +797,7 @@ factories_info = dict({
             "StartTime",
             "StopTime"],
         "traces": ["rtt"],
+        "tags": [tags.APPLICATION],
     }),
      "ns3::dot11s::PeerLink": dict({
         "category": "",
@@ -817,12 +818,12 @@ factories_info = dict({
         "configure_function": configure_device,
         "help": "",
         "connector_types": ["node", "err", "queue", "chan"],
-        "allow_addresses": True,
         "box_attributes": ["Mtu",
             "Address",
             "DataRate",
             "InterframeGap"],
-        "traces": ["p2ppcap", "p2pascii"]
+        "traces": ["p2ppcap", "p2pascii"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::NakagamiPropagationLossModel": dict({
         "category": FC.CATEGORY_LOSS_MODELS,
@@ -891,6 +892,7 @@ factories_info = dict({
             "Protocol",
             "StartTime",
             "StopTime"],
+        "tags": [tags.APPLICATION],
     }),
      "ns3::AdhocWifiMac": dict({
         "category": FC.CATEGORY_MAC_MODELS,
@@ -1037,11 +1039,11 @@ factories_info = dict({
         "configure_function": configure_device,
         "help": "Network interface associated to a file descriptor",
         "connector_types": ["node", "->fd"],
-        "allow_addresses": True,
         "box_attributes": ["Address", 
             "LinuxSocketAddress",
             "tun_proto", "tun_addr", "tun_port", "tun_key"],
-        "traces": ["fdpcap"]
+        "traces": ["fdpcap"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::Nepi::TunChannel": dict({
         "category": FC.CATEGORY_TUNNELS,
@@ -1053,7 +1055,9 @@ factories_info = dict({
                 "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"]
+        "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key"],
+        "tags": [tags.TUNNEL],
     }),
      "ns3::CsmaNetDevice": dict({
         "category": FC.CATEGORY_DEVICES,
@@ -1061,12 +1065,12 @@ factories_info = dict({
         "configure_function": configure_device,
         "help": "CSMA (carrier sense, multiple access) interface",
         "connector_types": ["node", "chan", "err", "queue"],
-        "allow_addresses": True,
         "box_attributes": ["Address",
             "Mtu",
             "SendEnable",
             "ReceiveEnable"],
-        "traces": ["csmapcap", "csmapcap_promisc"]
+        "traces": ["csmapcap", "csmapcap_promisc"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::UanPropModelThorp": dict({
         "category": "",
@@ -1111,8 +1115,8 @@ factories_info = dict({
         "configure_function": configure_element,
         "help": "",
         "connector_types": ["node", "chan"],
-        "allow_addresses": True,
         "box_attributes": [],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::FriisPropagationLossModel": dict({
         "category": FC.CATEGORY_LOSS_MODELS,
@@ -1169,6 +1173,7 @@ factories_info = dict({
         "help": "",
         "connector_types": [],
         "box_attributes": [],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::ConstantSpeedPropagationDelayModel": dict({
         "category": FC.CATEGORY_DELAY_MODELS,
@@ -1444,7 +1449,6 @@ factories_info = dict({
         "configure_function": configure_station,
         "help": "Base station for wireless mobile network",
         "connector_types": ["node", "chan", "phy", "uplnk", "dwnlnk"],
-        "allow_addresses": True,
         "box_attributes": ["InitialRangInterval",
             "DcdInterval",
             "UcdInterval",
@@ -1456,6 +1460,7 @@ factories_info = dict({
             "RTG",
             "TTG"],
         "traces": ["wimaxpcap", "wimaxascii"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::UdpServer": dict({
         "category": FC.CATEGORY_APPLICATIONS,
@@ -1523,6 +1528,7 @@ factories_info = dict({
             "Start",
             "Stop",
             "RxQueueSize"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::Ipv6ExtensionLooseRouting": dict({
         "category": FC.CATEGORY_ROUTING,
@@ -1560,6 +1566,7 @@ factories_info = dict({
         "connector_types": [],
         "box_attributes": ["Address",
             "Mtu"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::MatrixPropagationLossModel": dict({
         "category": FC.CATEGORY_LOSS_MODELS,
@@ -1575,8 +1582,8 @@ factories_info = dict({
         "configure_function": configure_device,
         "help": "",
         "connector_types": ["node", "mac", "phy", "manager"],
-        "allow_addresses": True,
         "box_attributes": ["Mtu"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::CsmaChannel": dict({
         "category": FC.CATEGORY_CHANNELS,
@@ -1593,10 +1600,10 @@ factories_info = dict({
         "configure_function": configure_element,
         "help": "",
         "connector_types": ["node"],
-        "allow_addresses": True,
         "box_attributes": ["Mtu",
            "EnableLearning",
            "ExpirationTime"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::Ipv6ExtensionRouting": dict({
         "category": FC.CATEGORY_ROUTING,
@@ -1643,6 +1650,7 @@ factories_info = dict({
             "PacketSize",
             "StartTime",
             "StopTime"],
+        "tags": [tags.APPLICATION],
     }),
      "ns3::UdpClient": dict({
         "category": FC.CATEGORY_APPLICATIONS,
@@ -1660,6 +1668,7 @@ factories_info = dict({
             "PacketSize",
             "StartTime",
             "StopTime"],
+        "tags": [tags.APPLICATION],
     }),
      "ns3::PointToPointChannel": dict({
         "category": FC.CATEGORY_CHANNELS,
@@ -1752,8 +1761,8 @@ factories_info = dict({
         "configure_function": configure_element,
         "help": "",
         "connector_types": [],
-        "allow_addresses": True,
         "box_attributes": ["Mtu"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::BasicEnergySource": dict({
         "category": FC.CATEGORY_ENERGY_MODELS,
@@ -1816,8 +1825,8 @@ factories_info = dict({
         "configure_function": configure_element,
         "help": "",
         "connector_types": [],
-        "allow_addresses": True,
         "box_attributes": [],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::RateErrorModel": dict({
         "category": FC.CATEGORY_ERROR_MODELS,
@@ -2114,8 +2123,8 @@ factories_info = dict({
         "configure_function": configure_element,
         "help": "",
         "connector_types": [],
-        "allow_addresses": True,
         "box_attributes": ["Mtu"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
      "ns3::UanPhyGen": dict({
         "category": FC.CATEGORY_PHY_MODELS,
@@ -2211,6 +2220,7 @@ factories_info = dict({
             "Protocol",
             "StartTime",
             "StopTime"],
+        "tags": [tags.APPLICATION],
     }),
      "ns3::RandomDirection2dMobilityModel": dict({
         "category": FC.CATEGORY_MOBILITY_MODELS,
@@ -2364,6 +2374,7 @@ factories_info = dict({
         "box_attributes": ["Port",
            "StartTime",
            "StopTime"],
+        "tags": [tags.APPLICATION],
     }),
      "ns3::AmrrWifiManager": dict({
         "category": FC.CATEGORY_MANAGERS,
@@ -2404,7 +2415,6 @@ factories_info = dict({
         "configure_function": configure_station,
         "help": "Subscriber station for mobile wireless network",
         "connector_types": ["node", "chan", "phy", "sflows"],
-        "allow_addresses": True,
         "box_attributes": ["LostDlMapInterval",
             "LostUlMapInterval",
             "MaxDcdInterval",
@@ -2421,6 +2431,7 @@ factories_info = dict({
             "RTG",
             "TTG"],
         "traces": ["wimaxpcap", "wimaxascii"],
+        "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
     }),
     "ns3::flame::FlameRtable": dict({
         "category": "",
index 6fcc07d..9f2fd3c 100644 (file)
@@ -6,7 +6,7 @@ import time
 from constants import TESTBED_ID
 from nepi.core import metadata
 from nepi.core.attributes import Attribute
-from nepi.util import validation
+from nepi.util import tags, validation
 from nepi.util.constants import ApplicationStatus as AS, \
         FactoryCategories as FC, \
         ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP
@@ -945,7 +945,6 @@ start_order = [ INTERNET, NODEIFACE, TAPIFACE, TUNIFACE, NODE, NETPIPE, NEPIDEPE
 
 factories_info = dict({
     NODE: dict({
-            "allow_routes": True,
             "help": "Virtualized Node (V-Server style)",
             "category": FC.CATEGORY_NODES,
             "create_function": create_node,
@@ -966,19 +965,19 @@ factories_info = dict({
                 # NEPI-in-NEPI attributes
                 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
             ],
-            "connector_types": ["devs", "apps", "pipes", "deps"]
+            "connector_types": ["devs", "apps", "pipes", "deps"],
+            "tags": [tags.NODE, tags.ALLOW_ROUTES],
        }),
     NODEIFACE: dict({
-            "has_addresses": True,
             "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
             "category": FC.CATEGORY_DEVICES,
             "create_function": create_nodeiface,
             "preconfigure_function": configure_nodeiface,
             "box_attributes": [ ],
-            "connector_types": ["node", "inet"]
+            "connector_types": ["node", "inet"],
+            "tags": [tags.INTERFACE, tags.HAS_ADDRESSES],
         }),
     TUNIFACE: dict({
-            "allow_addresses": True,
             "help": "Virtual TUN network interface (layer 3)",
             "category": FC.CATEGORY_DEVICES,
             "create_function": create_tuniface,
@@ -991,10 +990,10 @@ factories_info = dict({
                 "tun_proto", "tun_addr", "tun_port", "tun_key"
             ],
             "traces": ["packets"],
-            "connector_types": ["node","udp","tcp","fd->"]
+            "connector_types": ["node","udp","tcp","fd->"],
+            "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
         }),
     TAPIFACE: dict({
-            "allow_addresses": True,
             "help": "Virtual TAP network interface (layer 2)",
             "category": FC.CATEGORY_DEVICES,
             "create_function": create_tapiface,
@@ -1007,7 +1006,8 @@ factories_info = dict({
                 "tun_proto", "tun_addr", "tun_port", "tun_key"
             ],
             "traces": ["packets"],
-            "connector_types": ["node","udp","tcp","fd->"]
+            "connector_types": ["node","udp","tcp","fd->"],
+            "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
         }),
     APPLICATION: dict({
             "help": "Generic executable command line application",
@@ -1021,7 +1021,8 @@ factories_info = dict({
                                "depends", "build-depends", "build", "install",
                                "sources", "rpm-fusion" ],
             "connector_types": ["node"],
-            "traces": ["stdout", "stderr", "buildlog"]
+            "traces": ["stdout", "stderr", "buildlog"],
+            "tags": [tags.APPLICATION],
         }),
     DEPENDENCY: dict({
             "help": "Requirement for package or application to be installed on some node",
@@ -1031,16 +1032,16 @@ factories_info = dict({
             "box_attributes": ["depends", "build-depends", "build", "install",
                                "sources", "rpm-fusion" ],
             "connector_types": ["node"],
-            "traces": ["buildlog"]
+            "traces": ["buildlog"],
         }),
     NEPIDEPENDENCY: dict({
             "help": "Requirement for NEPI inside NEPI - required to run testbed instances inside a node",
             "category": FC.CATEGORY_APPLICATIONS,
             "create_function": create_nepi_dependency,
             "preconfigure_function": configure_dependency,
-            "box_attributes": [ ],
+            "box_attributes": [],
             "connector_types": ["node"],
-            "traces": ["buildlog"]
+            "traces": ["buildlog"],
         }),
     NS3DEPENDENCY: dict({
             "help": "Requirement for NS3 inside NEPI - required to run NS3 testbed instances inside a node. It also needs NepiDependency.",
@@ -1049,13 +1050,14 @@ factories_info = dict({
             "preconfigure_function": configure_dependency,
             "box_attributes": [ ],
             "connector_types": ["node"],
-            "traces": ["buildlog"]
+            "traces": ["buildlog"],
         }),
     INTERNET: dict({
             "help": "Internet routing",
             "category": FC.CATEGORY_CHANNELS,
             "create_function": create_internet,
             "connector_types": ["devs"],
+            "tags": [tags.INTERNET],
         }),
     NETPIPE: dict({
             "help": "Link emulation",
@@ -1067,7 +1069,7 @@ factories_info = dict({
                                "bw_in","plr_in","delay_in",
                                "bw_out","plr_out","delay_out"],
             "connector_types": ["node"],
-            "traces": ["netpipe_stats"]
+            "traces": ["netpipe_stats"],
         }),
 })
 
index 5bdbcde..5cbd072 100644 (file)
@@ -153,7 +153,7 @@ class ExperimentData(object):
         data = self.data[guid]
         if not "traces" in data:
             return []
-        return [trace_id for trace_id in data["traces"]]
+        return data["traces"]
 
     def get_connection_data(self, guid):
         data = self.data[guid]
@@ -229,7 +229,7 @@ class ExperimentParser(object):
     def traces_to_data(self, data, guid, traces):
         for trace in traces:
             if trace.enabled:
-                data.add_trace_data(guid, trace.trace_id)
+                data.add_trace_data(guid, trace.name)
 
     def connections_to_data(self, data, guid, connectors):
         for connector in connectors:
index 745df56..9cb26f3 100644 (file)
@@ -161,7 +161,7 @@ class AccessConfiguration(AttributesMap):
         
         from nepi.core.metadata import Metadata
         
-        for _,attr_info in Metadata.DEPLOYMENT_ATTRIBUTES:
+        for _,attr_info in Metadata.DEPLOYMENT_ATTRIBUTES.iteritems():
             self.add_attribute(**attr_info)
         
         if params:
@@ -648,10 +648,10 @@ class TestbedControllerServer(BaseServer):
         return self._testbed.status(guid)
 
     @Marshalling.handles(GET_ATTRIBUTE_LIST)
-    @Marshalling.args(int)
+    @Marshalling.args(int, int)
     @Marshalling.retval( Marshalling.pickled_data )
-    def get_attribute_list(self, guid):
-        return self._testbed.get_attribute_list(guid)
+    def get_attribute_list(self, guid, filter_flags = None):
+        return self._testbed.get_attribute_list(guid, filter_flags)
 
     @Marshalling.handles(GET_FACTORY_ID)
     @Marshalling.args(int)
index 85d39fb..e3fbdf2 100644 (file)
@@ -1,5 +1,36 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-MOBILE = 0
+MOBILE = "mobile"
+NODE = "node"
+INTERFACE = "interface"
+WIRELESS = "wireless"
+APPLICATION = "application"
+NAT = "nat"
+ROUTER = "router" 
+SWITCH = "switch"
+PPP = "point-to-point"
+PROTOCOL = "protocol"
+TUNNEL = "tunnel"
+INTERNET = "internet"
+HUB = "hub"
+ALLOW_ADDRESSES = "allow_addresses"
+ALLOW_ROUTES = "allow_routes"
+HAS_ADDRESSES = "has_addresses"
+HAS_ROUTES = "has_routes"
+
+class Taggable(object):
+    def __init__(self):
+        super(Taggable, self).__init__()
+        self._tags = list()
+
+    @property
+    def tags(self):
+        return self._tags
+
+    def add_tag(self, tag_id):
+        self._tags.append(tag_id)
+
+    def has_tag(self, tag_id):
+        return tag_id in self._tags
 
index e9a67c1..8456510 100755 (executable)
@@ -35,7 +35,7 @@ class DesignTestCase(unittest.TestCase):
         app.connector("node").connect(node1.connector("apps"))
         app.enable_trace("fake")
 
-        self.assertEquals(node1.tags, [tags.MOBILE])
+        self.assertEquals(node1.tags, [tags.MOBILE, tags.NODE, tags.ALLOW_ROUTES])
 
         xml = exp_desc.to_xml()
         exp_desc2 = ExperimentDescription()
index f20b9a2..b2ece94 100755 (executable)
@@ -40,7 +40,7 @@ class ExecuteTestCase(unittest.TestCase):
         instance.do_configure()
         instance.start()
         attr_list = instance.get_attribute_list(5)
-        self.assertEquals(attr_list, ["test", "fake", "cross", "label"])
+        self.assertEquals(attr_list, ["test", "fake", "cross", "maxAddresses", "label"])
         while instance.status(7) != AS.STATUS_FINISHED:
             time.sleep(0.5)
         app_result = instance.trace(7, "fake")
index 88d5410..b2f2b67 100644 (file)
@@ -114,8 +114,8 @@ attributes = dict({
                 "value": False,
                 "validation_function": validation.is_bool
         }),
-    "MaxAddresses": dict({
-                "name": "MaxAddresses",
+    "maxAddresses": dict({
+                "name": "maxAddresses",
                 "help": "Attribute that indicates the maximum number of addresses for an interface",
                 "type": Attribute.INTEGER,
                 "value": 3,
@@ -143,7 +143,7 @@ factories_info = dict({
             "status_function": None,
             "box_attributes": ["fake","test"],
             "connector_types": ["devs", "apps"],
-            "tags": [tags.MOBILE]
+            "tags": [tags.MOBILE, tags.NODE, tags.ALLOW_ROUTES],
        }),
     IFACE: dict({
             "help": "Fake iface",
@@ -153,9 +153,10 @@ factories_info = dict({
             "stop_function": None,
             "status_function": None,
             "allow_addresses": True,
-            "factory_attributes": ["fake", "MaxAddresses"],
+            "factory_attributes": ["fake", "maxAddresses"],
             "box_attributes": ["fake", "test", "cross"],
-            "connector_types": ["node", "iface", "cross"]
+            "connector_types": ["node", "iface", "cross"],
+            "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
        }),
     APP: dict({
             "help": "Fake application",
@@ -166,7 +167,8 @@ factories_info = dict({
             "status_function": status_application,
             "box_attributes": ["fake", "test"],
             "connector_types": ["node"],
-            "traces": ["fake"]
+            "traces": ["fake"],
+            "tags": [tags.APPLICATION],
         }),
 })
 
index 44fc8fb..f3d534d 100644 (file)
@@ -4,7 +4,7 @@
 from constants import TESTBED_ID
 from nepi.core import metadata
 from nepi.core.attributes import Attribute
-from nepi.util import validation
+from nepi.util import tags, validation
 from nepi.util.constants import ApplicationStatus as AS
 
 NODE = "Node"
@@ -134,7 +134,8 @@ factories_info = dict({
             "stop_function": None,
             "status_function": None,
             "box_attributes": ["fake","test"],
-            "connector_types": ["devs", "apps"]
+            "connector_types": ["devs", "apps"],
+            "tags": [tags.NODE, tags.ALLOW_ROUTES],
        }),
     IFACE: dict({
             "help": "Fake iface",
@@ -146,7 +147,8 @@ factories_info = dict({
             "allow_addresses": True,
             "factory_attributes": ["fake"],
             "box_attributes": ["fake", "test", "cross"],
-            "connector_types": ["node", "iface", "cross"]
+            "connector_types": ["node", "iface", "cross"],
+            "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
        }),
     APP: dict({
             "help": "Fake application",
@@ -157,7 +159,8 @@ factories_info = dict({
             "status_function": status_application,
             "box_attributes": ["fake", "test"],
             "connector_types": ["node"],
-            "traces": ["fake"]
+            "traces": ["fake"],
+            "tags": [tags.APPLICATION],
         }),
 })