diff --git a/src/_zkapauthorizer/lease_maintenance.py b/src/_zkapauthorizer/lease_maintenance.py index ea4e9206dae09887468883f8550e86f3099e72e3..dfb251db184171c3dfb8d0785b061013e515d0ce 100644 --- a/src/_zkapauthorizer/lease_maintenance.py +++ b/src/_zkapauthorizer/lease_maintenance.py @@ -256,16 +256,14 @@ class _FuzzyTimerService(Service): Schedule the next run of the operation. """ self._call = self.reactor.callLater( - self.sample_interval_distribution(), + self.sample_interval_distribution().total_seconds(), self._iterate, ) def lease_maintenance_service( + maintain_leases, reactor, - root_node, - storage_broker, - secret_holder, last_run, random, interval_mean=None, @@ -278,17 +276,6 @@ def lease_maintenance_service( :param IReactorClock reactor: A Twisted reactor for scheduling renewal activity. - :param IFilesystemNode root_node: A Tahoe-LAFS filesystem node 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 - might be found. - - :param SecretHolder secret_holder: The Tahoe-LAFS client node secret - holder which can give us the lease renewal secrets needed to renew - leases. - :param datetime last_run: The time at which lease maintenance last ran to inform an adjustment to the first interval before running it again, or ``None`` not to make such an adjustment. @@ -300,6 +287,9 @@ def lease_maintenance_service( :param timedelta interval_range: The range of the uniform distribution of lease renewal checks (centered on ``interval_mean``). + + :param maintain_leases: A no-argument callable which performs a round of + lease-maintenance. The resulting service calls this periodically. """ if interval_mean is None: interval_mean = timedelta(days=26) @@ -307,7 +297,6 @@ def lease_maintenance_service( interval_range = timedelta(days=4) halfrange = interval_range / 2 - min_lease_remaining = timedelta(days=3) def sample_interval_distribution(): return timedelta( seconds=random.uniform( @@ -327,20 +316,41 @@ def lease_maintenance_service( initial_interval, timedelta(0), ) + return _FuzzyTimerService( - partial( - renew_leases, - partial(visit_filesystem_nodes, root_node), - storage_broker, - secret_holder, - min_lease_remaining, - lambda: datetime.utcfromtimestamp(reactor.seconds()), - ), + maintain_leases, initial_interval, sample_interval_distribution, reactor, ) + +def maintain_leases_from_root(root_node, storage_broker, secret_holder, min_lease_remaining, get_now): + """ + :param IFilesystemNode root_node: A Tahoe-LAFS filesystem node 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 + might be found. + + :param SecretHolder secret_holder: The Tahoe-LAFS client node secret + holder which can give us the lease renewal secrets needed to renew + leases. + + :param get_now: A no-argument callable that returns the current time as a + ``datetime`` instance. + """ + return partial( + renew_leases, + partial(visit_filesystem_nodes, root_node), + storage_broker, + secret_holder, + min_lease_remaining, + get_now, + ) + + def calculate_initial_interval(sample_interval_distribution, last_run, now): """ Determine how long to wait before performing an initial (for this process) diff --git a/src/_zkapauthorizer/tests/test_lease_maintenance.py b/src/_zkapauthorizer/tests/test_lease_maintenance.py index 27b5e7d833c57f5f62967b5b394ff7686b860a00..670fd47d17135071fcb1c184a3a91995417f9922 100644 --- a/src/_zkapauthorizer/tests/test_lease_maintenance.py +++ b/src/_zkapauthorizer/tests/test_lease_maintenance.py @@ -31,6 +31,9 @@ import attr from testtools import ( TestCase, ) +from testtools.matchers import ( + Equals, +) from hypothesis import ( given, note, @@ -58,9 +61,9 @@ from twisted.application.service import ( from allmydata.util.hashutil import ( CRYPTO_VAL_SIZE, ) -from allmydata.client import ( - SecretHolder, -) +# from allmydata.client import ( +# SecretHolder, +# ) from ..foolscap import ( ShareStat, @@ -97,6 +100,10 @@ def interval_means(): ) +def dummy_maintain_leases(): + pass + + @attr.s class DummyStorageServer(object): """ @@ -171,14 +178,9 @@ class LeaseMaintenanceServiceTests(TestCase): The service provides ``IService``. """ clock = Clock() - root_node = object() - lease_secret = b"\0" * CRYPTO_VAL_SIZE - convergence_secret = b"\1" * CRYPTO_VAL_SIZE service = lease_maintenance_service( + dummy_maintain_leases, clock, - root_node, - DummyStorageBroker(clock, []), - SecretHolder(lease_secret, convergence_secret), None, random, ) @@ -199,20 +201,14 @@ class LeaseMaintenanceServiceTests(TestCase): with a size of ``range``. """ clock = Clock() - root_node = object() - lease_secret = b"\0" * CRYPTO_VAL_SIZE - convergence_secret = b"\1" * CRYPTO_VAL_SIZE - # Construct a range that fits in with the mean range_ = timedelta( seconds=random.uniform(0, mean.total_seconds()), ) service = lease_maintenance_service( + dummy_maintain_leases, clock, - root_node, - DummyStorageBroker(clock, []), - SecretHolder(lease_secret, convergence_secret), None, random, mean, @@ -243,10 +239,6 @@ class LeaseMaintenanceServiceTests(TestCase): run. """ datetime_now = datetime.utcfromtimestamp(clock.seconds()) - root_node = object() - lease_secret = b"\0" * CRYPTO_VAL_SIZE - convergence_secret = b"\1" * CRYPTO_VAL_SIZE - # Construct a range that fits in with the mean range_ = timedelta( seconds=random.uniform(0, mean.total_seconds()), @@ -256,10 +248,8 @@ class LeaseMaintenanceServiceTests(TestCase): last_run = datetime_now - since_last_run service = lease_maintenance_service( + dummy_maintain_leases, clock, - root_node, - DummyStorageBroker(clock, []), - SecretHolder(lease_secret, convergence_secret), last_run, random, mean, @@ -288,3 +278,30 @@ class LeaseMaintenanceServiceTests(TestCase): datetime.utcfromtimestamp(maintenance_call.getTime()), between(low, high), ) + + @given( + randoms(), + clocks(), + ) + def test_nodes_visited(self, random, clock): + """ + When the service runs, it calls the ``maintain_leases`` object. + """ + leases_maintained_at = [] + def maintain_leases(): + leases_maintained_at.append(datetime.utcfromtimestamp(clock.seconds())) + + service = lease_maintenance_service( + maintain_leases, + clock, + None, + random, + ) + service.startService() + [maintenance_call] = clock.getDelayedCalls() + clock.advance(maintenance_call.getTime() - clock.seconds()) + + self.assertThat( + leases_maintained_at, + Equals([datetime.utcfromtimestamp(clock.seconds())]), + )