Ticket #28: Refactor Box classes to use mixins, and provide read-only routes/addesses
authorClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Wed, 27 Apr 2011 13:11:12 +0000 (15:11 +0200)
committerClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Wed, 27 Apr 2011 13:11:12 +0000 (15:11 +0200)
src/nepi/core/design.py
src/nepi/core/execute.py
src/nepi/core/metadata.py
src/nepi/testbeds/planetlab/metadata_v01.py

index 3ef4a0d..cd0c384 100644 (file)
@@ -289,9 +289,9 @@ class Box(AttributesMap):
             t.destroy()
         self._connectors = self._traces = self._factory_attributes = None
 
-class AddressableBox(Box):
+class AddressableMixin(object):
     def __init__(self, guid, factory, testbed_guid, container = None):
-        super(AddressableBox, self).__init__(guid, factory, testbed_guid, 
+        super(AddressableMixin, self).__init__(guid, factory, testbed_guid, 
                 container)
         self._max_addresses = 1 # TODO: How to make this configurable!
         self._addresses = list()
@@ -304,6 +304,11 @@ class AddressableBox(Box):
     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.")
@@ -316,20 +321,26 @@ class AddressableBox(Box):
         del address
 
     def destroy(self):
-        super(AddressableBox, self).destroy()
-        for address in self.addresses:
+        super(UserAddressableMixin, self).destroy()
+        for address in list(self.addresses):
             self.delete_address(address)
         self._addresses = None
 
-class RoutingTableBox(Box):
-    def __init__(self, guid, factory, container = None):
-        super(RoutingTableBox, self).__init__(guid, factory, container)
+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)
@@ -340,23 +351,80 @@ class RoutingTableBox(Box):
         del route
 
     def destroy(self):
-        super(RoutingTableBox, self).destroy()
-        for route in self.routes:
+        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):
-    def __init__(self, factory_id, allow_addresses = False, 
-            allow_routes = False, Help = None, category = None):
+    _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 = (allow_addresses == True)
-        self._allow_routes = (allow_routes == True)
+        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._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):
@@ -370,6 +438,14 @@ class Factory(AttributesMap):
     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
@@ -403,12 +479,7 @@ class Factory(AttributesMap):
                 allowed, flags, validation_function)
 
     def create(self, guid, testbed_description):
-        if self._allow_addresses:
-            return AddressableBox(guid, self, testbed_description.guid)
-        elif self._allow_routes:
-            return RoutingTableBox(guid, self, testbed_description.guid)
-        else:
-            return Box(guid, self, testbed_description.guid)
+        return self._factory(guid, self, testbed_description.guid)
 
     def destroy(self):
         super(Factory, self).destroy()
index 8870742..f977802 100644 (file)
@@ -99,11 +99,14 @@ class Factory(AttributesMap):
     def __init__(self, factory_id, create_function, start_function, 
             stop_function, status_function, 
             configure_function, preconfigure_function,
-            allow_addresses = False, allow_routes = False):
+            allow_addresses = False, has_addresses = False,
+            allow_routes = False, has_routes = False):
         super(Factory, self).__init__()
         self._factory_id = factory_id
-        self._allow_addresses = (allow_addresses == True)
-        self._allow_routes = (allow_routes == True)
+        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
@@ -126,6 +129,14 @@ class Factory(AttributesMap):
     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
index 2645b8d..93a31c7 100644 (file)
@@ -86,6 +86,8 @@ class VersionedMetadataInfo(object):
             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,
@@ -190,11 +192,13 @@ class Metadata(object):
         for factory_id, info in self._metadata.factories_info.iteritems():
             help = info["help"]
             category = info["category"]
-            allow_addresses = info["allow_addresses"] \
-                    if "allow_addresses" in info else False
-            allow_routes = info["allow_routes"] \
-                    if "allow_routes" in info else False
-            factory = Factory(factory_id, allow_addresses, allow_routes,
+            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
@@ -222,10 +226,13 @@ class Metadata(object):
             preconfigure_function = info.get("preconfigure_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,
-                    allow_addresses, allow_routes)
+                    allow_addresses, has_addresses,
+                    allow_routes, has_routes)
                     
             # standard attributes
             self._add_standard_attributes(factory, info, False, True,
index b0b0c47..4fead34 100644 (file)
@@ -173,7 +173,7 @@ def configure_nodeiface(testbed_instance, guid):
     
     # Cannot explicitly configure addresses
     if guid in testbed_instance._add_address:
-        del testbed_instance._add_address[guid]
+        raise ValueError, "Cannot explicitly set address of public PlanetLab interface"
     
     # Get siblings
     node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
@@ -196,7 +196,7 @@ def configure_tuniface(testbed_instance, guid):
     addresses = testbed_instance._add_address[guid]
     for address in addresses:
         (address, netprefix, broadcast) = address
-        raise NotImplementedError, "C'mon... TUNs are hard..."
+        element.add_address(address, netprefix, broadcast)
     
     # Do some validations
     element.validate()
@@ -637,7 +637,7 @@ factories_info = dict({
             "connector_types": ["devs", "apps", "pipes"]
        }),
     NODEIFACE: dict({
-            "allow_addresses": True,
+            "has_addresses": True,
             "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
             "category": "devices",
             "create_function": create_nodeiface,