From 99f006c5841aa326b8349bc0a727ba672083a6a7 Mon Sep 17 00:00:00 2001 From: Zooko O'Whielacronx Date: Fri, 29 Feb 2008 18:40:27 -0700 Subject: [PATCH] wapi: add POST /uri/$DIRECTORY?t=set_children Unfinished bits: doc in webapi.txt, test handling of badly formed JSON, return reasonable HTTP response, examination of the effect of this patch on code coverage -- but I'm committing it anyway because MikeB can use it and I'm being called to dinner... --- src/allmydata/dirnode.py | 2 +- src/allmydata/interfaces.py | 2 +- src/allmydata/test/common.py | 2 +- src/allmydata/test/test_dirnode.py | 8 ++--- src/allmydata/test/test_web.py | 50 +++++++++++++++++++++++++++--- src/allmydata/webish.py | 35 +++++++++++++-------- 6 files changed, 73 insertions(+), 26 deletions(-) diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index fc6debc90..f80e71b7a 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -250,7 +250,7 @@ class NewDirectoryNode: assert isinstance(name, unicode) return self.set_node(name, self._create_node(child_uri), metadata) - def set_uris(self, entries): + def set_children(self, entries): node_entries = [] for e in entries: if len(e) == 2: diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index 07d1cc790..d4e6cc7cc 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -669,7 +669,7 @@ class IDirectoryNode(IMutableFilesystemNode): If this directory node is read-only, the Deferred will errback with a NotMutableError.""" - def set_uris(entries): + def set_children(entries): """Add multiple (name, child_uri) pairs (or (name, child_uri, metadata) triples) to a directory node. Returns a Deferred that fires (with None) when the operation finishes. This is equivalent to diff --git a/src/allmydata/test/common.py b/src/allmydata/test/common.py index c88e7f1a6..d0f32ceb7 100644 --- a/src/allmydata/test/common.py +++ b/src/allmydata/test/common.py @@ -110,7 +110,7 @@ def make_verifier_uri(): return uri.SSKVerifierURI(storage_index=os.urandom(16), fingerprint=os.urandom(32)) -class NonGridDirectoryNode(dirnode.NewDirectoryNode): +class FakeDirectoryNode(dirnode.NewDirectoryNode): """This offers IDirectoryNode, but uses a FakeMutableFileNode for the backing store, so it doesn't go to the grid. The child data is still encrypted and serialized, so this isn't useful for tests that want to diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py index c97d8dd21..9d053dc89 100644 --- a/src/allmydata/test/test_dirnode.py +++ b/src/allmydata/test/test_dirnode.py @@ -7,15 +7,13 @@ from allmydata.interfaces import IURI, IClient, IMutableFileNode, \ INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode from allmydata.util import hashutil, testutil from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \ - NonGridDirectoryNode, create_chk_filenode + FakeDirectoryNode, create_chk_filenode from twisted.internet import defer, reactor # to test dirnode.py, we want to construct a tree of real DirectoryNodes that # contain pointers to fake files. We start with a fake MutableFileNode that # stores all of its data in a static table. -FakeDirectoryNode = NonGridDirectoryNode - class Marker: implements(IFileNode, IMutableFileNode) # sure, why not def __init__(self, nodeuri): @@ -281,8 +279,8 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin): d.addCallback(lambda res: n.delete(u"d3")) d.addCallback(lambda res: n.delete(u"d4")) - # metadata through set_uris() - d.addCallback(lambda res: n.set_uris([ (u"e1", fake_file_uri), + # metadata through set_children() + d.addCallback(lambda res: n.set_children([ (u"e1", fake_file_uri), (u"e2", fake_file_uri, {}), (u"e3", fake_file_uri, {"key": "value"}), diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py index c78b0401d..122dbacc3 100644 --- a/src/allmydata/test/test_web.py +++ b/src/allmydata/test/test_web.py @@ -7,7 +7,7 @@ from twisted.web import client, error, http from twisted.python import failure, log from allmydata import interfaces, provisioning, uri, webish, upload, download from allmydata.util import fileutil -from allmydata.test.common import NonGridDirectoryNode, FakeCHKFileNode, FakeMutableFileNode, create_chk_filenode +from allmydata.test.common import FakeDirectoryNode, FakeCHKFileNode, FakeMutableFileNode, create_chk_filenode from allmydata.interfaces import IURI, INewDirectoryURI, IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI, IMutableFileNode # create a fake uploader/downloader, and a couple of fake dirnodes, then @@ -35,18 +35,18 @@ class FakeClient(service.MultiService): def connected_to_introducer(self): return False - def create_node_from_uri(self, uri): - u = IURI(uri) + def create_node_from_uri(self, auri): + u = uri.from_string(auri) if (INewDirectoryURI.providedBy(u) or IReadonlyNewDirectoryURI.providedBy(u)): - return NonGridDirectoryNode(self).init_from_uri(u) + return FakeDirectoryNode(self).init_from_uri(u) if IFileURI.providedBy(u): return FakeCHKFileNode(u, self) assert IMutableFileURI.providedBy(u), u return FakeMutableFileNode(self).init_from_uri(u) def create_empty_dirnode(self): - n = NonGridDirectoryNode(self) + n = FakeDirectoryNode(self) d = n.create() d.addCallback(lambda res: n) return d @@ -1299,6 +1299,46 @@ class Web(WebMixin, unittest.TestCase): d.addCallback(self.failUnlessNodeKeysAre, []) return d + def test_POST_set_children(self): + contents9, n9, newuri9 = self.makefile(9) + contents10, n10, newuri10 = self.makefile(10) + contents11, n11, newuri11 = self.makefile(11) + + reqbody = """{ + "atomic_added_1": [ "filenode", { "rw_uri": "%s", + "size": 0, + "metadata": { + "ctime": 1002777696.7564139, + "mtime": 1002777696.7564139 + } + } ], + "atomic_added_2": [ "filenode", { "rw_uri": "%s", + "size": 1, + "metadata": { + "ctime": 1002777696.7564139, + "mtime": 1002777696.7564139 + } + } ], + "atomic_added_3": [ "filenode", { "rw_uri": "%s", + "size": 2, + "metadata": { + "ctime": 1002777696.7564139, + "mtime": 1002777696.7564139 + } + } ] + }""" % (newuri9, newuri10, newuri11) + + url = self.webish_url + self.public_url + "/foo" + "?t=set_children" + + d = client.getPage(url, method="POST", postdata=reqbody) + def _then(res): + self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1") + self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2") + self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3") + + d.addCallback(_then) + return d + def test_POST_put_uri(self): contents, n, newuri = self.makefile(8) d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri) diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py index 7a8e954f4..f8da62717 100644 --- a/src/allmydata/webish.py +++ b/src/allmydata/webish.py @@ -899,6 +899,16 @@ class POSTHandler(rend.Page): d.addCallback(_got_child_check) return d + def _POST_set_children(self, children): + cs = [] + for name, (file_or_dir, mddict) in children.iteritems(): + cap = str(mddict.get('rw_uri') or mddict.get('ro_uri')) + cs.append((name, cap, mddict.get('metadata'))) + + d = self._node.set_children(cs) + d.addCallback(lambda res: "Okay so I did it.") + return d + def renderHTTP(self, ctx): req = inevow.IRequest(ctx) @@ -973,16 +983,16 @@ class POSTHandler(rend.Page): d = self._POST_overwrite(contents) elif t == "check": d = self._POST_check(name) - # elif t == "set_children": - # d = self._POST_set_(name) - # if not name: - # raise RuntimeError("set-uri requires a name") - # newuri = get_arg(req, "uri") - # assert newuri is not None - # d = self._check_replacement(name) - # d.addCallback(lambda res: self._node.set_uri(name, newuri)) - # d.addCallback(lambda res: newuri) - + elif t == "set_children": + req.content.seek(0) + body = req.content.read() + try: + children = simplejson.loads(body) + except ValueError, le: + le.args = tuple(le.args + (body,)) + # TODO test handling of bad JSON + raise + d = self._POST_set_children(children) else: print "BAD t=%s" % t return "BAD t=%s" % t @@ -1346,9 +1356,8 @@ class UnlinkedPUTSSKUploader(rend.Page): req = inevow.IRequest(ctx) assert req.method == "PUT" # SDMF: files are small, and we can only upload data - contents = req.content - contents.seek(0) - data = contents.read() + req.content.seek(0) + data = req.content.read() d = IClient(ctx).create_mutable_file(data) d.addCallback(lambda n: n.get_uri()) return d