Client API sketch for basic immutable interactions.
This commit is contained in:
parent
5f4db487f7
commit
9c20ac8e7b
|
@ -16,17 +16,19 @@ if PY2:
|
||||||
else:
|
else:
|
||||||
# typing module not available in Python 2, and we only do type checking in
|
# typing module not available in Python 2, and we only do type checking in
|
||||||
# Python 3 anyway.
|
# Python 3 anyway.
|
||||||
from typing import Union
|
from typing import Union, Set, List
|
||||||
from treq.testing import StubTreq
|
from treq.testing import StubTreq
|
||||||
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
# TODO Make sure to import Python version?
|
# TODO Make sure to import Python version?
|
||||||
from cbor2 import loads
|
from cbor2 import loads
|
||||||
|
|
||||||
|
|
||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
from twisted.internet.defer import inlineCallbacks, returnValue, fail
|
from twisted.internet.defer import inlineCallbacks, returnValue, fail, Deferred
|
||||||
from hyperlink import DecodedURL
|
from hyperlink import DecodedURL
|
||||||
import treq
|
import treq
|
||||||
|
|
||||||
|
@ -47,6 +49,80 @@ def swissnum_auth_header(swissnum): # type: (bytes) -> bytes
|
||||||
return b"Tahoe-LAFS " + b64encode(swissnum).strip()
|
return b"Tahoe-LAFS " + b64encode(swissnum).strip()
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class ImmutableCreateResult(object):
|
||||||
|
"""Result of creating a storage index for an immutable."""
|
||||||
|
|
||||||
|
already_have = attr.ib(type=Set[int])
|
||||||
|
allocated = attr.ib(type=Set[int])
|
||||||
|
|
||||||
|
|
||||||
|
class StorageClientImmutables(object):
|
||||||
|
"""
|
||||||
|
APIs for interacting with immutables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, client): # type: (StorageClient) -> None
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def create(
|
||||||
|
self,
|
||||||
|
storage_index,
|
||||||
|
share_numbers,
|
||||||
|
allocated_size,
|
||||||
|
upload_secret,
|
||||||
|
lease_renew_secret,
|
||||||
|
lease_cancel_secret,
|
||||||
|
): # type: (bytes, List[int], int, bytes, bytes, bytes) -> Deferred[ImmutableCreateResult]
|
||||||
|
"""
|
||||||
|
Create a new storage index for an immutable.
|
||||||
|
|
||||||
|
TODO retry internally on failure, to ensure the operation fully
|
||||||
|
succeeded. If sufficient number of failures occurred, the result may
|
||||||
|
fire with an error, but there's no expectation that user code needs to
|
||||||
|
have a recovery codepath; it will most likely just report an error to
|
||||||
|
the user.
|
||||||
|
|
||||||
|
Result fires when creating the storage index succeeded, if creating the
|
||||||
|
storage index failed the result will fire with an exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def write_share_chunk(
|
||||||
|
self, storage_index, share_number, upload_secret, offset, data
|
||||||
|
): # type: (bytes, int, bytes, int, bytes) -> Deferred[bool]
|
||||||
|
"""
|
||||||
|
Upload a chunk of data for a specific share.
|
||||||
|
|
||||||
|
TODO The implementation should retry failed uploads transparently a number
|
||||||
|
of times, so that if a failure percolates up, the caller can assume the
|
||||||
|
failure isn't a short-term blip.
|
||||||
|
|
||||||
|
Result fires when the upload succeeded, with a boolean indicating
|
||||||
|
whether the _complete_ share (i.e. all chunks, not just this one) has
|
||||||
|
been uploaded.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def read_share_chunk(
|
||||||
|
self, storage_index, share_number, offset, length
|
||||||
|
): # type: (bytes, int, int, int) -> Deferred[bytes]
|
||||||
|
"""
|
||||||
|
Download a chunk of data from a share.
|
||||||
|
|
||||||
|
TODO Failed downloads should be transparently retried and redownloaded
|
||||||
|
by the implementation a few times so that if a failure percolates up,
|
||||||
|
the caller can assume the failure isn't a short-term blip.
|
||||||
|
|
||||||
|
NOTE: the underlying HTTP protocol is much more flexible than this API,
|
||||||
|
so a future refactor may expand this in order to simplify the calling
|
||||||
|
code and perhaps download data more efficiently. But then again maybe
|
||||||
|
the HTTP protocol will be simplified, see
|
||||||
|
https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3777
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class StorageClient(object):
|
class StorageClient(object):
|
||||||
"""
|
"""
|
||||||
HTTP client that talks to the HTTP storage server.
|
HTTP client that talks to the HTTP storage server.
|
||||||
|
@ -77,7 +153,7 @@ class StorageClient(object):
|
||||||
for key, value in secrets.items():
|
for key, value in secrets.items():
|
||||||
headers.addRawHeader(
|
headers.addRawHeader(
|
||||||
"X-Tahoe-Authorization",
|
"X-Tahoe-Authorization",
|
||||||
b"%s %s" % (key.value.encode("ascii"), b64encode(value).strip())
|
b"%s %s" % (key.value.encode("ascii"), b64encode(value).strip()),
|
||||||
)
|
)
|
||||||
return self._treq.request(method, url, headers=headers, **kwargs)
|
return self._treq.request(method, url, headers=headers, **kwargs)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue