mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-02-20 17:52:50 +00:00
nodemaker: implement immutable directories (internal interface), for #607
* nodemaker.create_from_cap() now handles DIR2-CHK and DIR2-LIT * client.create_immutable_dirnode() is used to create them * no webapi yet
This commit is contained in:
parent
cc422f8dc0
commit
5fe713fc52
@ -460,6 +460,9 @@ class Client(node.Node, pollmixin.PollMixin):
|
||||
def create_dirnode(self, initial_children={}):
|
||||
d = self.nodemaker.create_new_mutable_directory(initial_children)
|
||||
return d
|
||||
def create_immutable_dirnode(self, children):
|
||||
return self.nodemaker.create_immutable_directory(children,
|
||||
self.convergence)
|
||||
|
||||
def create_mutable_file(self, contents=None, keysize=None):
|
||||
return self.nodemaker.create_mutable_file(contents, keysize)
|
||||
|
@ -18,8 +18,7 @@ from allmydata.monitor import Monitor
|
||||
from allmydata.util import hashutil, mathutil, base32, log
|
||||
from allmydata.util.assertutil import precondition
|
||||
from allmydata.util.netstring import netstring, split_netstring
|
||||
from allmydata.uri import DirectoryURI, ReadonlyDirectoryURI, \
|
||||
LiteralFileURI, from_string
|
||||
from allmydata.uri import LiteralFileURI, from_string, wrap_dirnode_cap
|
||||
from pycryptopp.cipher.aes import AES
|
||||
from allmydata.util.dictutil import AuxValueDict
|
||||
|
||||
@ -125,8 +124,11 @@ class Adder:
|
||||
|
||||
def _encrypt_rwcap(filenode, rwcap):
|
||||
assert isinstance(rwcap, str)
|
||||
writekey = filenode.get_writekey()
|
||||
if not writekey:
|
||||
return ""
|
||||
salt = hashutil.mutable_rwcap_salt_hash(rwcap)
|
||||
key = hashutil.mutable_rwcap_key_hash(salt, filenode.get_writekey())
|
||||
key = hashutil.mutable_rwcap_key_hash(salt, writekey)
|
||||
cryptor = AES(key)
|
||||
crypttext = cryptor.process(rwcap)
|
||||
mac = hashutil.hmac(key, salt + crypttext)
|
||||
@ -188,20 +190,23 @@ class DirectoryNode:
|
||||
def __init__(self, filenode, nodemaker, uploader):
|
||||
self._node = filenode
|
||||
filenode_cap = filenode.get_cap()
|
||||
if filenode_cap.is_readonly():
|
||||
self._uri = ReadonlyDirectoryURI(filenode_cap)
|
||||
else:
|
||||
self._uri = DirectoryURI(filenode_cap)
|
||||
self._uri = wrap_dirnode_cap(filenode_cap)
|
||||
self._nodemaker = nodemaker
|
||||
self._uploader = uploader
|
||||
self._most_recent_size = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s %s>" % (self.__class__.__name__, self.is_readonly() and "RO" or "RW", hasattr(self, '_uri') and self._uri.abbrev())
|
||||
return "<%s %s-%s %s>" % (self.__class__.__name__,
|
||||
self.is_readonly() and "RO" or "RW",
|
||||
self.is_mutable() and "MUT" or "IMM",
|
||||
hasattr(self, '_uri') and self._uri.abbrev())
|
||||
|
||||
def get_size(self):
|
||||
# return the size of our backing mutable file, in bytes, if we've
|
||||
# fetched it.
|
||||
if not self._node.is_mutable():
|
||||
# TODO?: consider using IMutableFileNode.providedBy(self._node)
|
||||
return self._node.get_size()
|
||||
return self._most_recent_size
|
||||
|
||||
def _set_size(self, data):
|
||||
@ -209,8 +214,12 @@ class DirectoryNode:
|
||||
return data
|
||||
|
||||
def _read(self):
|
||||
d = self._node.download_best_version()
|
||||
d.addCallback(self._set_size)
|
||||
if self._node.is_mutable():
|
||||
# use the IMutableFileNode API.
|
||||
d = self._node.download_best_version()
|
||||
d.addCallback(self._set_size)
|
||||
else:
|
||||
d = self._node.download_to_data()
|
||||
d.addCallback(self._unpack_contents)
|
||||
return d
|
||||
|
||||
|
@ -1638,7 +1638,7 @@ class IDownloadResults(Interface):
|
||||
class IUploader(Interface):
|
||||
def upload(uploadable):
|
||||
"""Upload the file. 'uploadable' must impement IUploadable. This
|
||||
returns a Deferred which fires with an UploadResults instance, from
|
||||
returns a Deferred which fires with an IUploadResults instance, from
|
||||
which the URI of the file can be obtained as results.uri ."""
|
||||
|
||||
def upload_ssk(write_capability, new_version, uploadable):
|
||||
|
@ -3,11 +3,16 @@ from zope.interface import implements
|
||||
from allmydata.util.assertutil import precondition
|
||||
from allmydata.interfaces import INodeMaker
|
||||
from allmydata.immutable.filenode import FileNode, LiteralFileNode
|
||||
from allmydata.immutable.upload import Data
|
||||
from allmydata.mutable.filenode import MutableFileNode
|
||||
from allmydata.dirnode import DirectoryNode, pack_children
|
||||
from allmydata.unknown import UnknownNode
|
||||
from allmydata import uri
|
||||
|
||||
class DummyImmutableFileNode:
|
||||
def get_writekey(self):
|
||||
return None
|
||||
|
||||
class NodeMaker:
|
||||
implements(INodeMaker)
|
||||
|
||||
@ -67,7 +72,10 @@ class NodeMaker:
|
||||
return self._create_immutable(cap)
|
||||
if isinstance(cap, (uri.ReadonlySSKFileURI, uri.WriteableSSKFileURI)):
|
||||
return self._create_mutable(cap)
|
||||
if isinstance(cap, (uri.ReadonlyDirectoryURI, uri.DirectoryURI)):
|
||||
if isinstance(cap, (uri.DirectoryURI,
|
||||
uri.ReadonlyDirectoryURI,
|
||||
uri.ImmutableDirectoryURI,
|
||||
uri.LiteralDirectoryURI)):
|
||||
filenode = self._create_from_cap(cap.get_filenode_cap())
|
||||
return self._create_dirnode(filenode)
|
||||
return None
|
||||
@ -92,3 +100,22 @@ class NodeMaker:
|
||||
pack_children(n, initial_children))
|
||||
d.addCallback(self._create_dirnode)
|
||||
return d
|
||||
|
||||
def create_immutable_directory(self, children, convergence):
|
||||
for (name, (node, metadata)) in children.iteritems():
|
||||
precondition(not isinstance(node, UnknownNode),
|
||||
"create_immutable_directory does not accept UnknownNode", node)
|
||||
precondition(isinstance(metadata, dict),
|
||||
"create_immutable_directory requires metadata to be a dict, not None", metadata)
|
||||
precondition(not node.is_mutable(),
|
||||
"create_immutable_directory requires immutable children", node)
|
||||
n = DummyImmutableFileNode() # writekey=None
|
||||
packed = pack_children(n, children)
|
||||
uploadable = Data(packed, convergence)
|
||||
d = self.uploader.upload(uploadable, history=self.history)
|
||||
def _uploaded(results):
|
||||
filecap = self.create_from_cap(results.uri)
|
||||
return filecap
|
||||
d.addCallback(_uploaded)
|
||||
d.addCallback(self._create_dirnode)
|
||||
return d
|
||||
|
@ -32,7 +32,7 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
def _done(res):
|
||||
self.failUnless(isinstance(res, dirnode.DirectoryNode))
|
||||
rep = str(res)
|
||||
self.failUnless("RW" in rep)
|
||||
self.failUnless("RW-MUT" in rep)
|
||||
d.addCallback(_done)
|
||||
return d
|
||||
|
||||
@ -51,7 +51,7 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
def _created(dn):
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
rep = str(dn)
|
||||
self.failUnless("RW" in rep)
|
||||
self.failUnless("RW-MUT" in rep)
|
||||
return dn.list()
|
||||
d.addCallback(_created)
|
||||
def _check_kids(children):
|
||||
@ -83,6 +83,102 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
||||
bad_kids2))
|
||||
return d
|
||||
|
||||
def test_immutable(self):
|
||||
self.basedir = "dirnode/Dirnode/test_immutable"
|
||||
self.set_up_grid()
|
||||
c = self.g.clients[0]
|
||||
nm = c.nodemaker
|
||||
setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861"
|
||||
one_uri = "URI:LIT:n5xgk" # LIT for "one"
|
||||
mut_readcap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
|
||||
mut_writecap = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
|
||||
kids = {u"one": (nm.create_from_cap(one_uri), {}),
|
||||
u"two": (nm.create_from_cap(setup_py_uri),
|
||||
{"metakey": "metavalue"}),
|
||||
}
|
||||
d = c.create_immutable_dirnode(kids)
|
||||
def _created(dn):
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
self.failIf(dn.is_mutable())
|
||||
self.failUnless(dn.is_readonly())
|
||||
rep = str(dn)
|
||||
self.failUnless("RO-IMM" in rep)
|
||||
cap = dn.get_cap()
|
||||
self.failUnlessIn("CHK", cap.to_string())
|
||||
self.cap = cap
|
||||
return dn.list()
|
||||
d.addCallback(_created)
|
||||
def _check_kids(children):
|
||||
self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"])
|
||||
one_node, one_metadata = children[u"one"]
|
||||
two_node, two_metadata = children[u"two"]
|
||||
self.failUnlessEqual(one_node.get_size(), 3)
|
||||
self.failUnlessEqual(two_node.get_size(), 14861)
|
||||
self.failUnless(isinstance(one_metadata, dict), one_metadata)
|
||||
self.failUnlessEqual(two_metadata["metakey"], "metavalue")
|
||||
d.addCallback(_check_kids)
|
||||
d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
|
||||
d.addCallback(lambda dn: dn.list())
|
||||
d.addCallback(_check_kids)
|
||||
future_writecap = "x-tahoe-crazy://I_am_from_the_future."
|
||||
future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
|
||||
future_node = UnknownNode(future_writecap, future_readcap)
|
||||
bad_kids1 = {u"one": (future_node, {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(AssertionError, "bad_kids1",
|
||||
"does not accept UnknownNode",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids1))
|
||||
bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(AssertionError, "bad_kids2",
|
||||
"requires metadata to be a dict",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids2))
|
||||
bad_kids3 = {u"one": (nm.create_from_cap(mut_writecap), {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(AssertionError, "bad_kids3",
|
||||
"create_immutable_directory requires immutable children",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids3))
|
||||
bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})}
|
||||
d.addCallback(lambda ign:
|
||||
self.shouldFail(AssertionError, "bad_kids4",
|
||||
"create_immutable_directory requires immutable children",
|
||||
c.create_immutable_dirnode,
|
||||
bad_kids4))
|
||||
d.addCallback(lambda ign: c.create_immutable_dirnode({}))
|
||||
def _created_empty(dn):
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
self.failIf(dn.is_mutable())
|
||||
self.failUnless(dn.is_readonly())
|
||||
rep = str(dn)
|
||||
self.failUnless("RO-IMM" in rep)
|
||||
cap = dn.get_cap()
|
||||
self.failUnlessIn("LIT", cap.to_string())
|
||||
self.failUnlessEqual(cap.to_string(), "URI:DIR2-LIT:")
|
||||
self.cap = cap
|
||||
return dn.list()
|
||||
d.addCallback(_created_empty)
|
||||
d.addCallback(lambda kids: self.failUnlessEqual(kids, {}))
|
||||
smallkids = {u"o": (nm.create_from_cap(one_uri), {})}
|
||||
d.addCallback(lambda ign: c.create_immutable_dirnode(smallkids))
|
||||
def _created_small(dn):
|
||||
self.failUnless(isinstance(dn, dirnode.DirectoryNode))
|
||||
self.failIf(dn.is_mutable())
|
||||
self.failUnless(dn.is_readonly())
|
||||
rep = str(dn)
|
||||
self.failUnless("RO-IMM" in rep)
|
||||
cap = dn.get_cap()
|
||||
self.failUnlessIn("LIT", cap.to_string())
|
||||
self.failUnlessEqual(cap.to_string(),
|
||||
"URI:DIR2-LIT:gi4tumj2n4wdcmz2kvjesosmjfkdu3rvpbtwwlbqhiwdeot3puwcy")
|
||||
self.cap = cap
|
||||
return dn.list()
|
||||
d.addCallback(_created_small)
|
||||
d.addCallback(lambda kids: self.failUnlessEqual(kids.keys(), [u"o"]))
|
||||
return d
|
||||
|
||||
def test_check(self):
|
||||
self.basedir = "dirnode/Dirnode/test_check"
|
||||
self.set_up_grid()
|
||||
|
@ -371,7 +371,8 @@ class NewDirnode(unittest.TestCase):
|
||||
self.failUnless(str(u2_verifier))
|
||||
|
||||
def test_literal(self):
|
||||
u1 = uri.LiteralDirectoryURI("data")
|
||||
u0 = uri.LiteralFileURI("data")
|
||||
u1 = uri.LiteralDirectoryURI(u0)
|
||||
self.failUnless(str(u1))
|
||||
u1s = u1.to_string()
|
||||
self.failUnlessEqual(u1.to_string(), "URI:DIR2-LIT:mrqxiyi")
|
||||
|
@ -166,6 +166,7 @@ class LiteralFileURI(_BaseURI):
|
||||
|
||||
def __init__(self, data=None):
|
||||
if data is not None:
|
||||
assert isinstance(data, str)
|
||||
self.data = data
|
||||
|
||||
@classmethod
|
||||
@ -454,13 +455,20 @@ class LiteralDirectoryURI(_ImmutableDirectoryBaseURI):
|
||||
BASE_STRING_RE=re.compile('^'+BASE_STRING)
|
||||
BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-LIT'+SEP)
|
||||
INNER_URI_CLASS=LiteralFileURI
|
||||
def __init__(self, data=None):
|
||||
filenode_uri = LiteralFileURI(data)
|
||||
_ImmutableDirectoryBaseURI.__init__(self, filenode_uri)
|
||||
def get_verify_cap(self):
|
||||
# LIT caps have no verifier, since they aren't distributed
|
||||
return None
|
||||
|
||||
def wrap_dirnode_cap(filecap):
|
||||
if isinstance(filecap, WriteableSSKFileURI):
|
||||
return DirectoryURI(filecap)
|
||||
if isinstance(filecap, ReadonlySSKFileURI):
|
||||
return ReadonlyDirectoryURI(filecap)
|
||||
if isinstance(filecap, CHKFileURI):
|
||||
return ImmutableDirectoryURI(filecap)
|
||||
if isinstance(filecap, LiteralFileURI):
|
||||
return LiteralDirectoryURI(filecap)
|
||||
assert False, "cannot wrap a dirnode around %s" % filecap.__class__
|
||||
|
||||
class DirectoryURIVerifier(_DirectoryBaseURI):
|
||||
implements(IVerifierURI)
|
||||
|
Loading…
x
Reference in New Issue
Block a user