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...
This commit is contained in:
Zooko O'Whielacronx 2008-02-29 18:40:27 -07:00
parent 5cdc678d24
commit 99f006c584
6 changed files with 73 additions and 26 deletions

View File

@ -250,7 +250,7 @@ class NewDirectoryNode:
assert isinstance(name, unicode) assert isinstance(name, unicode)
return self.set_node(name, self._create_node(child_uri), metadata) return self.set_node(name, self._create_node(child_uri), metadata)
def set_uris(self, entries): def set_children(self, entries):
node_entries = [] node_entries = []
for e in entries: for e in entries:
if len(e) == 2: if len(e) == 2:

View File

@ -669,7 +669,7 @@ class IDirectoryNode(IMutableFilesystemNode):
If this directory node is read-only, the Deferred will errback with a If this directory node is read-only, the Deferred will errback with a
NotMutableError.""" NotMutableError."""
def set_uris(entries): def set_children(entries):
"""Add multiple (name, child_uri) pairs (or (name, child_uri, """Add multiple (name, child_uri) pairs (or (name, child_uri,
metadata) triples) to a directory node. Returns a Deferred that fires metadata) triples) to a directory node. Returns a Deferred that fires
(with None) when the operation finishes. This is equivalent to (with None) when the operation finishes. This is equivalent to

View File

@ -110,7 +110,7 @@ def make_verifier_uri():
return uri.SSKVerifierURI(storage_index=os.urandom(16), return uri.SSKVerifierURI(storage_index=os.urandom(16),
fingerprint=os.urandom(32)) fingerprint=os.urandom(32))
class NonGridDirectoryNode(dirnode.NewDirectoryNode): class FakeDirectoryNode(dirnode.NewDirectoryNode):
"""This offers IDirectoryNode, but uses a FakeMutableFileNode for the """This offers IDirectoryNode, but uses a FakeMutableFileNode for the
backing store, so it doesn't go to the grid. The child data is still 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 encrypted and serialized, so this isn't useful for tests that want to

View File

@ -7,15 +7,13 @@ from allmydata.interfaces import IURI, IClient, IMutableFileNode, \
INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode
from allmydata.util import hashutil, testutil from allmydata.util import hashutil, testutil
from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \ 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 from twisted.internet import defer, reactor
# to test dirnode.py, we want to construct a tree of real DirectoryNodes that # 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 # contain pointers to fake files. We start with a fake MutableFileNode that
# stores all of its data in a static table. # stores all of its data in a static table.
FakeDirectoryNode = NonGridDirectoryNode
class Marker: class Marker:
implements(IFileNode, IMutableFileNode) # sure, why not implements(IFileNode, IMutableFileNode) # sure, why not
def __init__(self, nodeuri): 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"d3"))
d.addCallback(lambda res: n.delete(u"d4")) d.addCallback(lambda res: n.delete(u"d4"))
# metadata through set_uris() # metadata through set_children()
d.addCallback(lambda res: n.set_uris([ (u"e1", fake_file_uri), d.addCallback(lambda res: n.set_children([ (u"e1", fake_file_uri),
(u"e2", fake_file_uri, {}), (u"e2", fake_file_uri, {}),
(u"e3", fake_file_uri, (u"e3", fake_file_uri,
{"key": "value"}), {"key": "value"}),

View File

@ -7,7 +7,7 @@ from twisted.web import client, error, http
from twisted.python import failure, log from twisted.python import failure, log
from allmydata import interfaces, provisioning, uri, webish, upload, download from allmydata import interfaces, provisioning, uri, webish, upload, download
from allmydata.util import fileutil 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 from allmydata.interfaces import IURI, INewDirectoryURI, IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI, IMutableFileNode
# create a fake uploader/downloader, and a couple of fake dirnodes, then # 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): def connected_to_introducer(self):
return False return False
def create_node_from_uri(self, uri): def create_node_from_uri(self, auri):
u = IURI(uri) u = uri.from_string(auri)
if (INewDirectoryURI.providedBy(u) if (INewDirectoryURI.providedBy(u)
or IReadonlyNewDirectoryURI.providedBy(u)): or IReadonlyNewDirectoryURI.providedBy(u)):
return NonGridDirectoryNode(self).init_from_uri(u) return FakeDirectoryNode(self).init_from_uri(u)
if IFileURI.providedBy(u): if IFileURI.providedBy(u):
return FakeCHKFileNode(u, self) return FakeCHKFileNode(u, self)
assert IMutableFileURI.providedBy(u), u assert IMutableFileURI.providedBy(u), u
return FakeMutableFileNode(self).init_from_uri(u) return FakeMutableFileNode(self).init_from_uri(u)
def create_empty_dirnode(self): def create_empty_dirnode(self):
n = NonGridDirectoryNode(self) n = FakeDirectoryNode(self)
d = n.create() d = n.create()
d.addCallback(lambda res: n) d.addCallback(lambda res: n)
return d return d
@ -1299,6 +1299,46 @@ class Web(WebMixin, unittest.TestCase):
d.addCallback(self.failUnlessNodeKeysAre, []) d.addCallback(self.failUnlessNodeKeysAre, [])
return d 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): def test_POST_put_uri(self):
contents, n, newuri = self.makefile(8) contents, n, newuri = self.makefile(8)
d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri) d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)

View File

@ -899,6 +899,16 @@ class POSTHandler(rend.Page):
d.addCallback(_got_child_check) d.addCallback(_got_child_check)
return d 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): def renderHTTP(self, ctx):
req = inevow.IRequest(ctx) req = inevow.IRequest(ctx)
@ -973,16 +983,16 @@ class POSTHandler(rend.Page):
d = self._POST_overwrite(contents) d = self._POST_overwrite(contents)
elif t == "check": elif t == "check":
d = self._POST_check(name) d = self._POST_check(name)
# elif t == "set_children": elif t == "set_children":
# d = self._POST_set_(name) req.content.seek(0)
# if not name: body = req.content.read()
# raise RuntimeError("set-uri requires a name") try:
# newuri = get_arg(req, "uri") children = simplejson.loads(body)
# assert newuri is not None except ValueError, le:
# d = self._check_replacement(name) le.args = tuple(le.args + (body,))
# d.addCallback(lambda res: self._node.set_uri(name, newuri)) # TODO test handling of bad JSON
# d.addCallback(lambda res: newuri) raise
d = self._POST_set_children(children)
else: else:
print "BAD t=%s" % t print "BAD t=%s" % t
return "BAD t=%s" % t return "BAD t=%s" % t
@ -1346,9 +1356,8 @@ class UnlinkedPUTSSKUploader(rend.Page):
req = inevow.IRequest(ctx) req = inevow.IRequest(ctx)
assert req.method == "PUT" assert req.method == "PUT"
# SDMF: files are small, and we can only upload data # SDMF: files are small, and we can only upload data
contents = req.content req.content.seek(0)
contents.seek(0) data = req.content.read()
data = contents.read()
d = IClient(ctx).create_mutable_file(data) d = IClient(ctx).create_mutable_file(data)
d.addCallback(lambda n: n.get_uri()) d.addCallback(lambda n: n.get_uri())
return d return d