Cross-connection recovery among PlanetLab instances
authorClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Thu, 28 Jul 2011 15:13:17 +0000 (17:13 +0200)
committerClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Thu, 28 Jul 2011 15:13:17 +0000 (17:13 +0200)
Still not tested with other testbeds

src/nepi/testbeds/planetlab/execute.py
src/nepi/testbeds/planetlab/interfaces.py
src/nepi/testbeds/planetlab/metadata.py
src/nepi/testbeds/planetlab/node.py
test/testbeds/planetlab/integration_multi.py

index 2cbdaff..dbe0fb5 100644 (file)
@@ -540,6 +540,14 @@ class TestbedController(testbed_impl.TestbedController):
         self.do_connect_init()
         self.do_connect_compl()
         
+        # Manually recover nodes, to mark dependencies installed
+        # and clean up mutable attributes
+        self._do_in_factory_order(
+            lambda self, guid : self._elements[guid].recover(), 
+            [
+                metadata.NODE,
+            ])
+        
         # Assign nodes - since we're working off exeucte XML, nodes
         # have specific hostnames assigned and we don't need to do
         # real assignment, only find out node ids and check liveliness
@@ -550,13 +558,6 @@ class TestbedController(testbed_impl.TestbedController):
         # Execute configuration steps only for those object
         # kinds that do not have side effects
         
-        # Manually recover nodes, to mark dependencies installed
-        self._do_in_factory_order(
-            lambda self, guid : self._elements[guid].recover(), 
-            [
-                metadata.NODE,
-            ])
-        
         # Do the ones without side effects,
         # including nodes that need to set up home 
         # folders and all that
index 0a05bab..4f9c69b 100644 (file)
@@ -147,6 +147,7 @@ class TunIface(object):
         self.peer_addr = None
         self.peer_port = None
         self.peer_proto_impl = None
+        self._delay_recover = False
 
         # same as peer proto, but for execute-time standard attribute lookups
         self.tun_proto = None 
@@ -232,10 +233,13 @@ class TunIface(object):
         return impl
     
     def recover(self):
-        self.peer_proto_impl = self._impl_instance(
-            self._home_path,
-            False) # no way to know, no need to know
-        self.peer_proto_impl.recover()
+        if self.peer_proto:
+            self.peer_proto_impl = self._impl_instance(
+                self._home_path,
+                False) # no way to know, no need to know
+            self.peer_proto_impl.recover()
+        else:
+            self._delay_recover = True
     
     def prepare(self, home_path, listening):
         if not self.peer_iface and (self.peer_proto and (listening or (self.peer_addr and self.peer_port))):
@@ -247,7 +251,10 @@ class TunIface(object):
         if self.peer_iface:
             if not self.peer_proto_impl:
                 self.peer_proto_impl = self._impl_instance(home_path, listening)
-            self.peer_proto_impl.prepare()
+            if self._delay_recover:
+                self.peer_proto_impl.recover()
+            else:
+                self.peer_proto_impl.prepare()
     
     def setup(self):
         if self.peer_proto_impl:
index d3970d4..2ce01c2 100644 (file)
@@ -673,7 +673,7 @@ attributes = dict({
                 "type": Attribute.DOUBLE,
                 "range": (0,100),
                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
-                "validation_function": validation.is_double,
+                "validation_function": validation.is_number,
             }),
     "max_reliability": dict({
                 "name": "maxReliability",
@@ -681,7 +681,7 @@ attributes = dict({
                 "type": Attribute.DOUBLE,
                 "range": (0,100),
                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
-                "validation_function": validation.is_double,
+                "validation_function": validation.is_number,
             }),
     "min_bandwidth": dict({
                 "name": "minBandwidth",
@@ -689,7 +689,7 @@ attributes = dict({
                 "type": Attribute.DOUBLE,
                 "range": (0,2**31),
                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
-                "validation_function": validation.is_double,
+                "validation_function": validation.is_number,
             }),
     "max_bandwidth": dict({
                 "name": "maxBandwidth",
@@ -697,7 +697,7 @@ attributes = dict({
                 "type": Attribute.DOUBLE,
                 "range": (0,2**31),
                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
-                "validation_function": validation.is_double,
+                "validation_function": validation.is_number,
             }),
             
     "up": dict({
@@ -872,25 +872,25 @@ attributes = dict({
                 "name": "bwIn", 
                 "help": "Inbound bandwidth limit (in Mbit/s)",
                 "type": Attribute.DOUBLE,
-                "validation_function": validation.is_double,
+                "validation_function": validation.is_number,
             }),
     "bw_out":  dict({
                 "name": "bwOut", 
                 "help": "Outbound bandwidth limit (in Mbit/s)",
                 "type": Attribute.DOUBLE,
-                "validation_function": validation.is_double,
+                "validation_function": validation.is_number,
             }),
     "plr_in":  dict({
                 "name": "plrIn", 
                 "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)",
                 "type": Attribute.DOUBLE,
-                "validation_function": validation.is_double,
+                "validation_function": validation.is_number,
             }),
     "plr_out":  dict({
                 "name": "plrOut", 
                 "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)",
                 "type": Attribute.DOUBLE,
-                "validation_function": validation.is_double,
+                "validation_function": validation.is_number,
             }),
     "delay_in":  dict({
                 "name": "delayIn", 
index dec4d82..1b9b15e 100644 (file)
@@ -22,6 +22,20 @@ import application
 class UnresponsiveNodeError(RuntimeError):
     pass
 
+def _castproperty(typ, propattr):
+    def _get(self):
+        return getattr(self, propattr)
+    def _set(self, value):
+        if value is not None or (isinstance(value, basestring) and not value):
+            value = typ(value)
+        return setattr(self, propattr, value)
+    def _del(self, value):
+        return delattr(self, propattr)
+    _get.__name__ = propattr + '_get'
+    _set.__name__ = propattr + '_set'
+    _del.__name__ = propattr + '_del'
+    return property(_get, _set, _del)
+
 class Node(object):
     BASEFILTERS = {
         # Map Node attribute to plcapi filter name
@@ -46,6 +60,11 @@ class Node(object):
     RPM_FUSION_URL = 'http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-stable.noarch.rpm'
     RPM_FUSION_URL_F12 = 'http://download1.rpmfusion.org/free/fedora/releases/12/Everything/x86_64/os/rpmfusion-free-release-12-1.noarch.rpm'
     
+    minReliability = _castproperty(float, '_minReliability')
+    maxReliability = _castproperty(float, '_maxReliability')
+    minBandwidth = _castproperty(float, '_minBandwidth')
+    maxBandwidth = _castproperty(float, '_maxBandwidth')
+    
     def __init__(self, api=None):
         if not api:
             api = plcapi.PLCAPI()
@@ -322,8 +341,14 @@ class Node(object):
             raise AssertionError, "Misconfigured node: unspecified slice"
 
     def recover(self):
-        # Just mark dependencies installed
+        # Mark dependencies installed
         self._installed = True
+        
+        # Clear load attributes, they impair re-discovery
+        self.minReliability = \
+        self.maxReliability = \
+        self.minBandwidth = \
+        self.maxBandwidth = None
 
     def install_dependencies(self):
         if self.required_packages and not self._installed:
index a20c7ca..ff8dc7b 100755 (executable)
@@ -102,9 +102,13 @@ class PlanetLabMultiIntegrationTestCase(unittest.TestCase):
         
         return node1, iface1, tap1, tap1ip, inet
     
-    def _test_plpl_crossconnect(self, proto):
+    def _test_plpl_crossconnect(self, proto, recover = False):
         pl, pl2, exp = self.make_experiment_desc()
         
+        if recover:
+            pl.set_attribute_value(DC.RECOVERY_POLICY, DC.POLICY_RECOVER)
+            pl2.set_attribute_value(DC.RECOVERY_POLICY, DC.POLICY_RECOVER)
+        
         # Create PL node, ifaces, assign addresses
         node1, iface1, tap1, tap1ip, inet1 = self.make_pl_tapnode(pl, 
             "192.168.2.2", self.host1pl1, "node1")
@@ -129,18 +133,26 @@ class PlanetLabMultiIntegrationTestCase(unittest.TestCase):
 
         xml = exp.to_xml()
 
-        controller = ExperimentController(xml, self.root_dir)
-        controller.start()
-
-        while not controller.is_finished(ping.guid):
-            time.sleep(0.5)
-          
-        ping_result = controller.trace(ping.guid, "stdout")
-        tap_trace = controller.trace(tap1.guid, "packets")
-        tap2_trace = controller.trace(tap2.guid, "packets")
-
-        controller.stop()
-        controller.shutdown()
+        try:
+            controller = ExperimentController(xml, self.root_dir)
+            controller.start()
+            
+            if recover:
+                controller = None
+                controller = ExperimentController(None, self.root_dir)
+                controller.recover()
+
+            while not controller.is_finished(ping.guid):
+                time.sleep(0.5)
+              
+            ping_result = controller.trace(ping.guid, "stdout")
+            tap_trace = controller.trace(tap1.guid, "packets")
+            tap2_trace = controller.trace(tap2.guid, "packets")
+        
+        finally:
+            if controller is not None:
+                controller.stop()
+                controller.shutdown()
 
         # asserts at the end, to make sure there's proper cleanup
         self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
@@ -159,6 +171,12 @@ class PlanetLabMultiIntegrationTestCase(unittest.TestCase):
     def test_plpl_crossconnect_tcp(self):
         self._test_plpl_crossconnect("tcp")
 
+    @test_util.skipUnless(test_util.pl_auth() is not None, 
+        "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
+    def test_plpl_crossconnect_udp_recover(self):
+        self._test_plpl_crossconnect("udp", 
+            recover = True)
+
 
 if __name__ == '__main__':
     unittest.main()