2006-12-02 02:17:50 +00:00
|
|
|
|
2007-01-05 04:52:51 +00:00
|
|
|
from zope.interface import Interface
|
2007-04-14 02:04:38 +00:00
|
|
|
from foolscap.schema import StringConstraint, ListOf, TupleOf, SetOf, DictOf, \
|
|
|
|
ChoiceOf
|
2007-06-15 07:37:32 +00:00
|
|
|
from foolscap import RemoteInterface, Referenceable
|
2006-12-02 02:17:50 +00:00
|
|
|
|
2007-03-30 03:19:52 +00:00
|
|
|
HASH_SIZE=32
|
|
|
|
|
2007-04-04 22:59:36 +00:00
|
|
|
Hash = StringConstraint(maxLength=HASH_SIZE,
|
|
|
|
minLength=HASH_SIZE)# binary format 32-byte SHA256 hash
|
|
|
|
Nodeid = StringConstraint(maxLength=20,
|
|
|
|
minLength=20) # binary format 20-byte SHA1 hash
|
2007-05-22 21:08:30 +00:00
|
|
|
FURL = StringConstraint(1000)
|
2007-07-23 02:48:44 +00:00
|
|
|
StorageIndex = StringConstraint(16)
|
2007-04-27 01:08:29 +00:00
|
|
|
URI = StringConstraint(300) # kind of arbitrary
|
2007-12-18 20:15:08 +00:00
|
|
|
|
2007-04-04 22:59:36 +00:00
|
|
|
MAX_BUCKETS = 200 # per peer
|
2007-09-11 18:29:18 +00:00
|
|
|
|
|
|
|
# MAX_SEGMENT_SIZE in encode.py is 1 MiB (this constraint allows k = 1)
|
|
|
|
ShareData = StringConstraint(2**20)
|
2007-06-08 22:59:16 +00:00
|
|
|
URIExtensionData = StringConstraint(1000)
|
2007-08-28 00:28:51 +00:00
|
|
|
LeaseRenewSecret = Hash # used to protect bucket lease renewal requests
|
|
|
|
LeaseCancelSecret = Hash # used to protect bucket lease cancellation requests
|
|
|
|
|
2006-12-02 02:17:50 +00:00
|
|
|
|
2007-03-23 23:15:57 +00:00
|
|
|
class RIIntroducerClient(RemoteInterface):
|
2007-05-22 21:08:30 +00:00
|
|
|
def new_peers(furls=SetOf(FURL)):
|
2007-03-23 23:15:57 +00:00
|
|
|
return None
|
2007-07-12 22:33:30 +00:00
|
|
|
def set_encoding_parameters(parameters=(int, int, int)):
|
|
|
|
"""Advise the client of the recommended k-of-n encoding parameters
|
|
|
|
for this grid. 'parameters' is a tuple of (k, desired, n), where 'n'
|
|
|
|
is the total number of shares that will be created for any given
|
|
|
|
file, while 'k' is the number of shares that must be retrieved to
|
|
|
|
recover that file, and 'desired' is the minimum number of shares that
|
|
|
|
must be placed before the uploader will consider its job a success.
|
|
|
|
n/k is the expansion ratio, while k determines the robustness.
|
|
|
|
|
|
|
|
Introducers should specify 'n' according to the expected size of the
|
|
|
|
grid (there is no point to producing more shares than there are
|
|
|
|
peers), and k according to the desired reliability-vs-overhead goals.
|
|
|
|
|
2007-07-12 23:22:12 +00:00
|
|
|
Note that setting k=1 is equivalent to simple replication.
|
2007-07-12 22:33:30 +00:00
|
|
|
"""
|
|
|
|
return None
|
2007-03-23 23:15:57 +00:00
|
|
|
|
|
|
|
class RIIntroducer(RemoteInterface):
|
2007-05-22 21:08:30 +00:00
|
|
|
def hello(node=RIIntroducerClient, furl=FURL):
|
2007-03-22 21:39:30 +00:00
|
|
|
return None
|
|
|
|
|
2007-03-27 23:12:11 +00:00
|
|
|
class RIClient(RemoteInterface):
|
2007-04-26 19:01:25 +00:00
|
|
|
def get_versions():
|
|
|
|
"""Return a tuple of (my_version, oldest_supported) strings.
|
|
|
|
|
|
|
|
Each string can be parsed by an allmydata.util.version.Version
|
|
|
|
instance, and then compared. The first goal is to make sure that
|
|
|
|
nodes are not confused by speaking to an incompatible peer. The
|
|
|
|
second goal is to enable the development of backwards-compatibility
|
|
|
|
code.
|
|
|
|
|
|
|
|
This method is likely to change in incompatible ways until we get the
|
|
|
|
whole compatibility scheme nailed down.
|
|
|
|
"""
|
|
|
|
return TupleOf(str, str)
|
2006-12-02 02:17:50 +00:00
|
|
|
def get_service(name=str):
|
2007-04-04 22:59:36 +00:00
|
|
|
return Referenceable
|
2007-03-23 23:15:57 +00:00
|
|
|
def get_nodeid():
|
|
|
|
return Nodeid
|
2006-12-02 02:17:50 +00:00
|
|
|
|
|
|
|
class RIBucketWriter(RemoteInterface):
|
2007-07-13 21:04:49 +00:00
|
|
|
def write(offset=int, data=ShareData):
|
|
|
|
return None
|
|
|
|
|
|
|
|
def close():
|
|
|
|
"""
|
|
|
|
If the data that has been written is incomplete or inconsistent then
|
|
|
|
the server will throw the data away, else it will store it for future
|
|
|
|
retrieval.
|
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
|
|
|
class RIBucketReader(RemoteInterface):
|
|
|
|
def read(offset=int, length=int):
|
|
|
|
return ShareData
|
|
|
|
|
2007-10-31 02:47:36 +00:00
|
|
|
TestVector = ListOf(TupleOf(int, int, str, str))
|
|
|
|
# elements are (offset, length, operator, specimen)
|
2007-11-06 03:17:14 +00:00
|
|
|
# operator is one of "lt, le, eq, ne, ge, gt"
|
2007-10-31 02:47:36 +00:00
|
|
|
# nop always passes and is used to fetch data while writing.
|
|
|
|
# you should use length==len(specimen) for everything except nop
|
|
|
|
DataVector = ListOf(TupleOf(int, ShareData))
|
|
|
|
# (offset, data). This limits us to 30 writes of 1MiB each per call
|
2007-11-06 03:17:14 +00:00
|
|
|
TestAndWriteVectorsForShares = DictOf(int,
|
|
|
|
TupleOf(TestVector,
|
|
|
|
DataVector,
|
|
|
|
ChoiceOf(None, int))) # new_length
|
2007-11-05 07:37:01 +00:00
|
|
|
ReadVector = ListOf(TupleOf(int, int))
|
2007-11-06 03:17:14 +00:00
|
|
|
ReadData = ListOf(ShareData)
|
2007-10-31 02:47:36 +00:00
|
|
|
# returns data[offset:offset+length] for each element of TestVector
|
|
|
|
|
2007-07-13 21:04:49 +00:00
|
|
|
class RIStorageServer(RemoteInterface):
|
|
|
|
def allocate_buckets(storage_index=StorageIndex,
|
2007-08-28 00:28:51 +00:00
|
|
|
renew_secret=LeaseRenewSecret,
|
|
|
|
cancel_secret=LeaseCancelSecret,
|
2007-07-13 21:04:49 +00:00
|
|
|
sharenums=SetOf(int, maxLength=MAX_BUCKETS),
|
2007-07-13 22:09:01 +00:00
|
|
|
allocated_size=int, canary=Referenceable):
|
2007-07-13 21:04:49 +00:00
|
|
|
"""
|
2007-08-28 00:28:51 +00:00
|
|
|
@param storage_index: the index of the bucket to be created or
|
|
|
|
increfed.
|
|
|
|
@param sharenums: these are the share numbers (probably between 0 and
|
|
|
|
99) that the sender is proposing to store on this
|
|
|
|
server.
|
|
|
|
@param renew_secret: This is the secret used to protect bucket refresh
|
|
|
|
This secret is generated by the client and
|
|
|
|
stored for later comparison by the server. Each
|
|
|
|
server is given a different secret.
|
|
|
|
@param cancel_secret: Like renew_secret, but protects bucket decref.
|
|
|
|
@param canary: If the canary is lost before close(), the bucket is
|
|
|
|
deleted.
|
2007-07-13 21:04:49 +00:00
|
|
|
@return: tuple of (alreadygot, allocated), where alreadygot is what we
|
2007-08-28 00:28:51 +00:00
|
|
|
already have and is what we hereby agree to accept. New
|
|
|
|
leases are added for shares in both lists.
|
2007-07-13 21:04:49 +00:00
|
|
|
"""
|
|
|
|
return TupleOf(SetOf(int, maxLength=MAX_BUCKETS),
|
|
|
|
DictOf(int, RIBucketWriter, maxKeys=MAX_BUCKETS))
|
2007-08-28 06:41:40 +00:00
|
|
|
|
|
|
|
def renew_lease(storage_index=StorageIndex, renew_secret=LeaseRenewSecret):
|
|
|
|
"""
|
|
|
|
Renew the lease on a given bucket. Some networks will use this, some
|
|
|
|
will not.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def cancel_lease(storage_index=StorageIndex,
|
|
|
|
cancel_secret=LeaseCancelSecret):
|
|
|
|
"""
|
|
|
|
Cancel the lease on a given bucket. If this was the last lease on the
|
|
|
|
bucket, the bucket will be deleted.
|
|
|
|
"""
|
|
|
|
|
2007-07-13 21:04:49 +00:00
|
|
|
def get_buckets(storage_index=StorageIndex):
|
|
|
|
return DictOf(int, RIBucketReader, maxKeys=MAX_BUCKETS)
|
|
|
|
|
|
|
|
|
2007-11-06 03:17:14 +00:00
|
|
|
|
|
|
|
def slot_readv(storage_index=StorageIndex,
|
|
|
|
shares=ListOf(int), readv=ReadVector):
|
|
|
|
"""Read a vector from the numbered shares associated with the given
|
|
|
|
storage index. An empty shares list means to return data from all
|
|
|
|
known shares. Returns a dictionary with one key per share."""
|
2007-11-07 21:19:01 +00:00
|
|
|
return DictOf(int, ReadData) # shnum -> results
|
2007-11-06 03:17:14 +00:00
|
|
|
|
|
|
|
def slot_testv_and_readv_and_writev(storage_index=StorageIndex,
|
|
|
|
secrets=TupleOf(Hash, Hash, Hash),
|
|
|
|
tw_vectors=TestAndWriteVectorsForShares,
|
|
|
|
r_vector=ReadVector,
|
|
|
|
):
|
|
|
|
"""General-purpose test-and-set operation for mutable slots. Perform
|
|
|
|
a bunch of comparisons against the existing shares. If they all pass,
|
|
|
|
then apply a bunch of write vectors to those shares. Then use the
|
|
|
|
read vectors to extract data from all the shares and return the data.
|
|
|
|
|
|
|
|
This method is, um, large. The goal is to allow clients to update all
|
|
|
|
the shares associated with a mutable file in a single round trip.
|
|
|
|
|
2007-10-31 02:47:36 +00:00
|
|
|
@param storage_index: the index of the bucket to be created or
|
|
|
|
increfed.
|
|
|
|
@param write_enabler: a secret that is stored along with the slot.
|
|
|
|
Writes are accepted from any caller who can
|
|
|
|
present the matching secret. A different secret
|
|
|
|
should be used for each slot*server pair.
|
|
|
|
@param renew_secret: This is the secret used to protect bucket refresh
|
|
|
|
This secret is generated by the client and
|
|
|
|
stored for later comparison by the server. Each
|
|
|
|
server is given a different secret.
|
|
|
|
@param cancel_secret: Like renew_secret, but protects bucket decref.
|
|
|
|
|
2007-11-06 03:17:14 +00:00
|
|
|
The 'secrets' argument is a tuple of (write_enabler, renew_secret,
|
|
|
|
cancel_secret). The first is required to perform any write. The
|
|
|
|
latter two are used when allocating new shares. To simply acquire a
|
|
|
|
new lease on existing shares, use an empty testv and an empty writev.
|
|
|
|
|
|
|
|
Each share can have a separate test vector (i.e. a list of
|
|
|
|
comparisons to perform). If all vectors for all shares pass, then all
|
|
|
|
writes for all shares are recorded. Each comparison is a 4-tuple of
|
2007-11-07 01:53:34 +00:00
|
|
|
(offset, length, operator, specimen), which effectively does a bool(
|
|
|
|
(read(offset, length)) OPERATOR specimen ) and only performs the
|
|
|
|
write if all these evaluate to True. Basic test-and-set uses 'eq'.
|
|
|
|
Write-if-newer uses a seqnum and (offset, length, 'lt', specimen).
|
|
|
|
Write-if-same-or-newer uses 'le'.
|
|
|
|
|
|
|
|
Reads from the end of the container are truncated, and missing shares
|
|
|
|
behave like empty ones, so to assert that a share doesn't exist (for
|
|
|
|
use when creating a new share), use (0, 1, 'eq', '').
|
2007-11-06 03:17:14 +00:00
|
|
|
|
|
|
|
The write vector will be applied to the given share, expanding it if
|
|
|
|
necessary. A write vector applied to a share number that did not
|
|
|
|
exist previously will cause that share to be created.
|
|
|
|
|
|
|
|
Each write vector is accompanied by a 'new_length' argument. If
|
|
|
|
new_length is not None, use it to set the size of the container. This
|
|
|
|
can be used to pre-allocate space for a series of upcoming writes, or
|
|
|
|
truncate existing data. If the container is growing, new_length will
|
|
|
|
be applied before datav. If the container is shrinking, it will be
|
|
|
|
applied afterwards.
|
|
|
|
|
|
|
|
The read vector is used to extract data from all known shares,
|
|
|
|
*before* any writes have been applied. The same vector is used for
|
|
|
|
all shares. This captures the state that was tested by the test
|
|
|
|
vector.
|
|
|
|
|
|
|
|
This method returns two values: a boolean and a dict. The boolean is
|
|
|
|
True if the write vectors were applied, False if not. The dict is
|
|
|
|
keyed by share number, and each value contains a list of strings, one
|
|
|
|
for each element of the read vector.
|
2007-10-31 02:47:36 +00:00
|
|
|
|
2007-11-06 03:17:14 +00:00
|
|
|
If the write_enabler is wrong, this will raise BadWriteEnablerError.
|
|
|
|
To enable share migration, the exception will have the nodeid used
|
|
|
|
for the old write enabler embedded in it, in the following string::
|
2007-10-31 02:47:36 +00:00
|
|
|
|
2007-11-06 03:17:14 +00:00
|
|
|
The write enabler was recorded by nodeid '%s'.
|
2007-10-31 02:47:36 +00:00
|
|
|
|
2007-11-07 01:49:59 +00:00
|
|
|
Note that the nodeid here is encoded using the same base32 encoding
|
|
|
|
used by Foolscap and allmydata.util.idlib.nodeid_b2a().
|
|
|
|
|
2007-11-06 03:17:14 +00:00
|
|
|
"""
|
|
|
|
return TupleOf(bool, DictOf(int, ReadData))
|
2007-10-31 02:47:36 +00:00
|
|
|
|
2007-07-13 21:04:49 +00:00
|
|
|
class IStorageBucketWriter(Interface):
|
2007-03-30 03:19:52 +00:00
|
|
|
def put_block(segmentnum=int, data=ShareData):
|
2007-03-30 23:50:50 +00:00
|
|
|
"""@param data: For most segments, this data will be 'blocksize'
|
|
|
|
bytes in length. The last segment might be shorter.
|
2007-07-25 02:43:21 +00:00
|
|
|
@return: a Deferred that fires (with None) when the operation completes
|
2007-03-30 23:50:50 +00:00
|
|
|
"""
|
2007-06-07 02:40:20 +00:00
|
|
|
|
|
|
|
def put_plaintext_hashes(hashes=ListOf(Hash, maxLength=2**20)):
|
2007-07-25 02:43:21 +00:00
|
|
|
"""
|
|
|
|
@return: a Deferred that fires (with None) when the operation completes
|
|
|
|
"""
|
|
|
|
|
2007-06-07 02:40:20 +00:00
|
|
|
def put_crypttext_hashes(hashes=ListOf(Hash, maxLength=2**20)):
|
2007-07-25 02:43:21 +00:00
|
|
|
"""
|
|
|
|
@return: a Deferred that fires (with None) when the operation completes
|
|
|
|
"""
|
2007-06-07 02:40:20 +00:00
|
|
|
|
2007-04-30 06:51:15 +00:00
|
|
|
def put_block_hashes(blockhashes=ListOf(Hash, maxLength=2**20)):
|
2007-07-25 02:43:21 +00:00
|
|
|
"""
|
|
|
|
@return: a Deferred that fires (with None) when the operation completes
|
|
|
|
"""
|
2007-11-01 22:22:57 +00:00
|
|
|
|
2007-07-25 02:43:21 +00:00
|
|
|
def put_share_hashes(sharehashes=ListOf(TupleOf(int, Hash),
|
|
|
|
maxLength=2**20)):
|
|
|
|
"""
|
|
|
|
@return: a Deferred that fires (with None) when the operation completes
|
|
|
|
"""
|
2007-03-30 03:19:52 +00:00
|
|
|
|
2007-06-08 22:59:16 +00:00
|
|
|
def put_uri_extension(data=URIExtensionData):
|
|
|
|
"""This block of data contains integrity-checking information (hashes
|
|
|
|
of plaintext, crypttext, and shares), as well as encoding parameters
|
|
|
|
that are necessary to recover the data. This is a serialized dict
|
|
|
|
mapping strings to other strings. The hash of this data is kept in
|
|
|
|
the URI and verified before any of the data is used. All buckets for
|
|
|
|
a given file contain identical copies of this data.
|
2007-06-08 23:17:54 +00:00
|
|
|
|
|
|
|
The serialization format is specified with the following pseudocode:
|
|
|
|
for k in sorted(dict.keys()):
|
|
|
|
assert re.match(r'^[a-zA-Z_\-]+$', k)
|
|
|
|
write(k + ':' + netstring(dict[k]))
|
2007-07-25 02:43:21 +00:00
|
|
|
|
|
|
|
@return: a Deferred that fires (with None) when the operation completes
|
2007-06-02 01:48:01 +00:00
|
|
|
"""
|
2007-07-25 02:43:21 +00:00
|
|
|
|
2006-12-02 02:17:50 +00:00
|
|
|
def close():
|
2007-07-25 02:43:21 +00:00
|
|
|
"""Finish writing and close the bucket. The share is not finalized
|
|
|
|
until this method is called: if the uploading client disconnects
|
|
|
|
before calling close(), the partially-written share will be
|
|
|
|
discarded.
|
|
|
|
|
|
|
|
@return: a Deferred that fires (with None) when the operation completes
|
|
|
|
"""
|
2007-07-13 21:04:49 +00:00
|
|
|
|
|
|
|
class IStorageBucketReader(Interface):
|
2006-12-02 02:17:50 +00:00
|
|
|
|
2007-03-30 03:19:52 +00:00
|
|
|
def get_block(blocknum=int):
|
2007-03-30 23:50:50 +00:00
|
|
|
"""Most blocks will be the same size. The last block might be shorter
|
|
|
|
than the others.
|
2007-07-25 02:43:21 +00:00
|
|
|
|
|
|
|
@return: ShareData
|
2007-03-30 23:50:50 +00:00
|
|
|
"""
|
2007-06-07 07:15:41 +00:00
|
|
|
|
|
|
|
def get_plaintext_hashes():
|
2007-07-25 02:43:21 +00:00
|
|
|
"""
|
|
|
|
@return: ListOf(Hash, maxLength=2**20)
|
|
|
|
"""
|
|
|
|
|
2007-06-07 07:15:41 +00:00
|
|
|
def get_crypttext_hashes():
|
2007-07-25 02:43:21 +00:00
|
|
|
"""
|
|
|
|
@return: ListOf(Hash, maxLength=2**20)
|
|
|
|
"""
|
2007-06-07 07:15:41 +00:00
|
|
|
|
2007-03-30 03:19:52 +00:00
|
|
|
def get_block_hashes():
|
2007-07-25 02:43:21 +00:00
|
|
|
"""
|
|
|
|
@return: ListOf(Hash, maxLength=2**20)
|
|
|
|
"""
|
|
|
|
|
2007-03-30 03:19:52 +00:00
|
|
|
def get_share_hashes():
|
2007-07-25 02:43:21 +00:00
|
|
|
"""
|
|
|
|
@return: ListOf(TupleOf(int, Hash), maxLength=2**20)
|
|
|
|
"""
|
|
|
|
|
2007-06-08 22:59:16 +00:00
|
|
|
def get_uri_extension():
|
2007-07-25 02:43:21 +00:00
|
|
|
"""
|
|
|
|
@return: URIExtensionData
|
|
|
|
"""
|
2007-06-02 01:48:01 +00:00
|
|
|
|
2006-12-03 00:25:57 +00:00
|
|
|
|
2007-07-09 06:27:46 +00:00
|
|
|
|
2007-04-04 22:59:36 +00:00
|
|
|
# hm, we need a solution for forward references in schemas
|
|
|
|
from foolscap.schema import Any
|
2007-06-15 03:14:34 +00:00
|
|
|
|
|
|
|
FileNode_ = Any() # TODO: foolscap needs constraints on copyables
|
|
|
|
DirectoryNode_ = Any() # TODO: same
|
|
|
|
AnyNode_ = ChoiceOf(FileNode_, DirectoryNode_)
|
2007-06-25 20:23:51 +00:00
|
|
|
EncryptedThing = str
|
2007-06-15 03:14:34 +00:00
|
|
|
|
2007-07-21 22:40:36 +00:00
|
|
|
class IURI(Interface):
|
|
|
|
def init_from_string(uri):
|
|
|
|
"""Accept a string (as created by my to_string() method) and populate
|
|
|
|
this instance with its data. I am not normally called directly,
|
|
|
|
please use the module-level uri.from_string() function to convert
|
|
|
|
arbitrary URI strings into IURI-providing instances."""
|
|
|
|
|
|
|
|
def is_readonly():
|
|
|
|
"""Return False if this URI be used to modify the data. Return True
|
|
|
|
if this URI cannot be used to modify the data."""
|
|
|
|
|
|
|
|
def is_mutable():
|
|
|
|
"""Return True if the data can be modified by *somebody* (perhaps
|
|
|
|
someone who has a more powerful URI than this one)."""
|
|
|
|
|
|
|
|
def get_readonly():
|
|
|
|
"""Return another IURI instance, which represents a read-only form of
|
|
|
|
this one. If is_readonly() is True, this returns self."""
|
|
|
|
|
2007-10-15 23:16:39 +00:00
|
|
|
def get_verifier():
|
|
|
|
"""Return an instance that provides IVerifierURI, which can be used
|
|
|
|
to check on the availability of the file or directory, without
|
|
|
|
providing enough capabilities to actually read or modify the
|
|
|
|
contents. This may return None if the file does not need checking or
|
|
|
|
verification (e.g. LIT URIs).
|
|
|
|
"""
|
|
|
|
|
|
|
|
def to_string():
|
|
|
|
"""Return a string of printable ASCII characters, suitable for
|
|
|
|
passing into init_from_string."""
|
|
|
|
|
|
|
|
class IVerifierURI(Interface):
|
|
|
|
def init_from_string(uri):
|
|
|
|
"""Accept a string (as created by my to_string() method) and populate
|
|
|
|
this instance with its data. I am not normally called directly,
|
|
|
|
please use the module-level uri.from_string() function to convert
|
|
|
|
arbitrary URI strings into IURI-providing instances."""
|
|
|
|
|
2007-07-21 22:40:36 +00:00
|
|
|
def to_string():
|
|
|
|
"""Return a string of printable ASCII characters, suitable for
|
|
|
|
passing into init_from_string."""
|
|
|
|
|
|
|
|
class IDirnodeURI(Interface):
|
|
|
|
"""I am a URI which represents a dirnode."""
|
|
|
|
|
2007-10-15 23:16:39 +00:00
|
|
|
|
2007-07-21 22:40:36 +00:00
|
|
|
class IFileURI(Interface):
|
|
|
|
"""I am a URI which represents a filenode."""
|
|
|
|
def get_size():
|
|
|
|
"""Return the length (in bytes) of the file that I represent."""
|
|
|
|
|
2007-11-01 22:15:29 +00:00
|
|
|
class IMutableFileURI(Interface):
|
|
|
|
"""I am a URI which represents a mutable filenode."""
|
|
|
|
pass
|
|
|
|
class INewDirectoryURI(Interface):
|
|
|
|
pass
|
2007-12-03 21:52:42 +00:00
|
|
|
class IReadonlyNewDirectoryURI(Interface):
|
|
|
|
pass
|
2007-11-01 22:15:29 +00:00
|
|
|
|
2007-07-21 22:40:36 +00:00
|
|
|
|
2007-12-03 21:52:42 +00:00
|
|
|
class IFilesystemNode(Interface):
|
|
|
|
def get_uri():
|
|
|
|
"""
|
|
|
|
Return the URI that can be used by others to get access to this
|
|
|
|
node. If this node is read-only, the URI will only offer read-only
|
|
|
|
access. If this node is read-write, the URI will offer read-write
|
|
|
|
access.
|
|
|
|
|
|
|
|
If you have read-write access to a node and wish to share merely
|
|
|
|
read-only access with others, use get_readonly_uri().
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get_readonly_uri():
|
|
|
|
"""Return the directory URI that can be used by others to get
|
|
|
|
read-only access to this directory node. The result is a read-only
|
|
|
|
URI, regardless of whether this dirnode is read-only or read-write.
|
|
|
|
|
|
|
|
If you have merely read-only access to this dirnode,
|
|
|
|
get_readonly_uri() will return the same thing as get_uri().
|
|
|
|
"""
|
|
|
|
|
2007-12-05 06:01:37 +00:00
|
|
|
def get_verifier():
|
|
|
|
"""Return an IVerifierURI instance that represents the
|
|
|
|
'verifiy/refresh capability' for this node. The holder of this
|
|
|
|
capability will be able to renew the lease for this node, protecting
|
|
|
|
it from garbage-collection. They will also be able to ask a server if
|
|
|
|
it holds a share for the file or directory.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def check():
|
|
|
|
"""Perform a file check. See IChecker.check for details."""
|
|
|
|
|
|
|
|
def is_readonly():
|
|
|
|
"""Return True if this reference provides mutable access to the given
|
|
|
|
file or directory (i.e. if you can modify it), or False if not. Note
|
|
|
|
that even if this reference is read-only, someone else may hold a
|
|
|
|
read-write reference to it."""
|
|
|
|
|
|
|
|
def is_mutable():
|
|
|
|
"""Return True if this file or directory is mutable (by *somebody*,
|
|
|
|
not necessarily you), False if it is is immutable. Note that a file
|
|
|
|
might be mutable overall, but your reference to it might be
|
|
|
|
read-only. On the other hand, all references to an immutable file
|
|
|
|
will be read-only; there are no read-write references to an immutable
|
|
|
|
file.
|
|
|
|
"""
|
|
|
|
|
|
|
|
class IMutableFilesystemNode(IFilesystemNode):
|
|
|
|
pass
|
|
|
|
|
2007-12-03 21:52:42 +00:00
|
|
|
class IFileNode(IFilesystemNode):
|
|
|
|
def download(target):
|
|
|
|
"""Download the file's contents to a given IDownloadTarget"""
|
|
|
|
|
|
|
|
def download_to_data():
|
|
|
|
"""Download the file's contents. Return a Deferred that fires
|
|
|
|
with those contents."""
|
|
|
|
|
|
|
|
def get_size():
|
|
|
|
"""Return the length (in bytes) of the data this node represents."""
|
|
|
|
|
|
|
|
class IMutableFileNode(IFileNode, IMutableFilesystemNode):
|
2007-11-01 22:15:29 +00:00
|
|
|
def download_to_data():
|
|
|
|
"""Download the file's contents. Return a Deferred that fires with
|
|
|
|
those contents. If there are multiple retrievable versions in the
|
|
|
|
grid (because you failed to avoid simultaneous writes, see
|
|
|
|
docs/mutable.txt), this will return the first version that it can
|
|
|
|
reconstruct, and will silently ignore the others. In the future, a
|
|
|
|
more advanced API will signal and provide access to the multiple
|
|
|
|
heads."""
|
2007-12-03 21:52:42 +00:00
|
|
|
|
|
|
|
def replace(newdata, wait_for_numpeers=None):
|
2007-11-01 22:15:29 +00:00
|
|
|
"""Replace the old contents with the new data. Returns a Deferred
|
|
|
|
that fires (with None) when the operation is complete.
|
|
|
|
|
|
|
|
If the node detects that there are multiple outstanding versions of
|
|
|
|
the file, this will raise ConsistencyError, and may leave the
|
|
|
|
distributed file in an unusual state (the node will try to ensure
|
|
|
|
that at least one version of the file remains retrievable, but it may
|
|
|
|
or may not be the one you just tried to upload). You should respond
|
|
|
|
to this by downloading the current contents of the file and retrying
|
|
|
|
the replace() operation.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get_writekey():
|
|
|
|
"""Return this filenode's writekey, or None if the node does not have
|
|
|
|
write-capability. This may be used to assist with data structures
|
|
|
|
that need to make certain data available only to writers, such as the
|
|
|
|
read-write child caps in dirnodes. The recommended process is to have
|
|
|
|
reader-visible data be submitted to the filenode in the clear (where
|
|
|
|
it will be encrypted by the filenode using the readkey), but encrypt
|
|
|
|
writer-visible data using this writekey.
|
|
|
|
"""
|
|
|
|
|
2007-12-03 21:52:42 +00:00
|
|
|
class IDirectoryNode(IMutableFilesystemNode):
|
2007-06-25 20:23:51 +00:00
|
|
|
def get_uri():
|
2007-12-03 21:52:42 +00:00
|
|
|
"""
|
2007-06-27 02:41:20 +00:00
|
|
|
The dirnode ('1') URI returned by this method can be used in
|
|
|
|
set_uri() on a different directory ('2') to 'mount' a reference to
|
|
|
|
this directory ('1') under the other ('2'). This URI is just a
|
|
|
|
string, so it can be passed around through email or other out-of-band
|
|
|
|
protocol.
|
2007-06-25 20:23:51 +00:00
|
|
|
"""
|
|
|
|
|
2007-12-03 21:52:42 +00:00
|
|
|
def get_readonly_uri():
|
2007-06-25 20:23:51 +00:00
|
|
|
"""
|
2007-12-03 21:52:42 +00:00
|
|
|
The dirnode ('1') URI returned by this method can be used in
|
|
|
|
set_uri() on a different directory ('2') to 'mount' a reference to
|
|
|
|
this directory ('1') under the other ('2'). This URI is just a
|
|
|
|
string, so it can be passed around through email or other out-of-band
|
|
|
|
protocol.
|
2007-06-27 02:41:20 +00:00
|
|
|
"""
|
|
|
|
|
2007-06-15 07:37:32 +00:00
|
|
|
def list():
|
2007-06-25 20:23:51 +00:00
|
|
|
"""I return a Deferred that fires with a dictionary mapping child
|
2007-12-05 06:01:37 +00:00
|
|
|
name to (node, metadata_dict) tuples, in which 'node' is either an
|
|
|
|
IFileNode or IDirectoryNode, and 'metadata_dict' is a dictionary of
|
|
|
|
metadata."""
|
2007-06-15 07:37:32 +00:00
|
|
|
|
2007-08-15 20:22:01 +00:00
|
|
|
def has_child(name):
|
|
|
|
"""I return a Deferred that fires with a boolean, True if there
|
|
|
|
exists a child of the given name, False if not."""
|
|
|
|
|
2007-06-15 07:37:32 +00:00
|
|
|
def get(name):
|
2007-06-25 20:23:51 +00:00
|
|
|
"""I return a Deferred that fires with a specific named child node,
|
|
|
|
either an IFileNode or an IDirectoryNode."""
|
|
|
|
|
2007-07-07 02:38:37 +00:00
|
|
|
def get_child_at_path(path):
|
|
|
|
"""Transform a child path into an IDirectoryNode or IFileNode.
|
|
|
|
|
|
|
|
I perform a recursive series of 'get' operations to find the named
|
|
|
|
descendant node. I return a Deferred that fires with the node, or
|
|
|
|
errbacks with IndexError if the node could not be found.
|
|
|
|
|
|
|
|
The path can be either a single string (slash-separated) or a list of
|
|
|
|
path-name elements.
|
|
|
|
"""
|
|
|
|
|
2007-06-25 20:23:51 +00:00
|
|
|
def set_uri(name, child_uri):
|
|
|
|
"""I add a child (by URI) at the specific name. I return a Deferred
|
2007-08-17 00:03:19 +00:00
|
|
|
that fires when the operation finishes. I will replace any existing
|
|
|
|
child of the same name.
|
2007-06-25 20:23:51 +00:00
|
|
|
|
|
|
|
The child_uri could be for a file, or for a directory (either
|
|
|
|
read-write or read-only, using a URI that came from get_uri() ).
|
2007-06-15 07:37:32 +00:00
|
|
|
|
2007-06-25 20:23:51 +00:00
|
|
|
If this directory node is read-only, the Deferred will errback with a
|
|
|
|
NotMutableError."""
|
|
|
|
|
2007-12-19 06:30:02 +00:00
|
|
|
def set_uris(entries):
|
|
|
|
"""Add multiple (name, child_uri) pairs to a directory node. Returns
|
|
|
|
a Deferred that fires (with None) when the operation finishes. This
|
|
|
|
is equivalent to calling set_uri() multiple times, but is much more
|
|
|
|
efficient.
|
|
|
|
"""
|
|
|
|
|
2007-06-25 20:23:51 +00:00
|
|
|
def set_node(name, child):
|
2007-06-15 07:37:32 +00:00
|
|
|
"""I add a child at the specific name. I return a Deferred that fires
|
2007-06-25 20:23:51 +00:00
|
|
|
when the operation finishes. This Deferred will fire with the child
|
2007-08-17 00:03:19 +00:00
|
|
|
node that was just added. I will replace any existing child of the
|
|
|
|
same name.
|
2007-06-25 20:23:51 +00:00
|
|
|
|
|
|
|
If this directory node is read-only, the Deferred will errback with a
|
|
|
|
NotMutableError."""
|
2007-06-15 07:37:32 +00:00
|
|
|
|
2007-12-19 06:30:02 +00:00
|
|
|
def set_nodes(entries):
|
|
|
|
"""Add multiple (name, child_node) pairs to a directory node. Returns
|
|
|
|
a Deferred that fires (with None) when the operation finishes. This
|
|
|
|
is equivalent to calling set_node() multiple times, but is much more
|
|
|
|
efficient."""
|
|
|
|
|
|
|
|
|
2007-06-15 07:37:32 +00:00
|
|
|
def add_file(name, uploadable):
|
|
|
|
"""I upload a file (using the given IUploadable), then attach the
|
2007-06-25 20:23:51 +00:00
|
|
|
resulting FileNode to the directory at the given name. I return a
|
|
|
|
Deferred that fires (with the IFileNode of the uploaded file) when
|
|
|
|
the operation completes."""
|
2007-06-15 07:37:32 +00:00
|
|
|
|
2007-06-25 20:23:51 +00:00
|
|
|
def delete(name):
|
2007-06-15 07:37:32 +00:00
|
|
|
"""I remove the child at the specific name. I return a Deferred that
|
|
|
|
fires when the operation finishes."""
|
|
|
|
|
|
|
|
def create_empty_directory(name):
|
|
|
|
"""I create and attach an empty directory at the given name. I return
|
|
|
|
a Deferred that fires when the operation finishes."""
|
|
|
|
|
|
|
|
def move_child_to(current_child_name, new_parent, new_child_name=None):
|
|
|
|
"""I take one of my children and move them to a new parent. The child
|
|
|
|
is referenced by name. On the new parent, the child will live under
|
|
|
|
'new_child_name', which defaults to 'current_child_name'. I return a
|
|
|
|
Deferred that fires when the operation finishes."""
|
2006-12-07 21:58:23 +00:00
|
|
|
|
2007-06-27 02:41:20 +00:00
|
|
|
def build_manifest():
|
2007-10-15 23:16:39 +00:00
|
|
|
"""Return a frozenset of verifier-capability strings for all nodes
|
|
|
|
(directories and files) reachable from this one."""
|
2007-01-05 04:52:51 +00:00
|
|
|
|
2007-01-12 03:57:14 +00:00
|
|
|
class ICodecEncoder(Interface):
|
2007-01-16 04:22:22 +00:00
|
|
|
def set_params(data_size, required_shares, max_shares):
|
2007-01-05 04:52:51 +00:00
|
|
|
"""Set up the parameters of this encoder.
|
|
|
|
|
2007-03-28 02:05:09 +00:00
|
|
|
This prepares the encoder to perform an operation that converts a
|
|
|
|
single block of data into a number of shares, such that a future
|
|
|
|
ICodecDecoder can use a subset of these shares to recover the
|
|
|
|
original data. This operation is invoked by calling encode(). Once
|
|
|
|
the encoding parameters are set up, the encode operation can be
|
|
|
|
invoked multiple times.
|
|
|
|
|
|
|
|
set_params() prepares the encoder to accept blocks of input data that
|
|
|
|
are exactly 'data_size' bytes in length. The encoder will be prepared
|
|
|
|
to produce 'max_shares' shares for each encode() operation (although
|
|
|
|
see the 'desired_share_ids' to use less CPU). The encoding math will
|
|
|
|
be chosen such that the decoder can get by with as few as
|
|
|
|
'required_shares' of these shares and still reproduce the original
|
|
|
|
data. For example, set_params(1000, 5, 5) offers no redundancy at
|
|
|
|
all, whereas set_params(1000, 1, 10) provides 10x redundancy.
|
|
|
|
|
2007-03-28 03:14:45 +00:00
|
|
|
Numerical Restrictions: 'data_size' is required to be an integral
|
|
|
|
multiple of 'required_shares'. In general, the caller should choose
|
|
|
|
required_shares and max_shares based upon their reliability
|
|
|
|
requirements and the number of peers available (the total storage
|
|
|
|
space used is roughly equal to max_shares*data_size/required_shares),
|
|
|
|
then choose data_size to achieve the memory footprint desired (larger
|
|
|
|
data_size means more efficient operation, smaller data_size means
|
|
|
|
smaller memory footprint).
|
|
|
|
|
|
|
|
In addition, 'max_shares' must be equal to or greater than
|
|
|
|
'required_shares'. Of course, setting them to be equal causes
|
|
|
|
encode() to degenerate into a particularly slow form of the 'split'
|
|
|
|
utility.
|
|
|
|
|
2007-03-28 02:05:09 +00:00
|
|
|
See encode() for more details about how these parameters are used.
|
2007-03-28 03:14:45 +00:00
|
|
|
|
2007-03-28 02:05:09 +00:00
|
|
|
set_params() must be called before any other ICodecEncoder methods
|
|
|
|
may be invoked.
|
2007-01-05 04:52:51 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def get_encoder_type():
|
2007-01-17 04:29:59 +00:00
|
|
|
"""Return a short string that describes the type of this encoder.
|
2007-01-05 04:52:51 +00:00
|
|
|
|
2007-01-24 22:34:02 +00:00
|
|
|
There is required to be a global table of encoder classes. This method
|
|
|
|
returns an index into this table; the value at this index is an
|
|
|
|
encoder class, and this encoder is an instance of that class.
|
2007-01-05 04:52:51 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def get_serialized_params(): # TODO: maybe, maybe not
|
|
|
|
"""Return a string that describes the parameters of this encoder.
|
|
|
|
|
|
|
|
This string can be passed to the decoder to prepare it for handling
|
|
|
|
the encoded shares we create. It might contain more information than
|
|
|
|
was presented to set_params(), if there is some flexibility of
|
|
|
|
parameter choice.
|
|
|
|
|
|
|
|
This string is intended to be embedded in the URI, so there are
|
|
|
|
several restrictions on its contents. At the moment I'm thinking that
|
2007-01-17 04:29:59 +00:00
|
|
|
this means it may contain hex digits and hyphens, and nothing else.
|
|
|
|
The idea is that the URI contains something like '%s:%s:%s' %
|
|
|
|
(encoder.get_encoder_name(), encoder.get_serialized_params(),
|
2007-06-10 03:46:04 +00:00
|
|
|
b2a(crypttext_hash)), and this is enough information to construct a
|
2007-01-17 04:29:59 +00:00
|
|
|
compatible decoder.
|
2007-01-05 04:52:51 +00:00
|
|
|
"""
|
|
|
|
|
2007-03-30 17:52:19 +00:00
|
|
|
def get_block_size():
|
|
|
|
"""Return the length of the shares that encode() will produce.
|
|
|
|
"""
|
|
|
|
|
2007-03-28 03:14:45 +00:00
|
|
|
def encode_proposal(data, desired_share_ids=None):
|
|
|
|
"""Encode some data.
|
|
|
|
|
|
|
|
'data' must be a string (or other buffer object), and len(data) must
|
|
|
|
be equal to the 'data_size' value passed earlier to set_params().
|
|
|
|
|
|
|
|
This will return a Deferred that will fire with two lists. The first
|
|
|
|
is a list of shares, each of which is a string (or other buffer
|
|
|
|
object) such that len(share) is the same as what get_share_size()
|
|
|
|
returned earlier. The second is a list of shareids, in which each is
|
|
|
|
an integer. The lengths of the two lists will always be equal to each
|
|
|
|
other. The user should take care to keep each share closely
|
|
|
|
associated with its shareid, as one is useless without the other.
|
|
|
|
|
|
|
|
The length of this output list will normally be the same as the value
|
|
|
|
provided to the 'max_shares' parameter of set_params(). This may be
|
|
|
|
different if 'desired_share_ids' is provided.
|
|
|
|
|
|
|
|
'desired_share_ids', if provided, is required to be a sequence of
|
|
|
|
ints, each of which is required to be >= 0 and < max_shares. If not
|
|
|
|
provided, encode() will produce 'max_shares' shares, as if
|
|
|
|
'desired_share_ids' were set to range(max_shares). You might use this
|
|
|
|
if you initially thought you were going to use 10 peers, started
|
|
|
|
encoding, and then two of the peers dropped out: you could use
|
|
|
|
desired_share_ids= to skip the work (both memory and CPU) of
|
|
|
|
producing shares for the peers which are no longer available.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2007-01-24 22:34:02 +00:00
|
|
|
def encode(inshares, desired_share_ids=None):
|
2007-11-01 22:33:47 +00:00
|
|
|
"""Encode some data. This may be called multiple times. Each call is
|
2007-02-01 23:07:00 +00:00
|
|
|
independent.
|
2007-01-05 04:52:51 +00:00
|
|
|
|
2007-03-06 03:57:38 +00:00
|
|
|
inshares is a sequence of length required_shares, containing buffers
|
|
|
|
(i.e. strings), where each buffer contains the next contiguous
|
|
|
|
non-overlapping segment of the input data. Each buffer is required to
|
|
|
|
be the same length, and the sum of the lengths of the buffers is
|
|
|
|
required to be exactly the data_size promised by set_params(). (This
|
|
|
|
implies that the data has to be padded before being passed to
|
|
|
|
encode(), unless of course it already happens to be an even multiple
|
|
|
|
of required_shares in length.)
|
|
|
|
|
|
|
|
ALSO: the requirement to break up your data into 'required_shares'
|
|
|
|
chunks before calling encode() feels a bit surprising, at least from
|
|
|
|
the point of view of a user who doesn't know how FEC works. It feels
|
|
|
|
like an implementation detail that has leaked outside the
|
|
|
|
abstraction barrier. Can you imagine a use case in which the data to
|
|
|
|
be encoded might already be available in pre-segmented chunks, such
|
|
|
|
that it is faster or less work to make encode() take a list rather
|
|
|
|
than splitting a single string?
|
|
|
|
|
|
|
|
ALSO ALSO: I think 'inshares' is a misleading term, since encode()
|
|
|
|
is supposed to *produce* shares, so what it *accepts* should be
|
|
|
|
something other than shares. Other places in this interface use the
|
|
|
|
word 'data' for that-which-is-not-shares.. maybe we should use that
|
|
|
|
term?
|
|
|
|
|
|
|
|
'desired_share_ids', if provided, is required to be a sequence of
|
|
|
|
ints, each of which is required to be >= 0 and < max_shares. If not
|
|
|
|
provided, encode() will produce 'max_shares' shares, as if
|
2007-03-28 02:05:09 +00:00
|
|
|
'desired_share_ids' were set to range(max_shares). You might use this
|
|
|
|
if you initially thought you were going to use 10 peers, started
|
|
|
|
encoding, and then two of the peers dropped out: you could use
|
|
|
|
desired_share_ids= to skip the work (both memory and CPU) of
|
|
|
|
producing shares for the peers which are no longer available.
|
2007-01-24 22:34:02 +00:00
|
|
|
|
|
|
|
For each call, encode() will return a Deferred that fires with two
|
2007-02-01 23:07:00 +00:00
|
|
|
lists, one containing shares and the other containing the shareids.
|
2007-03-28 19:52:44 +00:00
|
|
|
The get_share_size() method can be used to determine the length of
|
|
|
|
the share strings returned by encode(). Each shareid is a small
|
|
|
|
integer, exactly as passed into 'desired_share_ids' (or
|
|
|
|
range(max_shares), if desired_share_ids was not provided).
|
|
|
|
|
2007-11-01 22:33:47 +00:00
|
|
|
The shares and their corresponding shareids are required to be kept
|
|
|
|
together during storage and retrieval. Specifically, the share data is
|
|
|
|
useless by itself: the decoder needs to be told which share is which
|
2007-02-01 23:07:00 +00:00
|
|
|
by providing it with both the shareid and the actual share data.
|
2007-01-05 04:52:51 +00:00
|
|
|
|
2007-03-28 19:52:44 +00:00
|
|
|
This function will allocate an amount of memory roughly equal to::
|
|
|
|
|
|
|
|
(max_shares - required_shares) * get_share_size()
|
2007-02-01 23:07:00 +00:00
|
|
|
|
2007-03-28 19:52:44 +00:00
|
|
|
When combined with the memory that the caller must allocate to
|
|
|
|
provide the input data, this leads to a memory footprint roughly
|
|
|
|
equal to the size of the resulting encoded shares (i.e. the expansion
|
|
|
|
factor times the size of the input segment).
|
2007-01-05 04:52:51 +00:00
|
|
|
"""
|
|
|
|
|
2007-03-28 03:14:45 +00:00
|
|
|
# rejected ideas:
|
|
|
|
#
|
|
|
|
# returning a list of (shareidN,shareN) tuples instead of a pair of
|
|
|
|
# lists (shareids..,shares..). Brian thought the tuples would
|
|
|
|
# encourage users to keep the share and shareid together throughout
|
|
|
|
# later processing, Zooko pointed out that the code to iterate
|
|
|
|
# through two lists is not really more complicated than using a list
|
|
|
|
# of tuples and there's also a performance improvement
|
|
|
|
#
|
|
|
|
# having 'data_size' not required to be an integral multiple of
|
|
|
|
# 'required_shares'. Doing this would require encode() to perform
|
|
|
|
# padding internally, and we'd prefer to have any padding be done
|
|
|
|
# explicitly by the caller. Yes, it is an abstraction leak, but
|
|
|
|
# hopefully not an onerous one.
|
|
|
|
|
|
|
|
|
2007-01-12 03:57:14 +00:00
|
|
|
class ICodecDecoder(Interface):
|
2007-01-05 04:52:51 +00:00
|
|
|
def set_serialized_params(params):
|
|
|
|
"""Set up the parameters of this encoder, from a string returned by
|
|
|
|
encoder.get_serialized_params()."""
|
|
|
|
|
2007-03-30 17:52:19 +00:00
|
|
|
def get_needed_shares():
|
2007-01-16 04:22:22 +00:00
|
|
|
"""Return the number of shares needed to reconstruct the data.
|
2007-01-24 22:34:02 +00:00
|
|
|
set_serialized_params() is required to be called before this."""
|
2007-01-16 04:22:22 +00:00
|
|
|
|
2007-01-24 22:34:02 +00:00
|
|
|
def decode(some_shares, their_shareids):
|
2007-01-05 04:52:51 +00:00
|
|
|
"""Decode a partial list of shares into data.
|
|
|
|
|
2007-02-01 23:07:00 +00:00
|
|
|
'some_shares' is required to be a sequence of buffers of sharedata, a
|
2007-01-24 22:34:02 +00:00
|
|
|
subset of the shares returned by ICodecEncode.encode(). Each share is
|
|
|
|
required to be of the same length. The i'th element of their_shareids
|
2007-02-01 23:07:00 +00:00
|
|
|
is required to be the shareid of the i'th buffer in some_shares.
|
2007-01-24 22:34:02 +00:00
|
|
|
|
|
|
|
This returns a Deferred which fires with a sequence of buffers. This
|
|
|
|
sequence will contain all of the segments of the original data, in
|
2007-03-28 02:05:09 +00:00
|
|
|
order. The sum of the lengths of all of the buffers will be the
|
2007-01-24 22:34:02 +00:00
|
|
|
'data_size' value passed into the original ICodecEncode.set_params()
|
2007-03-28 02:05:09 +00:00
|
|
|
call. To get back the single original input block of data, use
|
|
|
|
''.join(output_buffers), or you may wish to simply write them in
|
|
|
|
order to an output file.
|
|
|
|
|
|
|
|
Note that some of the elements in the result sequence may be
|
|
|
|
references to the elements of the some_shares input sequence. In
|
|
|
|
particular, this means that if those share objects are mutable (e.g.
|
|
|
|
arrays) and if they are changed, then both the input (the
|
|
|
|
'some_shares' parameter) and the output (the value given when the
|
|
|
|
deferred is triggered) will change.
|
2007-01-24 22:34:02 +00:00
|
|
|
|
|
|
|
The length of 'some_shares' is required to be exactly the value of
|
|
|
|
'required_shares' passed into the original ICodecEncode.set_params()
|
|
|
|
call.
|
2007-01-05 04:52:51 +00:00
|
|
|
"""
|
2007-01-21 22:01:34 +00:00
|
|
|
|
2007-03-28 18:24:53 +00:00
|
|
|
class IEncoder(Interface):
|
2007-07-24 02:31:53 +00:00
|
|
|
"""I take an object that provides IEncryptedUploadable, which provides
|
|
|
|
encrypted data, and a list of shareholders. I then encode, hash, and
|
|
|
|
deliver shares to those shareholders. I will compute all the necessary
|
|
|
|
Merkle hash trees that are necessary to validate the crypttext that
|
|
|
|
eventually comes back from the shareholders. I provide the URI Extension
|
|
|
|
Block Hash, and the encoding parameters, both of which must be included
|
|
|
|
in the URI.
|
2007-03-28 18:24:53 +00:00
|
|
|
|
|
|
|
I do not choose shareholders, that is left to the IUploader. I must be
|
|
|
|
given a dict of RemoteReferences to storage buckets that are ready and
|
|
|
|
willing to receive data.
|
|
|
|
"""
|
|
|
|
|
2007-07-24 02:31:53 +00:00
|
|
|
def set_size(size):
|
|
|
|
"""Specify the number of bytes that will be encoded. This must be
|
|
|
|
peformed before get_serialized_params() can be called.
|
2007-03-28 18:24:53 +00:00
|
|
|
"""
|
2007-07-24 02:31:53 +00:00
|
|
|
def set_params(params):
|
|
|
|
"""Override the default encoding parameters. 'params' is a tuple of
|
|
|
|
(k,d,n), where 'k' is the number of required shares, 'd' is the
|
|
|
|
shares_of_happiness, and 'n' is the total number of shares that will
|
|
|
|
be created.
|
2007-03-28 18:24:53 +00:00
|
|
|
|
2007-07-24 02:31:53 +00:00
|
|
|
Encoding parameters can be set in three ways. 1: The Encoder class
|
2007-10-16 02:53:59 +00:00
|
|
|
provides defaults (3/7/10). 2: the Encoder can be constructed with
|
2007-07-24 02:31:53 +00:00
|
|
|
an 'options' dictionary, in which the
|
|
|
|
needed_and_happy_and_total_shares' key can be a (k,d,n) tuple. 3:
|
|
|
|
set_params((k,d,n)) can be called.
|
2007-03-30 18:53:03 +00:00
|
|
|
|
2007-07-24 02:31:53 +00:00
|
|
|
If you intend to use set_params(), you must call it before
|
|
|
|
get_share_size or get_param are called.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def set_encrypted_uploadable(u):
|
|
|
|
"""Provide a source of encrypted upload data. 'u' must implement
|
|
|
|
IEncryptedUploadable.
|
|
|
|
|
|
|
|
When this is called, the IEncryptedUploadable will be queried for its
|
|
|
|
length and the storage_index that should be used.
|
2007-03-30 18:53:03 +00:00
|
|
|
|
2007-07-24 02:31:53 +00:00
|
|
|
This returns a Deferred that fires with this Encoder instance.
|
|
|
|
|
|
|
|
This must be performed before start() can be called.
|
2007-03-30 18:53:03 +00:00
|
|
|
"""
|
|
|
|
|
2007-07-24 02:31:53 +00:00
|
|
|
def get_param(name):
|
|
|
|
"""Return an encoding parameter, by name.
|
|
|
|
|
|
|
|
'storage_index': return a string with the (16-byte truncated SHA-256
|
|
|
|
hash) storage index to which these shares should be
|
|
|
|
pushed.
|
|
|
|
|
|
|
|
'share_counts': return a tuple describing how many shares are used:
|
|
|
|
(needed_shares, shares_of_happiness, total_shares)
|
|
|
|
|
|
|
|
'num_segments': return an int with the number of segments that
|
|
|
|
will be encoded.
|
|
|
|
|
|
|
|
'segment_size': return an int with the size of each segment.
|
|
|
|
|
|
|
|
'block_size': return the size of the individual blocks that will
|
|
|
|
be delivered to a shareholder's put_block() method. By
|
|
|
|
knowing this, the shareholder will be able to keep all
|
|
|
|
blocks in a single file and still provide random access
|
|
|
|
when reading them. # TODO: can we avoid exposing this?
|
|
|
|
|
|
|
|
'share_size': an int with the size of the data that will be stored
|
|
|
|
on each shareholder. This is aggregate amount of data
|
|
|
|
that will be sent to the shareholder, summed over all
|
|
|
|
the put_block() calls I will ever make. It is useful to
|
|
|
|
determine this size before asking potential
|
|
|
|
shareholders whether they will grant a lease or not,
|
|
|
|
since their answers will depend upon how much space we
|
|
|
|
need. TODO: this might also include some amount of
|
|
|
|
overhead, like the size of all the hashes. We need to
|
|
|
|
decide whether this is useful or not.
|
|
|
|
|
|
|
|
'serialized_params': a string with a concise description of the
|
|
|
|
codec name and its parameters. This may be passed
|
|
|
|
into the IUploadable to let it make sure that
|
|
|
|
the same file encoded with different parameters
|
|
|
|
will result in different storage indexes.
|
|
|
|
|
|
|
|
Once this is called, set_size() and set_params() may not be called.
|
2007-03-28 18:24:53 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def set_shareholders(shareholders):
|
2007-07-24 02:31:53 +00:00
|
|
|
"""Tell the encoder where to put the encoded shares. 'shareholders'
|
|
|
|
must be a dictionary that maps share number (an integer ranging from
|
|
|
|
0 to n-1) to an instance that provides IStorageBucketWriter. This
|
|
|
|
must be performed before start() can be called."""
|
2007-03-28 18:24:53 +00:00
|
|
|
|
|
|
|
def start():
|
2007-07-24 02:31:53 +00:00
|
|
|
"""Begin the encode/upload process. This involves reading encrypted
|
|
|
|
data from the IEncryptedUploadable, encoding it, uploading the shares
|
2007-03-28 18:24:53 +00:00
|
|
|
to the shareholders, then sending the hash trees.
|
|
|
|
|
2007-07-24 02:31:53 +00:00
|
|
|
set_encrypted_uploadable() and set_shareholders() must be called
|
|
|
|
before this can be invoked.
|
|
|
|
|
|
|
|
This returns a Deferred that fires with a tuple of
|
|
|
|
(uri_extension_hash, needed_shares, total_shares, size) when the
|
|
|
|
upload process is complete. This information, plus the encryption
|
|
|
|
key, is sufficient to construct the URI.
|
2007-03-28 18:24:53 +00:00
|
|
|
"""
|
|
|
|
|
2007-03-28 18:31:31 +00:00
|
|
|
class IDecoder(Interface):
|
|
|
|
"""I take a list of shareholders and some setup information, then
|
|
|
|
download, validate, decode, and decrypt data from them, writing the
|
|
|
|
results to an output file.
|
|
|
|
|
|
|
|
I do not locate the shareholders, that is left to the IDownloader. I must
|
|
|
|
be given a dict of RemoteReferences to storage buckets that are ready to
|
|
|
|
send data.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def setup(outfile):
|
|
|
|
"""I take a file-like object (providing write and close) to which all
|
|
|
|
the plaintext data will be written.
|
|
|
|
|
|
|
|
TODO: producer/consumer . Maybe write() should return a Deferred that
|
|
|
|
indicates when it will accept more data? But probably having the
|
|
|
|
IDecoder be a producer is easier to glue to IConsumer pieces.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def set_shareholders(shareholders):
|
|
|
|
"""I take a dictionary that maps share identifiers (small integers)
|
|
|
|
to RemoteReferences that provide RIBucketReader. This must be called
|
|
|
|
before start()."""
|
|
|
|
|
|
|
|
def start():
|
|
|
|
"""I start the download. This process involves retrieving data and
|
|
|
|
hash chains from the shareholders, using the hashes to validate the
|
|
|
|
data, decoding the shares into segments, decrypting the segments,
|
|
|
|
then writing the resulting plaintext to the output file.
|
|
|
|
|
|
|
|
I return a Deferred that will fire (with self) when the download is
|
|
|
|
complete.
|
|
|
|
"""
|
2007-03-28 18:24:53 +00:00
|
|
|
|
2007-01-21 22:01:34 +00:00
|
|
|
class IDownloadTarget(Interface):
|
2007-07-03 22:09:00 +00:00
|
|
|
def open(size):
|
2007-07-03 20:47:37 +00:00
|
|
|
"""Called before any calls to write() or close(). If an error
|
|
|
|
occurs before any data is available, fail() may be called without
|
2007-07-03 22:09:00 +00:00
|
|
|
a previous call to open().
|
|
|
|
|
|
|
|
'size' is the length of the file being downloaded, in bytes."""
|
|
|
|
|
2007-01-21 22:01:34 +00:00
|
|
|
def write(data):
|
|
|
|
"""Output some data to the target."""
|
|
|
|
def close():
|
|
|
|
"""Inform the target that there is no more data to be written."""
|
2007-07-03 20:18:14 +00:00
|
|
|
def fail(why):
|
|
|
|
"""fail() is called to indicate that the download has failed. 'why'
|
|
|
|
is a Failure object indicating what went wrong. No further methods
|
|
|
|
will be invoked on the IDownloadTarget after fail()."""
|
2007-01-21 22:01:34 +00:00
|
|
|
def register_canceller(cb):
|
|
|
|
"""The FileDownloader uses this to register a no-argument function
|
|
|
|
that the target can call to cancel the download. Once this canceller
|
|
|
|
is invoked, no further calls to write() or close() will be made."""
|
2007-07-07 02:39:47 +00:00
|
|
|
def finish():
|
2007-01-21 22:01:34 +00:00
|
|
|
"""When the FileDownloader is done, this finish() function will be
|
|
|
|
called. Whatever it returns will be returned to the invoker of
|
|
|
|
Downloader.download.
|
|
|
|
"""
|
|
|
|
|
|
|
|
class IDownloader(Interface):
|
|
|
|
def download(uri, target):
|
|
|
|
"""Perform a CHK download, sending the data to the given target.
|
2007-07-03 20:18:14 +00:00
|
|
|
'target' must provide IDownloadTarget.
|
|
|
|
|
|
|
|
Returns a Deferred that fires (with the results of target.finish)
|
|
|
|
when the download is finished, or errbacks if something went wrong."""
|
2007-01-21 22:01:34 +00:00
|
|
|
|
2007-07-24 02:31:53 +00:00
|
|
|
class IEncryptedUploadable(Interface):
|
|
|
|
def get_size():
|
|
|
|
"""This behaves just like IUploadable.get_size()."""
|
|
|
|
|
|
|
|
def set_serialized_encoding_parameters(serialized_encoding_parameters):
|
|
|
|
"""Tell me what encoding parameters will be used for my data.
|
|
|
|
|
|
|
|
'serialized_encoding_parameters' is a string which indicates how the
|
|
|
|
data will be encoded (codec name, blocksize, number of shares).
|
|
|
|
|
|
|
|
I may use this when get_storage_index() is called, to influence the
|
|
|
|
index that I return. Or, I may just ignore it.
|
|
|
|
|
|
|
|
set_serialized_encoding_parameters() may be called 0 or 1 times. If
|
|
|
|
called, it must be called before get_storage_index().
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get_storage_index():
|
|
|
|
"""Return a Deferred that fires with a 16-byte storage index. This
|
|
|
|
value may be influenced by the parameters earlier set by
|
|
|
|
set_serialized_encoding_parameters().
|
|
|
|
"""
|
|
|
|
|
|
|
|
def set_segment_size(segment_size):
|
|
|
|
"""Set the segment size, to allow the IEncryptedUploadable to
|
|
|
|
accurately create the plaintext segment hash tree. This must be
|
|
|
|
called before any calls to read_encrypted."""
|
|
|
|
|
|
|
|
def read_encrypted(length):
|
|
|
|
"""This behaves just like IUploadable.read(), but returns crypttext
|
|
|
|
instead of plaintext. set_segment_size() must be called before the
|
|
|
|
first call to read_encrypted()."""
|
|
|
|
|
|
|
|
def get_plaintext_segment_hashtree_nodes(num_segments):
|
|
|
|
"""Get the nodes of a merkle hash tree over the plaintext segments.
|
|
|
|
|
|
|
|
This returns a Deferred which fires with a sequence of hashes. Each
|
|
|
|
hash is a node of a merkle hash tree, generally obtained from::
|
|
|
|
|
|
|
|
tuple(HashTree(segment_hashes))
|
|
|
|
|
|
|
|
'num_segments' is used to assert that the number of segments that the
|
|
|
|
IEncryptedUploadable handled matches the number of segments that the
|
|
|
|
encoder was expecting.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get_plaintext_hash():
|
|
|
|
"""Get the hash of the whole plaintext.
|
|
|
|
|
|
|
|
This returns a Deferred which fires with a tagged SHA-256 hash of the
|
|
|
|
whole plaintext, obtained from hashutil.plaintext_hash(data).
|
|
|
|
"""
|
|
|
|
|
|
|
|
def close():
|
|
|
|
"""Just like IUploadable.close()."""
|
|
|
|
|
2007-01-21 22:01:34 +00:00
|
|
|
class IUploadable(Interface):
|
2007-07-20 01:21:44 +00:00
|
|
|
def get_size():
|
|
|
|
"""Return a Deferred that will fire with the length of the data to be
|
|
|
|
uploaded, in bytes. This will be called before the data is actually
|
|
|
|
used, to compute encoding parameters.
|
|
|
|
"""
|
|
|
|
|
2007-07-24 02:31:53 +00:00
|
|
|
def set_serialized_encoding_parameters(serialized_encoding_parameters):
|
|
|
|
"""Tell me what encoding parameters will be used for my data.
|
|
|
|
|
|
|
|
'serialized_encoding_parameters' is a string which indicates how the
|
|
|
|
data will be encoded (codec name, blocksize, number of shares).
|
|
|
|
|
|
|
|
I may use this when get_encryption_key() is called, to influence the
|
|
|
|
key that I return. Or, I may just ignore it.
|
|
|
|
|
|
|
|
set_serialized_encoding_parameters() may be called 0 or 1 times. If
|
|
|
|
called, it must be called before get_encryption_key().
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get_encryption_key():
|
2007-07-20 01:21:44 +00:00
|
|
|
"""Return a Deferred that fires with a 16-byte AES key. This key will
|
|
|
|
be used to encrypt the data. The key will also be hashed to derive
|
2007-07-24 02:31:53 +00:00
|
|
|
the StorageIndex.
|
2007-07-20 01:21:44 +00:00
|
|
|
|
|
|
|
Uploadables which want to achieve convergence should hash their file
|
2007-07-24 02:31:53 +00:00
|
|
|
contents and the serialized_encoding_parameters to form the key
|
|
|
|
(which of course requires a full pass over the data). Uploadables can
|
|
|
|
use the upload.ConvergentUploadMixin class to achieve this
|
|
|
|
automatically.
|
2007-07-20 01:21:44 +00:00
|
|
|
|
|
|
|
Uploadables which do not care about convergence (or do not wish to
|
|
|
|
make multiple passes over the data) can simply return a
|
|
|
|
strongly-random 16 byte string.
|
2007-07-24 02:31:53 +00:00
|
|
|
|
|
|
|
get_encryption_key() may be called multiple times: the IUploadable is
|
|
|
|
required to return the same value each time.
|
2007-07-20 01:21:44 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def read(length):
|
|
|
|
"""Return a Deferred that fires with a list of strings (perhaps with
|
|
|
|
only a single element) which, when concatenated together, contain the
|
|
|
|
next 'length' bytes of data. If EOF is near, this may provide fewer
|
|
|
|
than 'length' bytes. The total number of bytes provided by read()
|
|
|
|
before it signals EOF must equal the size provided by get_size().
|
|
|
|
|
|
|
|
If the data must be acquired through multiple internal read
|
|
|
|
operations, returning a list instead of a single string may help to
|
|
|
|
reduce string copies.
|
|
|
|
|
|
|
|
'length' will typically be equal to (min(get_size(),1MB)/req_shares),
|
|
|
|
so a 10kB file means length=3kB, 100kB file means length=30kB,
|
|
|
|
and >=1MB file means length=300kB.
|
|
|
|
|
|
|
|
This method provides for a single full pass through the data. Later
|
|
|
|
use cases may desire multiple passes or access to only parts of the
|
|
|
|
data (such as a mutable file making small edits-in-place). This API
|
|
|
|
will be expanded once those use cases are better understood.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def close():
|
|
|
|
"""The upload is finished, and whatever filehandle was in use may be
|
|
|
|
closed."""
|
2007-01-21 22:01:34 +00:00
|
|
|
|
|
|
|
class IUploader(Interface):
|
2007-12-03 21:52:42 +00:00
|
|
|
def upload(uploadable, wait_for_numpeers=None):
|
2007-01-21 22:01:34 +00:00
|
|
|
"""Upload the file. 'uploadable' must impement IUploadable. This
|
|
|
|
returns a Deferred which fires with the URI of the file."""
|
|
|
|
|
|
|
|
def upload_ssk(write_capability, new_version, uploadable):
|
2007-04-18 22:46:37 +00:00
|
|
|
"""TODO: how should this work?"""
|
2007-01-24 22:10:53 +00:00
|
|
|
def upload_data(data):
|
|
|
|
"""Like upload(), but accepts a string."""
|
|
|
|
|
|
|
|
def upload_filename(filename):
|
|
|
|
"""Like upload(), but accepts an absolute pathname."""
|
|
|
|
|
|
|
|
def upload_filehandle(filehane):
|
|
|
|
"""Like upload(), but accepts an open filehandle."""
|
|
|
|
|
2007-10-23 00:46:24 +00:00
|
|
|
class IChecker(Interface):
|
|
|
|
def check(uri_to_check):
|
|
|
|
"""Accepts an IVerifierURI, and checks upon the health of its target.
|
|
|
|
|
|
|
|
For now, uri_to_check must be an IVerifierURI. In the future we
|
|
|
|
expect to relax that to be anything that can be adapted to
|
|
|
|
IVerifierURI (like read-only or read-write dirnode/filenode URIs).
|
|
|
|
|
|
|
|
This returns a Deferred. For dirnodes, this fires with either True or
|
|
|
|
False (dirnodes are not distributed, so their health is a boolean).
|
|
|
|
|
|
|
|
For filenodes, this fires with a tuple of (needed_shares,
|
|
|
|
total_shares, found_shares, sharemap). The first three are ints. The
|
|
|
|
basic health of the file is found_shares / needed_shares: if less
|
|
|
|
than 1.0, the file is unrecoverable.
|
|
|
|
|
|
|
|
The sharemap has a key for each sharenum. The value is a list of
|
|
|
|
(binary) nodeids who hold that share. If two shares are kept on the
|
|
|
|
same nodeid, they will fail as a pair, and overall reliability is
|
|
|
|
decreased.
|
|
|
|
|
|
|
|
The IChecker instance remembers the results of the check. By default,
|
|
|
|
these results are stashed in RAM (and are forgotten at shutdown). If
|
|
|
|
a file named 'checker_results.db' exists in the node's basedir, it is
|
|
|
|
used as a sqlite database of results, making them persistent across
|
|
|
|
runs. To start using this feature, just 'touch checker_results.db',
|
|
|
|
and the node will initialize it properly the next time it is started.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def verify(uri_to_check):
|
|
|
|
"""Accepts an IVerifierURI, and verifies the crypttext of the target.
|
|
|
|
|
|
|
|
This is a more-intensive form of checking. For verification, the
|
|
|
|
file's crypttext contents are retrieved, and the associated hash
|
|
|
|
checks are performed. If a storage server is holding a corrupted
|
|
|
|
share, verification will detect the problem, but checking will not.
|
|
|
|
This returns a Deferred that fires with True if the crypttext hashes
|
|
|
|
look good, and will probably raise an exception if anything goes
|
|
|
|
wrong.
|
|
|
|
|
|
|
|
For dirnodes, 'verify' is the same as 'check', so the Deferred will
|
|
|
|
fire with True or False.
|
|
|
|
|
|
|
|
Verification currently only uses a minimal subset of peers, so a lot
|
|
|
|
of share corruption will not be caught by it. We expect to improve
|
|
|
|
this in the future.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def checker_results_for(uri_to_check):
|
2007-10-23 01:10:46 +00:00
|
|
|
"""Accepts an IVerifierURI, and returns a list of previously recorded
|
|
|
|
checker results. This method performs no checking itself: it merely
|
|
|
|
reports the results of checks that have taken place in the past.
|
2007-10-23 00:46:24 +00:00
|
|
|
|
|
|
|
Each element of the list is a two-entry tuple: (when, results).
|
|
|
|
The 'when' values are timestamps (float seconds since epoch), and the
|
|
|
|
results are as defined in the check() method.
|
|
|
|
|
|
|
|
Note: at the moment, this is specified to return synchronously. We
|
|
|
|
might need to back away from this in the future.
|
|
|
|
"""
|
|
|
|
|
2007-12-04 21:32:04 +00:00
|
|
|
class IClient(Interface):
|
2007-12-07 01:36:58 +00:00
|
|
|
def upload(uploadable, wait_for_numpeers=None):
|
2007-12-04 21:32:04 +00:00
|
|
|
"""Upload some data into a CHK, get back the URI string for it.
|
|
|
|
@param uploadable: something that implements IUploadable
|
|
|
|
@param wait_for_numpeers: don't upload anything until we have at least
|
|
|
|
this many peers connected
|
|
|
|
@return: a Deferred that fires with the (string) URI for this file.
|
|
|
|
"""
|
2007-12-07 01:36:58 +00:00
|
|
|
|
|
|
|
def create_mutable_file(contents="", wait_for_numpeers=None):
|
|
|
|
"""Create a new mutable file with contents, get back the URI string.
|
|
|
|
@param contents: the initial contents to place in the file.
|
|
|
|
@param wait_for_numpeers: don't upload anything until we have at least
|
|
|
|
this many peers connected
|
|
|
|
@return: a Deferred that fires with tne (string) SSK URI for the new
|
|
|
|
file.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def create_empty_dirnode(wait_for_numpeers=None):
|
2007-12-04 21:32:04 +00:00
|
|
|
"""Create a new dirnode, empty and unattached.
|
|
|
|
@param wait_for_numpeers: don't create anything until we have at least
|
|
|
|
this many peers connected.
|
|
|
|
@return: a Deferred that fires with the new IDirectoryNode instance.
|
|
|
|
"""
|
2007-12-07 01:36:58 +00:00
|
|
|
|
2007-12-04 21:32:04 +00:00
|
|
|
def create_node_from_uri(uri):
|
|
|
|
"""Create a new IFilesystemNode instance from the uri, synchronously.
|
|
|
|
@param uri: a string or IURI-providing instance. This could be for a
|
|
|
|
LiteralFileNode, a CHK file node, a mutable file node, or
|
|
|
|
a directory node
|
|
|
|
@return: an instance that provides IFilesystemNode (or more usefully one
|
|
|
|
of its subclasses). File-specifying URIs will result in
|
|
|
|
IFileNode or IMutableFileNode -providing instances, like
|
|
|
|
FileNode, LiteralFileNode, or MutableFileNode.
|
|
|
|
Directory-specifying URIs will result in
|
|
|
|
IDirectoryNode-providing instances, like NewDirectoryNode.
|
|
|
|
"""
|
|
|
|
|
2007-10-23 00:46:24 +00:00
|
|
|
|
2007-01-21 22:15:31 +00:00
|
|
|
class NotCapableError(Exception):
|
|
|
|
"""You have tried to write to a read-only node."""
|
2007-03-08 02:16:06 +00:00
|
|
|
|
2007-10-31 02:47:36 +00:00
|
|
|
class BadWriteEnablerError(Exception):
|
|
|
|
pass
|
|
|
|
|
2007-03-08 02:16:06 +00:00
|
|
|
class RIControlClient(RemoteInterface):
|
2007-05-30 00:39:39 +00:00
|
|
|
|
|
|
|
def wait_for_client_connections(num_clients=int):
|
|
|
|
"""Do not return until we have connections to at least NUM_CLIENTS
|
|
|
|
storage servers.
|
|
|
|
"""
|
|
|
|
|
2007-03-08 02:16:06 +00:00
|
|
|
def upload_from_file_to_uri(filename=str):
|
2007-04-30 20:06:09 +00:00
|
|
|
"""Upload a file to the grid. This accepts a filename (which must be
|
2007-03-08 02:16:06 +00:00
|
|
|
absolute) that points to a file on the node's local disk. The node
|
2007-04-30 20:06:09 +00:00
|
|
|
will read the contents of this file, upload it to the grid, then
|
2007-03-08 02:16:06 +00:00
|
|
|
return the URI at which it was uploaded.
|
|
|
|
"""
|
|
|
|
return URI
|
|
|
|
|
|
|
|
def download_from_uri_to_file(uri=URI, filename=str):
|
2007-04-30 20:06:09 +00:00
|
|
|
"""Download a file from the grid, placing it on the node's local disk
|
2007-03-08 02:16:06 +00:00
|
|
|
at the given filename (which must be absolute[?]). Returns the
|
|
|
|
absolute filename where the file was written."""
|
|
|
|
return str
|
|
|
|
|
|
|
|
# debug stuff
|
|
|
|
|
|
|
|
def get_memory_usage():
|
|
|
|
"""Return a dict describes the amount of memory currently in use. The
|
|
|
|
keys are 'VmPeak', 'VmSize', and 'VmData'. The values are integers,
|
|
|
|
measuring memory consupmtion in bytes."""
|
|
|
|
return DictOf(str, int)
|
2007-09-20 01:40:18 +00:00
|
|
|
|
2007-12-14 09:05:31 +00:00
|
|
|
def speed_test(count=int, size=int, mutable=bool):
|
2007-09-20 23:55:33 +00:00
|
|
|
"""Write 'count' tempfiles to disk, all of the given size. Measure
|
|
|
|
how long (in seconds) it takes to upload them all to the servers.
|
2007-12-14 09:05:31 +00:00
|
|
|
Then measure how long it takes to download all of them. If 'mutable'
|
|
|
|
is True, use mutable files instead of immutable ones.
|
2007-09-21 01:52:44 +00:00
|
|
|
|
|
|
|
Returns a tuple of (upload_time, download_time).
|
2007-09-20 01:40:18 +00:00
|
|
|
"""
|
2007-09-21 01:52:44 +00:00
|
|
|
return (float, float)
|
2007-09-26 19:21:15 +00:00
|
|
|
|
|
|
|
def measure_peer_response_time():
|
|
|
|
"""Send a short message to each connected peer, and measure the time
|
|
|
|
it takes for them to respond to it. This is a rough measure of the
|
|
|
|
application-level round trip time.
|
|
|
|
|
|
|
|
@return: a dictionary mapping peerid to a float (RTT time in seconds)
|
|
|
|
"""
|
|
|
|
|
|
|
|
return DictOf(Nodeid, float)
|