From 1d98deb66940dd123e17d9c31b88bf3e796a66c3 Mon Sep 17 00:00:00 2001 From: Claudio-Daniel Freire Date: Thu, 28 Jul 2011 17:13:17 +0200 Subject: [PATCH] Cross-connection recovery among PlanetLab instances Still not tested with other testbeds --- src/nepi/testbeds/planetlab/execute.py | 15 +++---- src/nepi/testbeds/planetlab/interfaces.py | 17 +++++--- src/nepi/testbeds/planetlab/metadata.py | 16 +++---- src/nepi/testbeds/planetlab/node.py | 27 +++++++++++- test/testbeds/planetlab/integration_multi.py | 44 ++++++++++++++------ 5 files changed, 85 insertions(+), 34 deletions(-) diff --git a/src/nepi/testbeds/planetlab/execute.py b/src/nepi/testbeds/planetlab/execute.py index 2cbdaff2..dbe0fb5a 100644 --- a/src/nepi/testbeds/planetlab/execute.py +++ b/src/nepi/testbeds/planetlab/execute.py @@ -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 diff --git a/src/nepi/testbeds/planetlab/interfaces.py b/src/nepi/testbeds/planetlab/interfaces.py index 0a05bab0..4f9c69be 100644 --- a/src/nepi/testbeds/planetlab/interfaces.py +++ b/src/nepi/testbeds/planetlab/interfaces.py @@ -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: diff --git a/src/nepi/testbeds/planetlab/metadata.py b/src/nepi/testbeds/planetlab/metadata.py index d3970d43..2ce01c23 100644 --- a/src/nepi/testbeds/planetlab/metadata.py +++ b/src/nepi/testbeds/planetlab/metadata.py @@ -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", diff --git a/src/nepi/testbeds/planetlab/node.py b/src/nepi/testbeds/planetlab/node.py index dec4d825..1b9b15e2 100644 --- a/src/nepi/testbeds/planetlab/node.py +++ b/src/nepi/testbeds/planetlab/node.py @@ -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: diff --git a/test/testbeds/planetlab/integration_multi.py b/test/testbeds/planetlab/integration_multi.py index a20c7ca4..ff8dc7b1 100755 --- a/test/testbeds/planetlab/integration_multi.py +++ b/test/testbeds/planetlab/integration_multi.py @@ -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() -- 2.47.0