diff --git a/src/_secureaccesstokenauthorizer/_storage_client.py b/src/_secureaccesstokenauthorizer/_storage_client.py index f1df27490e70e7c8a6938e7ca6aa0b52b3c2a177..bfdf7b9930474b2e83464f728f67d31585b83c4f 100644 --- a/src/_secureaccesstokenauthorizer/_storage_client.py +++ b/src/_secureaccesstokenauthorizer/_storage_client.py @@ -90,3 +90,16 @@ class SecureAccessTokenAuthorizerStorageClient(object): renew_secret, cancel_secret, ) + + def renew_lease( + self, + storage_index, + renew_secret, + ): + return self._rref.callRemote( + "renew_lease", + self._get_tokens(), + storage_index, + renew_secret, + ) + diff --git a/src/_secureaccesstokenauthorizer/_storage_server.py b/src/_secureaccesstokenauthorizer/_storage_server.py index 77ab53b94b1d8244110e470d1495fa146f47e1b1..0943681b5c5bff41eca5b23172b502539d19c175 100644 --- a/src/_secureaccesstokenauthorizer/_storage_server.py +++ b/src/_secureaccesstokenauthorizer/_storage_server.py @@ -119,6 +119,10 @@ class SecureAccessTokenAuthorizerStorageServer(Referenceable): self._validate_tokens(tokens) return self._original.remote_add_lease(*a, **kw) + def remote_renew_lease(self, tokens, *a, **kw): + self._validate_tokens(tokens) + return self._original.remote_renew_lease(*a, **kw) + # I don't understand why this is required. # SecureAccessTokenAuthorizerStorageServer is-a Referenceable. It seems like # the built in adapter should take care of this case. diff --git a/src/_secureaccesstokenauthorizer/tests/test_storage_protocol.py b/src/_secureaccesstokenauthorizer/tests/test_storage_protocol.py index 96369af1c7b4e2249028d65bf07f4a2bed79f03f..35bd82de39c649887b4fc6096602347a9412a13b 100644 --- a/src/_secureaccesstokenauthorizer/tests/test_storage_protocol.py +++ b/src/_secureaccesstokenauthorizer/tests/test_storage_protocol.py @@ -17,13 +17,10 @@ Tests for communication between the client and server components. """ import attr -from struct import ( - unpack, -) - from fixtures import ( Fixture, TempDir, + MonkeyPatch, ) from testtools import ( TestCase, @@ -225,14 +222,80 @@ class ImmutableTests(TestCase): cancel_secret, ), ) - - # It's hard to assert much about the lease without knowing about - # *some* implementation details of the storage server. I prefer to - # know Python API details rather than on-disk format details. - [(_, reader)] = self.server.remote_get_buckets(storage_index).items() - leases = list(reader._share_file.get_leases()) + [(_, leases)] = get_leases(self.server, storage_index).items() self.assertThat(leases, HasLength(2)) + @given( + storage_index=storage_indexes(), + renew_secret=lease_renew_secrets(), + cancel_secret=lease_cancel_secrets(), + sharenum=sharenums(), + size=sizes(), + ) + def test_renew_lease(self, storage_index, renew_secret, cancel_secret, sharenum, size): + """ + A lease on an immutable share can be updated to expire at a later time. + """ + # Hypothesis causes our storage server to be used many times. Clean + # up between iterations. + cleanup_storage_server(self.anonymous_storage_server) + + # Take control of time (in this hacky, fragile way) so we can verify + # the expiration time gets bumped by the renewal. + now = 1000000000.5 + self.useFixture(MonkeyPatch("time.time", lambda: now)) + + # Create a share we can toy with. + _, allocated = self.anonymous_storage_server.remote_allocate_buckets( + storage_index, + renew_secret, + cancel_secret, + {sharenum}, + size, + canary=self.canary, + ) + [(_, writer)] = allocated.items() + writer.remote_write(0, bytes_for_share(sharenum, size)) + writer.remote_close() + + now += 100000 + extract_result( + self.client.renew_lease( + storage_index, + renew_secret, + ), + ) + + # Based on Tahoe-LAFS' hard-coded renew time. + RENEW_INTERVAL = 60 * 60 * 24 * 31 + + [(_, [lease])] = get_leases(self.server, storage_index).items() + self.assertThat( + lease.get_expiration_time(), + Equals(int(now + RENEW_INTERVAL)), + ) + + +def get_leases(storage_server, storage_index): + """ + Get all leases for all shares of the given storage index on the given + server. + + :param StorageServer storage_server: The storage server on which to find + the information. + + :param bytes storage_index: The storage index for which to look up shares. + + :return dict[int, list[LeaseInfo]]: The lease information for each share. + """ + # It's hard to assert much about the lease without knowing about *some* + # implementation details of the storage server. I prefer to know Python + # API details rather than on-disk format details. + return { + sharenum: list(reader._share_file.get_leases()) + for (sharenum, reader) + in storage_server.remote_get_buckets(storage_index).items() + } def cleanup_storage_server(storage_server): """