diff --git a/src/allmydata/storage/http_client.py b/src/allmydata/storage/http_client.py index 475aa2330..d83ecbdff 100644 --- a/src/allmydata/storage/http_client.py +++ b/src/allmydata/storage/http_client.py @@ -161,7 +161,7 @@ class StorageClientImmutables(object): APIs for interacting with immutables. """ - def __init__(self, client): # type: (StorageClient) -> None + def __init__(self, client: StorageClient): self._client = client @inlineCallbacks @@ -208,6 +208,27 @@ class StorageClientImmutables(object): ) ) + @inlineCallbacks + def abort_upload( + self, storage_index: bytes, share_number: int, upload_secret: bytes + ) -> Deferred[None]: + """Abort the upload.""" + url = self._client.relative_url( + "/v1/immutable/{}/{}/abort".format(_encode_si(storage_index), share_number) + ) + response = yield self._client.request( + "PUT", + url, + upload_secret=upload_secret, + ) + + if response.code == http.OK: + return + else: + raise ClientException( + response.code, + ) + @inlineCallbacks def write_share_chunk( self, storage_index, share_number, upload_secret, offset, data diff --git a/src/allmydata/storage/http_server.py b/src/allmydata/storage/http_server.py index d0653a97c..1acf08a81 100644 --- a/src/allmydata/storage/http_server.py +++ b/src/allmydata/storage/http_server.py @@ -303,6 +303,33 @@ class HTTPServer(object): }, ) + @_authorized_route( + _app, + {Secrets.UPLOAD}, + "/v1/immutable///abort", + methods=["PUT"], + ) + def abort_share_upload(self, request, authorization, storage_index, share_number): + """Abort an in-progress immutable share upload.""" + try: + bucket = self._uploads.get_write_bucket( + storage_index, share_number, authorization[Secrets.UPLOAD] + ) + except _HTTPError: + # TODO 3877 If 404, check if this was already uploaded, in which case return 405 + # TODO 3877 write tests for 404 cases? + raise + + # TODO 3877 test for checking upload secret + + # Abort the upload: + bucket.abort() + # Stop tracking the bucket, so we can create a new one later if a + # client requests it: + self._uploads.remove_write_bucket(storage_index, share_number) + + return b"" + @_authorized_route( _app, {Secrets.UPLOAD}, diff --git a/src/allmydata/storage_client.py b/src/allmydata/storage_client.py index c2e26b4b6..9e47ba3ff 100644 --- a/src/allmydata/storage_client.py +++ b/src/allmydata/storage_client.py @@ -1059,7 +1059,8 @@ class _HTTPBucketWriter(object): finished = attr.ib(type=bool, default=False) def abort(self): - pass # TODO in later ticket + return self.client.abort_upload(self.storage_index, self.share_number, + self.upload_secret) @defer.inlineCallbacks def write(self, offset, data): diff --git a/src/allmydata/test/test_istorageserver.py b/src/allmydata/test/test_istorageserver.py index 95261ddb2..668eeecc5 100644 --- a/src/allmydata/test/test_istorageserver.py +++ b/src/allmydata/test/test_istorageserver.py @@ -176,8 +176,9 @@ class IStorageServerImmutableAPIsTestsMixin(object): canary=Referenceable(), ) - # Bucket 1 is fully written in one go. - yield allocated[0].callRemote("write", 0, b"1" * 1024) + # Bucket 1 get some data written (but not all, or HTTP implicitly + # finishes the upload) + yield allocated[0].callRemote("write", 0, b"1" * 1023) # Disconnect or abort, depending on the test: yield abort_or_disconnect(allocated[0]) @@ -1156,7 +1157,6 @@ class HTTPImmutableAPIsTests( # These will start passing in future PRs as HTTP protocol is implemented. SKIP_TESTS = { - "test_abort", "test_add_lease_renewal", "test_add_new_lease", "test_advise_corrupt_share",