diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py index 1ec0f75305d2307cafeedd7187f08ef3e1779fa0..82d996f7c760bdee4ed6b72e0823ee43cb2b2050 100644 --- a/src/_zkapauthorizer/_plugin.py +++ b/src/_zkapauthorizer/_plugin.py @@ -238,7 +238,7 @@ def _create_maintenance_service(reactor, node_config, client_node): # Create the operation which performs the lease maintenance job when # called. maintain_leases = maintain_leases_from_root( - client_node.create_node_from_uri( + lambda: client_node.create_node_from_uri( node_config.get_private_config(b"rootcap"), ), client_node.get_storage_broker(), diff --git a/src/_zkapauthorizer/lease_maintenance.py b/src/_zkapauthorizer/lease_maintenance.py index f32de8dff441b6832a906f32681182c7a9233f45..4289232b0e3958ca097681f0388076633fa2836b 100644 --- a/src/_zkapauthorizer/lease_maintenance.py +++ b/src/_zkapauthorizer/lease_maintenance.py @@ -50,6 +50,7 @@ from twisted.python.log import ( from allmydata.interfaces import ( IDirectoryNode, + IFilesystemNode, ) from allmydata.util.hashutil import ( file_renewal_secret_hash, @@ -81,6 +82,10 @@ def visit_storage_indexes(root_node, visit): :return Deferred: A Deferred which fires after all nodes have been visited. """ + if not IFilesystemNode.providedBy(root_node): + raise TypeError("root_node must provide IFilesystemNode, {!r} does not".format( + root_node, + )) stack = [root_node] while stack: elem = stack.pop() @@ -438,7 +443,7 @@ def read_time_from_path(path): return parse_datetime(when) -def visit_storage_indexes_from_root(visitor, root_node): +def visit_storage_indexes_from_root(visitor, get_root_node): """ An operation for ``lease_maintenance_service`` which applies the given visitor to ``root_node`` and all its children. @@ -446,14 +451,18 @@ def visit_storage_indexes_from_root(visitor, root_node): :param visitor: A one-argument callable which takes the traversal function and which should call it as desired. - :param IFilesystemNode root_node: The filesystem node at which traversal - will begin. + :param get_root_node: A no-argument callable which returns the filesystem + node (``IFilesystemNode``) at which traversal will begin. :return: A no-argument callable to perform the visits. """ - return partial( - visitor, - partial(visit_storage_indexes, root_node), + return lambda: visitor( + partial( + visit_storage_indexes, + # Make sure we call get_root_node each time to give us a chance to + # notice when it changes. + get_root_node(), + ), ) @@ -486,7 +495,7 @@ class MemoryMaintenanceObserver(object): def maintain_leases_from_root( - root_node, + get_root_node, storage_broker, secret_holder, min_lease_remaining, @@ -498,8 +507,9 @@ def maintain_leases_from_root( and all its children and renews their leases if they have ``min_lease_remaining`` or less on them. - :param IFilesystemNode root_node: A Tahoe-LAFS filesystem node to use as - the root of a node hierarchy to be maintained. + :param get_root_node: A no-argument callable which returns the Tahoe-LAFS + filesystem node (``IFilesystemNode``) to use as the root of a node + hierarchy to be maintained. :param StorageFarmBroker storage_broker: The storage broker which can put us in touch with storage servers where shares of the nodes to maintain @@ -529,7 +539,7 @@ def maintain_leases_from_root( return visit_storage_indexes_from_root( visitor, - root_node, + get_root_node, ) diff --git a/src/_zkapauthorizer/tests/test_lease_maintenance.py b/src/_zkapauthorizer/tests/test_lease_maintenance.py index 5b90fe3654839390e7a551bfc80b1d0a2bfd5160..d5dd7c530c81750a052ed9dfabf93dbd4e415f3d 100644 --- a/src/_zkapauthorizer/tests/test_lease_maintenance.py +++ b/src/_zkapauthorizer/tests/test_lease_maintenance.py @@ -411,7 +411,7 @@ class VisitStorageIndexesFromRootTests(TestCase): operation = visit_storage_indexes_from_root( perform_visit, - root_node, + lambda: root_node, ) self.assertThat( @@ -516,7 +516,7 @@ class MaintainLeasesFromRootTests(TestCase): ) operation = maintain_leases_from_root( - root_node, + lambda: root_node, storage_broker, secret_holder, min_lease_remaining, @@ -569,7 +569,7 @@ class MaintainLeasesFromRootTests(TestCase): observers = [observer] progress = observers.pop operation = maintain_leases_from_root( - root_node, + lambda: root_node, storage_broker, secret_holder, min_lease_remaining, diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py index c6b4e89d0cff64d7870ae4588cdd2f1bc725bdd1..55c30a5d2205b5427300359c427f33c95482dca1 100644 --- a/src/_zkapauthorizer/tests/test_plugin.py +++ b/src/_zkapauthorizer/tests/test_plugin.py @@ -441,7 +441,7 @@ class LeaseMaintenanceServiceTests(TestCase): """ Tests for the plugin's initialization of the lease maintenance service. """ - def _created_test(self, get_config, servers_yaml): + def _created_test(self, get_config, servers_yaml, rootcap): original_tempdir = tempfile.tempdir tempdir = self.useFixture(TempDir()) @@ -455,10 +455,11 @@ class LeaseMaintenanceServiceTests(TestCase): b"servers.yaml", servers_yaml, ) - config.write_private_config( - b"rootcap", - b"dddddddd", - ) + if rootcap: + config.write_private_config( + b"rootcap", + b"dddddddd", + ) try: d = create_client_from_config(config) @@ -492,4 +493,19 @@ class LeaseMaintenanceServiceTests(TestCase): maintenance service after it has at least one storage server to connect to. """ - return self._created_test(get_config, servers_yaml) + return self._created_test(get_config, servers_yaml, rootcap=True) + + + @settings( + deadline=None, + ) + @given( + tahoe_configs_with_dummy_redeemer, + sampled_from([SERVERS_YAML, TWO_SERVERS_YAML]), + ) + def test_created_without_rootcap(self, get_config, servers_yaml): + """ + The lease maintenance service can be created even if no rootcap has yet + been written to the client's configuration directory. + """ + return self._created_test(get_config, servers_yaml, rootcap=False)