Add t=mkdir-immutable to the webapi. Closes #607.

* change t=mkdir-with-children to not use multipart/form encoding. Instead,
  the request body is all JSON. t=mkdir-immutable uses this format too.
* make nodemaker.create_immutable_dirnode() get convergence from SecretHolder,
  but let callers override it
* raise NotDeepImmutableError instead of using assert()
* add mutable= argument to DirectoryNode.create_subdirectory(), default True
This commit is contained in:
Brian Warner 2009-11-17 23:09:00 -08:00
parent d2426ea3bd
commit f85690697a
13 changed files with 296 additions and 65 deletions

View File

@ -356,17 +356,15 @@ POST /uri?t=mkdir-with-children
write-cap as the HTTP response body. The new directory is not attached to write-cap as the HTTP response body. The new directory is not attached to
any other directory: the returned write-cap is the only reference to it. any other directory: the returned write-cap is the only reference to it.
Initial children are provided in the "children" field of the POST form. This Initial children are provided as the body of the POST form (this is more
is more efficient than doing separate mkdir and add-children operations. If efficient than doing separate mkdir and set_children operations). If the
this value is empty, the new directory will be empty. body is empty, the new directory will be empty. If not empty, the body will
be interpreted as a JSON-encoded dictionary of children with which the new
If not empty, it will be interpreted as a JSON-encoded dictionary of directory should be populated, using the same format as would be returned in
children with which the new directory should be populated, using the same the 'children' value of the t=json GET request, described below. Each
format as would be returned in the 'children' value of the t=json GET dictionary key should be a child name, and each value should be a list of
request, described below. Each dictionary key should be a child name, and [TYPE, PROPDICT], where PROPDICT contains "rw_uri", "ro_uri", and "metadata"
each value should be a list of [TYPE, PROPDICT], where PROPDICT contains keys (all others are ignored). For example, the PUT request body could be:
"rw_uri", "ro_uri", and "metadata" keys (all others are ignored). For
example, the PUT request body could be:
{ {
"Fran\u00e7ais": [ "filenode", { "Fran\u00e7ais": [ "filenode", {
@ -391,6 +389,20 @@ POST /uri?t=mkdir-with-children
} } } ] } } } ]
} }
Note that the webapi-using client application must not provide the
"Content-Type: multipart/form-data" header that usually accompanies HTML
form submissions, since the body is not formatted this way. Doing so will
cause a server error as the lower-level code misparses the request body.
POST /uri?t=mkdir-immutable
Like t=mkdir-with-children above, but the new directory will be
deep-immutable. This means that the directory itself is immutable, and that
it can only contain deep-immutable objects, like immutable files, literal
files, and deep-immutable directories. A non-empty request body is
mandatory, since after the directory is created, it will not be possible to
add more children to it.
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
@ -410,8 +422,13 @@ PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children
Like above, but if the final directory is created, it will be populated with Like above, but if the final directory is created, it will be populated with
initial children via the POST 'children' form field, as described above in initial children from the POST request body, as described above in the
the /uri?t=mkdir-with-children operation. /uri?t=mkdir-with-children operation.
POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-immutable
Like above, but the final directory will be deep-immutable, with the
children specified as a JSON dictionary in the POST request body.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME
@ -425,8 +442,15 @@ POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME
As above, but the new directory will be populated with initial children via As above, but the new directory will be populated with initial children via
the POST 'children' form field, as described in /uri?t=mkdir-with-children the POST request body, as described in /uri?t=mkdir-with-children above.
above. Note that the name= argument must be passed as a queryarg, because the POST
request body is used for the initial children JSON.
POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-immutable&name=NAME
As above, but the new directory will be deep-immutable, with the children
specified as a JSON dictionary in the POST request body. Again, the name=
argument must be passed as a queryarg.
=== Get Information About A File Or Directory (as JSON) === === Get Information About A File Or Directory (as JSON) ===

View File

@ -465,9 +465,8 @@ class Client(node.Node, pollmixin.PollMixin):
def create_dirnode(self, initial_children={}): def create_dirnode(self, initial_children={}):
d = self.nodemaker.create_new_mutable_directory(initial_children) d = self.nodemaker.create_new_mutable_directory(initial_children)
return d return d
def create_immutable_dirnode(self, children): def create_immutable_dirnode(self, children, convergence=None):
return self.nodemaker.create_immutable_directory(children, return self.nodemaker.create_immutable_directory(children, convergence)
self.convergence)
def create_mutable_file(self, contents=None, keysize=None): def create_mutable_file(self, contents=None, keysize=None):
return self.nodemaker.create_mutable_file(contents, keysize) return self.nodemaker.create_mutable_file(contents, keysize)

View File

@ -188,6 +188,8 @@ class DirectoryNode:
filenode_class = MutableFileNode filenode_class = MutableFileNode
def __init__(self, filenode, nodemaker, uploader): def __init__(self, filenode, nodemaker, uploader):
assert IFileNode.providedBy(filenode), filenode
assert not IDirectoryNode.providedBy(filenode), filenode
self._node = filenode self._node = filenode
filenode_cap = filenode.get_cap() filenode_cap = filenode.get_cap()
self._uri = wrap_dirnode_cap(filenode_cap) self._uri = wrap_dirnode_cap(filenode_cap)
@ -491,11 +493,15 @@ class DirectoryNode:
d.addCallback(lambda res: deleter.old_child) d.addCallback(lambda res: deleter.old_child)
return d return d
def create_subdirectory(self, name, initial_children={}, overwrite=True): def create_subdirectory(self, name, initial_children={}, overwrite=True,
mutable=True):
assert isinstance(name, unicode) assert isinstance(name, unicode)
if self.is_readonly(): if self.is_readonly():
return defer.fail(NotMutableError()) return defer.fail(NotMutableError())
if mutable:
d = self._nodemaker.create_new_mutable_directory(initial_children) d = self._nodemaker.create_new_mutable_directory(initial_children)
else:
d = self._nodemaker.create_immutable_directory(initial_children)
def _created(child): def _created(child):
entries = {name: (child, None)} entries = {name: (child, None)}
a = Adder(self, entries, overwrite=overwrite) a = Adder(self, entries, overwrite=overwrite)

View File

@ -480,6 +480,9 @@ class UnhandledCapTypeError(Exception):
"""I recognize the cap/URI, but I cannot create an IFilesystemNode for """I recognize the cap/URI, but I cannot create an IFilesystemNode for
it.""" it."""
class NotDeepImmutableError(Exception):
"""Deep-immutable directories can only contain deep-immutable children"""
class IFilesystemNode(Interface): class IFilesystemNode(Interface):
def get_cap(): def get_cap():
"""Return the strongest 'cap instance' associated with this node. """Return the strongest 'cap instance' associated with this node.

View File

@ -1,7 +1,7 @@
import weakref import weakref
from zope.interface import implements from zope.interface import implements
from allmydata.util.assertutil import precondition from allmydata.util.assertutil import precondition
from allmydata.interfaces import INodeMaker from allmydata.interfaces import INodeMaker, NotDeepImmutableError
from allmydata.immutable.filenode import FileNode, LiteralFileNode from allmydata.immutable.filenode import FileNode, LiteralFileNode
from allmydata.immutable.upload import Data from allmydata.immutable.upload import Data
from allmydata.mutable.filenode import MutableFileNode from allmydata.mutable.filenode import MutableFileNode
@ -101,14 +101,16 @@ class NodeMaker:
d.addCallback(self._create_dirnode) d.addCallback(self._create_dirnode)
return d return d
def create_immutable_directory(self, children, convergence): def create_immutable_directory(self, children, convergence=None):
if convergence is None:
convergence = self.secret_holder.get_convergence_secret()
for (name, (node, metadata)) in children.iteritems(): for (name, (node, metadata)) in children.iteritems():
precondition(not isinstance(node, UnknownNode), precondition(not isinstance(node, UnknownNode),
"create_immutable_directory does not accept UnknownNode", node) "create_immutable_directory does not accept UnknownNode", node)
precondition(isinstance(metadata, dict), precondition(isinstance(metadata, dict),
"create_immutable_directory requires metadata to be a dict, not None", metadata) "create_immutable_directory requires metadata to be a dict, not None", metadata)
precondition(not node.is_mutable(), if node.is_mutable():
"create_immutable_directory requires immutable children", node) raise NotDeepImmutableError("%s is not immutable" % (node,))
n = DummyImmutableFileNode() # writekey=None n = DummyImmutableFileNode() # writekey=None
packed = pack_children(n, children) packed = pack_children(n, children)
uploadable = Data(packed, convergence) uploadable = Data(packed, convergence)

View File

@ -47,6 +47,8 @@ class FakeCHKFileNode:
return self.my_uri.to_string() return self.my_uri.to_string()
def get_readonly_uri(self): def get_readonly_uri(self):
return self.my_uri.to_string() return self.my_uri.to_string()
def get_cap(self):
return self.my_uri
def get_verify_cap(self): def get_verify_cap(self):
return self.my_uri.get_verify_cap() return self.my_uri.get_verify_cap()
def get_repair_cap(self): def get_repair_cap(self):

View File

@ -1,12 +1,13 @@
import time import time
from zope.interface import implements
from twisted.trial import unittest from twisted.trial import unittest
from twisted.internet import defer from twisted.internet import defer
from allmydata import uri, dirnode from allmydata import uri, dirnode
from allmydata.client import Client from allmydata.client import Client
from allmydata.immutable import upload from allmydata.immutable import upload
from allmydata.interfaces import IFileNode, \ from allmydata.interfaces import IFileNode, IMutableFileNode, \
ExistingChildError, NoSuchChildError, \ ExistingChildError, NoSuchChildError, NotDeepImmutableError, \
IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError
from allmydata.mutable.filenode import MutableFileNode from allmydata.mutable.filenode import MutableFileNode
from allmydata.mutable.common import UncoordinatedWriteError from allmydata.mutable.common import UncoordinatedWriteError
@ -137,14 +138,14 @@ class Dirnode(GridTestMixin, unittest.TestCase,
bad_kids2)) bad_kids2))
bad_kids3 = {u"one": (nm.create_from_cap(mut_writecap), {})} bad_kids3 = {u"one": (nm.create_from_cap(mut_writecap), {})}
d.addCallback(lambda ign: d.addCallback(lambda ign:
self.shouldFail(AssertionError, "bad_kids3", self.shouldFail(NotDeepImmutableError, "bad_kids3",
"create_immutable_directory requires immutable children", "is not immutable",
c.create_immutable_dirnode, c.create_immutable_dirnode,
bad_kids3)) bad_kids3))
bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})} bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})}
d.addCallback(lambda ign: d.addCallback(lambda ign:
self.shouldFail(AssertionError, "bad_kids4", self.shouldFail(NotDeepImmutableError, "bad_kids4",
"create_immutable_directory requires immutable children", "is not immutable",
c.create_immutable_dirnode, c.create_immutable_dirnode,
bad_kids4)) bad_kids4))
d.addCallback(lambda ign: c.create_immutable_dirnode({})) d.addCallback(lambda ign: c.create_immutable_dirnode({}))
@ -177,7 +178,31 @@ class Dirnode(GridTestMixin, unittest.TestCase,
return dn.list() return dn.list()
d.addCallback(_created_small) d.addCallback(_created_small)
d.addCallback(lambda kids: self.failUnlessEqual(kids.keys(), [u"o"])) d.addCallback(lambda kids: self.failUnlessEqual(kids.keys(), [u"o"]))
# now test n.create_subdirectory(mutable=False)
d.addCallback(lambda ign: c.create_dirnode())
def _made_parent(n):
d = n.create_subdirectory(u"subdir", kids, mutable=False)
d.addCallback(lambda sd: sd.list())
d.addCallback(_check_kids)
d.addCallback(lambda ign: n.list())
d.addCallback(lambda children:
self.failUnlessEqual(children.keys(), [u"subdir"]))
d.addCallback(lambda ign: n.get(u"subdir"))
d.addCallback(lambda sd: sd.list())
d.addCallback(_check_kids)
d.addCallback(lambda ign: n.get(u"subdir"))
d.addCallback(lambda sd: self.failIf(sd.is_mutable()))
bad_kids = {u"one": (nm.create_from_cap(mut_writecap), {})}
d.addCallback(lambda ign:
self.shouldFail(NotDeepImmutableError, "YZ",
"is not immutable",
n.create_subdirectory,
u"sub2", bad_kids, mutable=False))
return d return d
d.addCallback(_made_parent)
return d
def test_check(self): def test_check(self):
self.basedir = "dirnode/Dirnode/test_check" self.basedir = "dirnode/Dirnode/test_check"
@ -972,6 +997,7 @@ class Packing(unittest.TestCase):
fn, kids, deep_immutable=True) fn, kids, deep_immutable=True)
class FakeMutableFile: class FakeMutableFile:
implements(IMutableFileNode)
counter = 0 counter = 0
def __init__(self, initial_contents=""): def __init__(self, initial_contents=""):
self.data = self._get_initial_contents(initial_contents) self.data = self._get_initial_contents(initial_contents)

View File

@ -714,7 +714,8 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
d.addCallback(lambda junk: self.clients[3].create_dirnode()) d.addCallback(lambda junk: self.clients[3].create_dirnode())
d.addCallback(check_kg_poolsize, -2) d.addCallback(check_kg_poolsize, -2)
# use_helper induces use of clients[3], which is the using-key_gen client # use_helper induces use of clients[3], which is the using-key_gen client
d.addCallback(lambda junk: self.POST("uri", use_helper=True, t="mkdir", name='george')) d.addCallback(lambda junk:
self.POST("uri?t=mkdir&name=george", use_helper=True))
d.addCallback(check_kg_poolsize, -3) d.addCallback(check_kg_poolsize, -3)
return d return d
@ -1053,10 +1054,6 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
return getPage(url, method="GET", followRedirect=followRedirect) return getPage(url, method="GET", followRedirect=followRedirect)
def POST(self, urlpath, followRedirect=False, use_helper=False, **fields): def POST(self, urlpath, followRedirect=False, use_helper=False, **fields):
if use_helper:
url = self.helper_webish_url + urlpath
else:
url = self.webish_url + urlpath
sepbase = "boogabooga" sepbase = "boogabooga"
sep = "--" + sepbase sep = "--" + sepbase
form = [] form = []
@ -1076,11 +1073,21 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
form.append(str(value)) form.append(str(value))
form.append(sep) form.append(sep)
form[-1] += "--" form[-1] += "--"
body = ""
headers = {}
if fields:
body = "\r\n".join(form) + "\r\n" body = "\r\n".join(form) + "\r\n"
headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase, headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
} return self.POST2(urlpath, body, headers, followRedirect, use_helper)
return getPage(url, method="POST", postdata=body,
headers=headers, followRedirect=followRedirect) def POST2(self, urlpath, body="", headers={}, followRedirect=False,
use_helper=False):
if use_helper:
url = self.helper_webish_url + urlpath
else:
url = self.webish_url + urlpath
return getPage(url, method="POST", postdata=body, headers=headers,
followRedirect=followRedirect)
def _test_web(self, res): def _test_web(self, res):
base = self.webish_url base = self.webish_url

View File

@ -281,7 +281,6 @@ class WebMixin(object):
return client.getPage(url, method="DELETE") return client.getPage(url, method="DELETE")
def POST(self, urlpath, followRedirect=False, **fields): def POST(self, urlpath, followRedirect=False, **fields):
url = self.webish_url + urlpath
sepbase = "boogabooga" sepbase = "boogabooga"
sep = "--" + sepbase sep = "--" + sepbase
form = [] form = []
@ -306,9 +305,15 @@ class WebMixin(object):
form.append(value) form.append(value)
form.append(sep) form.append(sep)
form[-1] += "--" form[-1] += "--"
body = ""
headers = {}
if fields:
body = "\r\n".join(form) + "\r\n" body = "\r\n".join(form) + "\r\n"
headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase, headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
} return self.POST2(urlpath, body, headers, followRedirect)
def POST2(self, urlpath, body="", headers={}, followRedirect=False):
url = self.webish_url + urlpath
return client.getPage(url, method="POST", postdata=body, return client.getPage(url, method="POST", postdata=body,
headers=headers, followRedirect=followRedirect) headers=headers, followRedirect=followRedirect)
@ -1125,8 +1130,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
def test_POST_NEWDIRURL_initial_children(self): def test_POST_NEWDIRURL_initial_children(self):
(newkids, filecap1, filecap2, filecap3, (newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children() dircap) = self._create_initial_children()
d = self.POST(self.public_url + "/foo/newdir?t=mkdir-with-children", d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
children=simplejson.dumps(newkids)) simplejson.dumps(newkids))
def _check(uri): def _check(uri):
n = self.s.create_node_from_uri(uri.strip()) n = self.s.create_node_from_uri(uri.strip())
d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
@ -1150,6 +1155,42 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
return d return d
def test_POST_NEWDIRURL_immutable(self):
(newkids, filecap1, immdircap) = self._create_immutable_children()
d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
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"dirchild-imm",
immdircap))
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)
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
d.addErrback(self.explain_web_error)
return d
def test_POST_NEWDIRURL_immutable_bad(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
"400 Bad Request",
"a mkdir-immutable operation was given a child that was not itself immutable",
self.POST2,
self.public_url + "/foo/newdir?t=mkdir-immutable",
simplejson.dumps(newkids))
return d
def test_PUT_NEWDIRURL_exists(self): def test_PUT_NEWDIRURL_exists(self):
d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "") d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
d.addCallback(lambda res: d.addCallback(lambda res:
@ -1898,8 +1939,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
def test_POST_mkdir_initial_children(self): def test_POST_mkdir_initial_children(self):
newkids, filecap1, ign, ign, ign = self._create_initial_children() newkids, filecap1, ign, ign, ign = self._create_initial_children()
d = self.POST(self.public_url + "/foo", t="mkdir-with-children", d = self.POST2(self.public_url +
name="newdir", children=simplejson.dumps(newkids)) "/foo?t=mkdir-with-children&name=newdir",
simplejson.dumps(newkids))
d.addCallback(lambda res: d.addCallback(lambda res:
self.failUnlessNodeHasChild(self._foo_node, u"newdir")) self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
d.addCallback(lambda res: self._foo_node.get(u"newdir")) d.addCallback(lambda res: self._foo_node.get(u"newdir"))
@ -1908,6 +1950,33 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
return d return d
def test_POST_mkdir_immutable(self):
(newkids, filecap1, immdircap) = self._create_immutable_children()
d = self.POST2(self.public_url +
"/foo?t=mkdir-immutable&name=newdir",
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)
d.addCallback(lambda res: self._foo_node.get(u"newdir"))
d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
return d
def test_POST_mkdir_immutable_bad(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
"400 Bad Request",
"a mkdir-immutable operation was given a child that was not itself immutable",
self.POST2,
self.public_url +
"/foo?t=mkdir-immutable&name=newdir",
simplejson.dumps(newkids))
return d
def test_POST_mkdir_2(self): def test_POST_mkdir_2(self):
d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "") d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
d.addCallback(lambda res: d.addCallback(lambda res:
@ -1957,11 +2026,23 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
} }
return newkids, filecap1, filecap2, filecap3, dircap return newkids, filecap1, filecap2, filecap3, dircap
def _create_immutable_children(self):
contents, n, filecap1 = self.makefile(12)
md1 = {"metakey1": "metavalue1"}
tnode = create_chk_filenode("immutable directory contents\n"*10)
dnode = DirectoryNode(tnode, None, None)
assert not dnode.is_mutable()
immdircap = dnode.get_uri()
newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
"metadata": md1, }],
u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
}
return newkids, filecap1, immdircap
def test_POST_mkdir_no_parentdir_initial_children(self): def test_POST_mkdir_no_parentdir_initial_children(self):
(newkids, filecap1, filecap2, filecap3, (newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children() dircap) = self._create_initial_children()
d = self.POST("/uri?t=mkdir-with-children", d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
children=simplejson.dumps(newkids))
def _after_mkdir(res): def _after_mkdir(res):
self.failUnless(res.startswith("URI:DIR"), res) self.failUnless(res.startswith("URI:DIR"), res)
n = self.s.create_node_from_uri(res) n = self.s.create_node_from_uri(res)
@ -1989,8 +2070,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
400, "Bad Request", 400, "Bad Request",
"t=mkdir does not accept children=, " "t=mkdir does not accept children=, "
"try t=mkdir-with-children instead", "try t=mkdir-with-children instead",
self.POST, "/uri?t=mkdir", # without children self.POST2, "/uri?t=mkdir", # without children
children=simplejson.dumps(newkids)) simplejson.dumps(newkids))
return d return d
def test_POST_noparent_bad(self): def test_POST_noparent_bad(self):
@ -2000,6 +2081,34 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
self.POST, "/uri?t=bogus") self.POST, "/uri?t=bogus")
return d return d
def test_POST_mkdir_no_parentdir_immutable(self):
(newkids, filecap1, immdircap) = self._create_immutable_children()
d = self.POST2("/uri?t=mkdir-immutable", 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"dirchild-imm",
immdircap))
return d2
d.addCallback(_after_mkdir)
return d
def test_POST_mkdir_no_parentdir_immutable_bad(self):
(newkids, filecap1, filecap2, filecap3,
dircap) = self._create_initial_children()
d = self.shouldFail2(error.Error,
"test_POST_mkdir_no_parentdir_immutable_bad",
"400 Bad Request",
"a mkdir-immutable operation was given a child that was not itself immutable",
self.POST2,
"/uri?t=mkdir-immutable",
simplejson.dumps(newkids))
return d
def test_welcome_page_mkdir_button(self): def test_welcome_page_mkdir_button(self):
# Fetch the welcome page. # Fetch the welcome page.
d = self.GET("/") d = self.GET("/")

View File

@ -7,7 +7,8 @@ from nevow import loaders, appserver
from nevow.inevow import IRequest from nevow.inevow import IRequest
from nevow.util import resource_filename from nevow.util import resource_filename
from allmydata.interfaces import ExistingChildError, NoSuchChildError, \ from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
FileTooLargeError, NotEnoughSharesError, NoSharesError FileTooLargeError, NotEnoughSharesError, NoSharesError, \
NotDeepImmutableError
from allmydata.mutable.common import UnrecoverableFileError from allmydata.mutable.common import UnrecoverableFileError
from allmydata.util import abbreviate # TODO: consolidate from allmydata.util import abbreviate # TODO: consolidate
@ -57,8 +58,9 @@ def get_arg(ctx_or_req, argname, default=None, multiple=False):
def convert_children_json(nodemaker, children_json): def convert_children_json(nodemaker, children_json):
"""I convert the JSON output of GET?t=json into the dict-of-nodes input """I convert the JSON output of GET?t=json into the dict-of-nodes input
to both dirnode.create_subdirectory() and to both dirnode.create_subdirectory() and
client.create_directory(initial_children=).""" client.create_directory(initial_children=). This is used by
initial_children = {} t=mkdir-with-children and t=mkdir-immutable"""
children = {}
if children_json: if children_json:
data = simplejson.loads(children_json) data = simplejson.loads(children_json)
for (name, (ctype, propdict)) in data.iteritems(): for (name, (ctype, propdict)) in data.iteritems():
@ -71,8 +73,8 @@ def convert_children_json(nodemaker, children_json):
readcap = str(readcap) readcap = str(readcap)
metadata = propdict.get("metadata", {}) metadata = propdict.get("metadata", {})
childnode = nodemaker.create_from_cap(writecap, readcap) childnode = nodemaker.create_from_cap(writecap, readcap)
initial_children[name] = (childnode, metadata) children[name] = (childnode, metadata)
return initial_children return children
def abbreviate_time(data): def abbreviate_time(data):
# 1.23s, 790ms, 132us # 1.23s, 790ms, 132us
@ -176,6 +178,10 @@ def humanize_failure(f):
"failure, or disk corruption. You should perform a filecheck on " "failure, or disk corruption. You should perform a filecheck on "
"this object to learn more.") "this object to learn more.")
return (t, http.GONE) return (t, http.GONE)
if f.check(NotDeepImmutableError):
t = ("NotDeepImmutableError: a mkdir-immutable operation was given "
"a child that was not itself immutable: %s" % (f.value,))
return (t, http.BAD_REQUEST)
if f.check(WebError): if f.check(WebError):
return (f.value.text, f.value.code) return (f.value.text, f.value.code)
if f.check(FileTooLargeError): if f.check(FileTooLargeError):

View File

@ -94,15 +94,21 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
if DEBUG: print " terminal" if DEBUG: print " terminal"
# terminal node # terminal node
if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir"), if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir"),
("POST", "mkdir-with-children") ]: ("POST", "mkdir-with-children"),
("POST", "mkdir-immutable") ]:
if DEBUG: print " making final directory" if DEBUG: print " making final directory"
# final directory # final directory
kids = {} kids = {}
if (method,t) == ("POST", "mkdir-with-children"): if t in ("mkdir-with-children", "mkdir-immutable"):
kids_json = get_arg(req, "children", "") req.content.seek(0)
kids_json = req.content.read()
kids = convert_children_json(self.client.nodemaker, kids = convert_children_json(self.client.nodemaker,
kids_json) kids_json)
d = self.node.create_subdirectory(name, kids) mutable = True
if t == "mkdir-immutable":
mutable = False
d = self.node.create_subdirectory(name, kids,
mutable=mutable)
d.addCallback(make_handler_for, d.addCallback(make_handler_for,
self.client, self.node, name) self.client, self.node, name)
return d return d
@ -186,6 +192,8 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
d = self._POST_mkdir(req) d = self._POST_mkdir(req)
elif t == "mkdir-with-children": elif t == "mkdir-with-children":
d = self._POST_mkdir_with_children(req) d = self._POST_mkdir_with_children(req)
elif t == "mkdir-immutable":
d = self._POST_mkdir_immutable(req)
elif t == "mkdir-p": elif t == "mkdir-p":
# TODO: docs, tests # TODO: docs, tests
d = self._POST_mkdir_p(req) d = self._POST_mkdir_p(req)
@ -242,12 +250,28 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
return defer.succeed(self.node.get_uri()) # TODO: urlencode return defer.succeed(self.node.get_uri()) # TODO: urlencode
name = name.decode("utf-8") name = name.decode("utf-8")
replace = boolean_of_arg(get_arg(req, "replace", "true")) replace = boolean_of_arg(get_arg(req, "replace", "true"))
kids_json = get_arg(req, "children", "") req.content.seek(0)
kids_json = req.content.read()
kids = convert_children_json(self.client.nodemaker, kids_json) kids = convert_children_json(self.client.nodemaker, kids_json)
d = self.node.create_subdirectory(name, kids, overwrite=replace) d = self.node.create_subdirectory(name, kids, overwrite=replace)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d return d
def _POST_mkdir_immutable(self, req):
name = get_arg(req, "name", "")
if not name:
# our job is done, it was handled by the code in got_child
# which created the final directory (i.e. us)
return defer.succeed(self.node.get_uri()) # TODO: urlencode
name = name.decode("utf-8")
replace = boolean_of_arg(get_arg(req, "replace", "true"))
req.content.seek(0)
kids_json = req.content.read()
kids = convert_children_json(self.client.nodemaker, kids_json)
d = self.node.create_subdirectory(name, kids, mutable=False)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d
def _POST_mkdir_p(self, req): def _POST_mkdir_p(self, req):
path = get_arg(req, "path") path = get_arg(req, "path")
if not path: if not path:

View File

@ -73,6 +73,9 @@ class URIHandler(RenderMixin, rend.Page):
elif t == "mkdir-with-children": elif t == "mkdir-with-children":
return unlinked.POSTUnlinkedCreateDirectoryWithChildren(req, return unlinked.POSTUnlinkedCreateDirectoryWithChildren(req,
self.client) self.client)
elif t == "mkdir-immutable":
return unlinked.POSTUnlinkedCreateImmutableDirectory(req,
self.client)
errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, " errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
"and POST?t=mkdir") "and POST?t=mkdir")
raise WebError(errmsg, http.BAD_REQUEST) raise WebError(errmsg, http.BAD_REQUEST)

View File

@ -91,8 +91,9 @@ def POSTUnlinkedSSK(req, client):
def POSTUnlinkedCreateDirectory(req, client): def POSTUnlinkedCreateDirectory(req, client):
# "POST /uri?t=mkdir", to create an unlinked directory. # "POST /uri?t=mkdir", to create an unlinked directory.
kids_json = get_arg(req, "children", None) req.content.seek(0)
if kids_json is not None: kids_json = req.content.read()
if kids_json:
raise WebError("t=mkdir does not accept children=, " raise WebError("t=mkdir does not accept children=, "
"try t=mkdir-with-children instead", "try t=mkdir-with-children instead",
http.BAD_REQUEST) http.BAD_REQUEST)
@ -112,7 +113,8 @@ def POSTUnlinkedCreateDirectory(req, client):
def POSTUnlinkedCreateDirectoryWithChildren(req, client): def POSTUnlinkedCreateDirectoryWithChildren(req, client):
# "POST /uri?t=mkdir", to create an unlinked directory. # "POST /uri?t=mkdir", to create an unlinked directory.
kids_json = get_arg(req, "children", "") req.content.seek(0)
kids_json = req.content.read()
kids = convert_children_json(client.nodemaker, kids_json) kids = convert_children_json(client.nodemaker, kids_json)
d = client.create_dirnode(initial_children=kids) d = client.create_dirnode(initial_children=kids)
redirect = get_arg(req, "redirect_to_result", "false") redirect = get_arg(req, "redirect_to_result", "false")
@ -128,3 +130,21 @@ def POSTUnlinkedCreateDirectoryWithChildren(req, client):
d.addCallback(lambda dirnode: dirnode.get_uri()) d.addCallback(lambda dirnode: dirnode.get_uri())
return d return d
def POSTUnlinkedCreateImmutableDirectory(req, client):
# "POST /uri?t=mkdir", to create an unlinked directory.
req.content.seek(0)
kids_json = req.content.read()
kids = convert_children_json(client.nodemaker, kids_json)
d = client.create_immutable_dirnode(kids)
redirect = get_arg(req, "redirect_to_result", "false")
if boolean_of_arg(redirect):
def _then_redir(res):
new_url = "uri/" + urllib.quote(res.get_uri())
req.setResponseCode(http.SEE_OTHER) # 303
req.setHeader('location', new_url)
req.finish()
return ''
d.addCallback(_then_redir)
else:
d.addCallback(lambda dirnode: dirnode.get_uri())
return d