webapi: t=mkdir now accepts initial children, using the same JSON that t=json

emits.

client.create_dirnode(initial_children=) now works.
This commit is contained in:
Brian Warner 2009-10-12 19:34:44 -07:00
parent cf65cc2ae3
commit b30041c5ec
6 changed files with 223 additions and 13 deletions

View File

@ -345,10 +345,46 @@ PUT /uri
POST /uri?t=mkdir
PUT /uri?t=mkdir
Create a new empty directory and return its write-cap as the HTTP response
body. This does not make the newly created directory visible from the
virtual drive. The "PUT" operation is provided for backwards compatibility:
new code should use POST.
Create a new directory (either empty or with some initial children) and
return its write-cap as the HTTP response body. This does not make the newly
created directory visible from the virtual drive. The "PUT" operation is
provided for backwards compatibility: new code should use POST.
Initial children are provided in the "children" field of the POST form, or
as the request body of the PUT request. This is more efficient than doing
separate mkdir and add-children operations. If this value is empty, the new
directory will be empty.
If not empty, it will be interpreted as a JSON-encoded dictionary of
children with which the new directory should be populated, using the same
format as would be returned in the 'children' value of the t=json GET
request, described below. Each dictionary key should be a child name, and
each value should be a list of [TYPE, PROPDICT], where PROPDICT contains
"rw_uri", "ro_uri", and "metadata" keys (all others are ignored). For
example, the PUT request body could be:
{
"Fran\u00e7ais": [ "filenode", {
"ro_uri": "URI:CHK:...",
"size": bytes,
"metadata": {
"ctime": 1202777696.7564139,
"mtime": 1202777696.7564139,
"tahoe": {
"linkcrtime": 1202777696.7564139,
"linkmotime": 1202777696.7564139,
} } } ],
"subdir": [ "dirnode", {
"rw_uri": "URI:DIR2:...",
"ro_uri": "URI:DIR2-RO:...",
"metadata": {
"ctime": 1202778102.7589991,
"mtime": 1202778111.2160511,
"tahoe": {
"linkcrtime": 1202777696.7564139,
"linkmotime": 1202777696.7564139,
} } } ]
}
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
@ -358,6 +394,9 @@ PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
intermediate directories as necessary. If the named target directory already
exists, this will make no changes to it.
If a directory is created, it will be populated with initial children via
the PUT request body or POST 'children' form field, as described above.
This will return an error if a blocking file is present at any of the parent
names, preventing the server from creating the necessary parent directory.
@ -367,7 +406,9 @@ PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME
Create a new empty directory and attach it to the given existing directory.
This will create additional intermediate directories as necessary.
This will create additional intermediate directories as necessary. The new
directory will be populated with initial children via the PUT request body
or POST 'children' form field, as described above.
The URL of this form points to the parent of the bottom-most new directory,
whereas the previous form has a URL that points directly to the bottom-most
@ -742,6 +783,9 @@ POST /uri?t=mkdir
"false"), then the HTTP response body will simply be the write-cap of the
new directory.
It accepts the same initial-children arguments as described in earlier
t=mkdir sections, but these are unlikely to be useful from a browser form.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=CHILDNAME
This creates a new directory as a child of the designated SUBDIR. This will
@ -753,6 +797,9 @@ POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=CHILDNAME
when_done= argument, the HTTP response will simply contain the write-cap of
the directory that was just created.
It accepts the same initial-children arguments as described in earlier
t=mkdir sections, but these are unlikely to be useful from a browser form.
=== Uploading a File ===

View File

@ -459,7 +459,6 @@ class Client(node.Node, pollmixin.PollMixin):
def create_dirnode(self, initial_children={}):
d = self.nodemaker.create_new_mutable_directory()
assert not initial_children, "not ready yet: %s" % (initial_children,)
if initial_children:
d.addCallback(lambda n: n.set_children(initial_children))
return d

View File

@ -11,13 +11,14 @@ from allmydata import interfaces, uri, webish
from allmydata.storage.shares import get_share_file
from allmydata.storage_client import StorageFarmBroker
from allmydata.immutable import upload, download
from allmydata.dirnode import DirectoryNode
from allmydata.nodemaker import NodeMaker
from allmydata.unknown import UnknownNode
from allmydata.web import status, common
from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
from allmydata.util import fileutil, base32
from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
create_chk_filenode, WebErrorMixin, ShouldFailMixin
create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
from allmydata.interfaces import IMutableFileNode
from allmydata.mutable import servermap, publish, retrieve
import common_util as testutil
@ -1121,6 +1122,66 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(self.failUnlessNodeKeysAre, [])
return d
def test_PUT_NEWDIRURL_initial_children(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.PUT(self.public_url + "/foo/newdir?t=mkdir",
simplejson.dumps(newkids))
def _check(uri):
n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
d2.addCallback(lambda ign:
n.get_child_and_metadata_at_path(u"child-imm"))
d2.addCallback(lambda (c1, md1):
self.failUnlessEqual(md1["metakey1"], "metavalue1"))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable",
filecap2))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable-ro",
filecap3))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"dirchild", dircap))
return d2
d.addCallback(_check)
d.addCallback(lambda res:
self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
return d
def test_POST_NEWDIRURL_initial_children(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.POST(self.public_url + "/foo/newdir?t=mkdir",
children=simplejson.dumps(newkids))
def _check(uri):
n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable",
filecap2))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable-ro",
filecap3))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"dirchild", dircap))
return d2
d.addCallback(_check)
d.addCallback(lambda res:
self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
return d
def test_PUT_NEWDIRURL_exists(self):
d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
d.addCallback(lambda res:
@ -1867,6 +1928,18 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(self.failUnlessNodeKeysAre, [])
return d
def test_POST_mkdir_initial_children(self):
newkids, filecap1, ign, ign, ign = self._create_initial_children()
d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir",
children=simplejson.dumps(newkids))
d.addCallback(lambda res:
self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
return d
def test_POST_mkdir_2(self):
d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
d.addCallback(lambda res:
@ -1900,6 +1973,44 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(_check_target)
return d
def _create_initial_children(self):
contents, n, filecap1 = self.makefile(12)
md1 = {"metakey1": "metavalue1"}
filecap2 = make_mutable_file_uri()
node3 = self.s.create_node_from_uri(make_mutable_file_uri())
filecap3 = node3.get_readonly_uri()
node4 = self.s.create_node_from_uri(make_mutable_file_uri())
dircap = DirectoryNode(node4, None, None).get_uri()
newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
"metadata": md1, }],
u"child-mutable": ["filenode", {"rw_uri": filecap2}],
u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
u"dirchild": ["dirnode", {"rw_uri": dircap}],
}
return newkids, filecap1, filecap2, filecap3, dircap
def test_POST_mkdir_no_parentdir_initial_children(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.POST("/uri?t=mkdir", children=simplejson.dumps(newkids))
def _after_mkdir(res):
self.failUnless(res.startswith("URI:DIR"), res)
n = self.s.create_node_from_uri(res)
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable",
filecap2))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable-ro",
filecap3))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"dirchild", dircap))
return d2
d.addCallback(_after_mkdir)
return d
def test_POST_noparent_bad(self):
d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
"/uri accepts only PUT, PUT?t=mkdir, "
@ -2392,6 +2503,27 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(self.failUnlessIsEmptyJSON)
return d
def test_PUT_mkdir_initial_children(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.PUT("/uri?t=mkdir", simplejson.dumps(newkids))
def _check(uri):
n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-imm", filecap1))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable",
filecap2))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"child-mutable-ro",
filecap3))
d2.addCallback(lambda ign:
self.failUnlessChildURIIs(n, u"dirchild", dircap))
return d2
d.addCallback(_check)
return d
def test_POST_check(self):
d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
def _done(res):

View File

@ -1,4 +1,5 @@
import simplejson
from twisted.web import http, server
from twisted.python import log
from zope.interface import Interface
@ -53,6 +54,22 @@ def get_arg(ctx_or_req, argname, default=None, multiple=False):
return results[0]
return default
def convert_initial_children_json(initial_children_json):
initial_children = {}
if initial_children_json:
data = simplejson.loads(initial_children_json)
for (name, (ctype, propdict)) in data.iteritems():
name = unicode(name)
writecap = propdict.get("rw_uri")
if writecap is not None:
writecap = str(writecap)
readcap = propdict.get("ro_uri")
if readcap is not None:
readcap = str(readcap)
metadata = propdict.get("metadata", {})
initial_children[name] = (writecap, readcap, metadata)
return initial_children
def abbreviate_time(data):
# 1.23s, 790ms, 132us
if data is None:

View File

@ -22,7 +22,7 @@ from allmydata.web.common import text_plain, WebError, \
IOpHandleTable, NeedOperationHandleError, \
boolean_of_arg, get_arg, get_root, parse_replace_arg, \
should_create_intermediate_directories, \
getxmlfile, RenderMixin, humanize_failure
getxmlfile, RenderMixin, humanize_failure, convert_initial_children_json
from allmydata.web.filenode import ReplaceMeMixin, \
FileNodeHandler, PlaceHolderNodeHandler
from allmydata.web.check_results import CheckResults, \
@ -96,7 +96,13 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir") ]:
if DEBUG: print " making final directory"
# final directory
d = self.node.create_subdirectory(name)
if method == "POST":
kids_json = get_arg(req, "children", "")
else:
req.content.seek(0)
kids_json = req.content.read()
initial_children = convert_initial_children_json(kids_json)
d = self.node.create_subdirectory(name, initial_children)
d.addCallback(make_handler_for,
self.client, self.node, name)
return d
@ -221,7 +227,10 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
return defer.succeed(self.node.get_uri()) # TODO: urlencode
name = name.decode("utf-8")
replace = boolean_of_arg(get_arg(req, "replace", "true"))
d = self.node.create_subdirectory(name, overwrite=replace)
children_json = get_arg(req, "children", "")
initial_children = convert_initial_children_json(children_json)
d = self.node.create_subdirectory(name, initial_children,
overwrite=replace)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d

View File

@ -4,7 +4,8 @@ from twisted.web import http
from twisted.internet import defer
from nevow import rend, url, tags as T
from allmydata.immutable.upload import FileHandle
from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg
from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
convert_initial_children_json
from allmydata.web import status
def PUTUnlinkedCHK(req, client):
@ -25,7 +26,10 @@ def PUTUnlinkedSSK(req, client):
def PUTUnlinkedCreateDirectory(req, client):
# "PUT /uri?t=mkdir", to create an unlinked directory.
d = client.create_dirnode()
req.content.seek(0)
initial_children_json = req.content.read()
initial_children = convert_initial_children_json(initial_children_json)
d = client.create_dirnode(initial_children=initial_children)
d.addCallback(lambda dirnode: dirnode.get_uri())
# XXX add redirect_to_result
return d
@ -90,7 +94,9 @@ def POSTUnlinkedSSK(req, client):
def POSTUnlinkedCreateDirectory(req, client):
# "POST /uri?t=mkdir", to create an unlinked directory.
d = client.create_dirnode()
initial_json = get_arg(req, "children", "")
initial_children = convert_initial_children_json(initial_json)
d = client.create_dirnode(initial_children=initial_children)
redirect = get_arg(req, "redirect_to_result", "false")
if boolean_of_arg(redirect):
def _then_redir(res):