From: Brian Warner Date: Wed, 18 Nov 2009 07:09:00 +0000 (-0800) Subject: Add t=mkdir-immutable to the webapi. Closes #607. X-Git-Tag: trac-4100 X-Git-Url: https://git.rkrishnan.org/pf/content/it.html?a=commitdiff_plain;h=f85690697a21e6690830cd6058d88698bbe43548;p=tahoe-lafs%2Ftahoe-lafs.git 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 --- diff --git a/docs/frontends/webapi.txt b/docs/frontends/webapi.txt index 7a6abbb9..b43d836c 100644 --- a/docs/frontends/webapi.txt +++ b/docs/frontends/webapi.txt @@ -356,17 +356,15 @@ POST /uri?t=mkdir-with-children 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. - Initial children are provided in the "children" field of the POST form. 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: + Initial children are provided as the body of the POST form (this is more + efficient than doing separate mkdir and set_children operations). If the + 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 + 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", { @@ -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 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 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 - the /uri?t=mkdir-with-children operation. + initial children from the POST request body, as described above in the + /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 @@ -425,8 +442,15 @@ POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&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 - the POST 'children' form field, as described in /uri?t=mkdir-with-children - above. + the POST request body, as described in /uri?t=mkdir-with-children 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) === diff --git a/src/allmydata/client.py b/src/allmydata/client.py index 1cf1af57..7bab5577 100644 --- a/src/allmydata/client.py +++ b/src/allmydata/client.py @@ -465,9 +465,8 @@ 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_immutable_dirnode(self, children, convergence=None): + return self.nodemaker.create_immutable_directory(children, convergence) def create_mutable_file(self, contents=None, keysize=None): return self.nodemaker.create_mutable_file(contents, keysize) diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index c8ad5db2..ddab1cc2 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -188,6 +188,8 @@ class DirectoryNode: filenode_class = MutableFileNode def __init__(self, filenode, nodemaker, uploader): + assert IFileNode.providedBy(filenode), filenode + assert not IDirectoryNode.providedBy(filenode), filenode self._node = filenode filenode_cap = filenode.get_cap() self._uri = wrap_dirnode_cap(filenode_cap) @@ -491,11 +493,15 @@ class DirectoryNode: d.addCallback(lambda res: deleter.old_child) 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) if self.is_readonly(): return defer.fail(NotMutableError()) - d = self._nodemaker.create_new_mutable_directory(initial_children) + if mutable: + d = self._nodemaker.create_new_mutable_directory(initial_children) + else: + d = self._nodemaker.create_immutable_directory(initial_children) def _created(child): entries = {name: (child, None)} a = Adder(self, entries, overwrite=overwrite) diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index ece332c3..769cc028 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -480,6 +480,9 @@ class UnhandledCapTypeError(Exception): """I recognize the cap/URI, but I cannot create an IFilesystemNode for it.""" +class NotDeepImmutableError(Exception): + """Deep-immutable directories can only contain deep-immutable children""" + class IFilesystemNode(Interface): def get_cap(): """Return the strongest 'cap instance' associated with this node. diff --git a/src/allmydata/nodemaker.py b/src/allmydata/nodemaker.py index 63cc18ec..db49d362 100644 --- a/src/allmydata/nodemaker.py +++ b/src/allmydata/nodemaker.py @@ -1,7 +1,7 @@ import weakref from zope.interface import implements 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.upload import Data from allmydata.mutable.filenode import MutableFileNode @@ -101,14 +101,16 @@ class NodeMaker: d.addCallback(self._create_dirnode) 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(): 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) + if node.is_mutable(): + raise NotDeepImmutableError("%s is not immutable" % (node,)) n = DummyImmutableFileNode() # writekey=None packed = pack_children(n, children) uploadable = Data(packed, convergence) diff --git a/src/allmydata/test/common.py b/src/allmydata/test/common.py index 5d31e5f9..d0580750 100644 --- a/src/allmydata/test/common.py +++ b/src/allmydata/test/common.py @@ -47,6 +47,8 @@ class FakeCHKFileNode: return self.my_uri.to_string() def get_readonly_uri(self): return self.my_uri.to_string() + def get_cap(self): + return self.my_uri def get_verify_cap(self): return self.my_uri.get_verify_cap() def get_repair_cap(self): diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py index 8d1f1689..65f781ba 100644 --- a/src/allmydata/test/test_dirnode.py +++ b/src/allmydata/test/test_dirnode.py @@ -1,12 +1,13 @@ import time +from zope.interface import implements from twisted.trial import unittest from twisted.internet import defer from allmydata import uri, dirnode from allmydata.client import Client from allmydata.immutable import upload -from allmydata.interfaces import IFileNode, \ - ExistingChildError, NoSuchChildError, \ +from allmydata.interfaces import IFileNode, IMutableFileNode, \ + ExistingChildError, NoSuchChildError, NotDeepImmutableError, \ IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError from allmydata.mutable.filenode import MutableFileNode from allmydata.mutable.common import UncoordinatedWriteError @@ -137,14 +138,14 @@ class Dirnode(GridTestMixin, unittest.TestCase, 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", + self.shouldFail(NotDeepImmutableError, "bad_kids3", + "is not immutable", 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", + self.shouldFail(NotDeepImmutableError, "bad_kids4", + "is not immutable", c.create_immutable_dirnode, bad_kids4)) d.addCallback(lambda ign: c.create_immutable_dirnode({})) @@ -177,8 +178,32 @@ class Dirnode(GridTestMixin, unittest.TestCase, return dn.list() d.addCallback(_created_small) 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 + d.addCallback(_made_parent) return d + def test_check(self): self.basedir = "dirnode/Dirnode/test_check" self.set_up_grid() @@ -972,6 +997,7 @@ class Packing(unittest.TestCase): fn, kids, deep_immutable=True) class FakeMutableFile: + implements(IMutableFileNode) counter = 0 def __init__(self, initial_contents=""): self.data = self._get_initial_contents(initial_contents) diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index f8fcf0ed..6fd99a14 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -714,7 +714,8 @@ class SystemTest(SystemTestMixin, unittest.TestCase): d.addCallback(lambda junk: self.clients[3].create_dirnode()) d.addCallback(check_kg_poolsize, -2) # 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) return d @@ -1053,10 +1054,6 @@ class SystemTest(SystemTestMixin, unittest.TestCase): return getPage(url, method="GET", followRedirect=followRedirect) 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" sep = "--" + sepbase form = [] @@ -1076,11 +1073,21 @@ class SystemTest(SystemTestMixin, unittest.TestCase): form.append(str(value)) form.append(sep) form[-1] += "--" - body = "\r\n".join(form) + "\r\n" - headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase, - } - return getPage(url, method="POST", postdata=body, - headers=headers, followRedirect=followRedirect) + body = "" + headers = {} + if fields: + body = "\r\n".join(form) + "\r\n" + headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase + return self.POST2(urlpath, body, headers, followRedirect, use_helper) + + 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): base = self.webish_url diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py index a2f52b1b..c96a8196 100644 --- a/src/allmydata/test/test_web.py +++ b/src/allmydata/test/test_web.py @@ -281,7 +281,6 @@ class WebMixin(object): return client.getPage(url, method="DELETE") def POST(self, urlpath, followRedirect=False, **fields): - url = self.webish_url + urlpath sepbase = "boogabooga" sep = "--" + sepbase form = [] @@ -306,9 +305,15 @@ class WebMixin(object): form.append(value) form.append(sep) form[-1] += "--" - body = "\r\n".join(form) + "\r\n" - headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase, - } + body = "" + headers = {} + if fields: + body = "\r\n".join(form) + "\r\n" + 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, headers=headers, followRedirect=followRedirect) @@ -1125,8 +1130,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase): 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-with-children", - children=simplejson.dumps(newkids)) + d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children", + simplejson.dumps(newkids)) def _check(uri): n = self.s.create_node_from_uri(uri.strip()) 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) 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): d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "") d.addCallback(lambda res: @@ -1898,8 +1939,9 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase): 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-with-children", - name="newdir", children=simplejson.dumps(newkids)) + d = self.POST2(self.public_url + + "/foo?t=mkdir-with-children&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")) @@ -1908,6 +1950,33 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase): d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) 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): d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "") d.addCallback(lambda res: @@ -1957,11 +2026,23 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase): } 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): (newkids, filecap1, filecap2, filecap3, dircap) = self._create_initial_children() - d = self.POST("/uri?t=mkdir-with-children", - children=simplejson.dumps(newkids)) + d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids)) def _after_mkdir(res): self.failUnless(res.startswith("URI:DIR"), res) n = self.s.create_node_from_uri(res) @@ -1989,8 +2070,8 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase): 400, "Bad Request", "t=mkdir does not accept children=, " "try t=mkdir-with-children instead", - self.POST, "/uri?t=mkdir", # without children - children=simplejson.dumps(newkids)) + self.POST2, "/uri?t=mkdir", # without children + simplejson.dumps(newkids)) return d def test_POST_noparent_bad(self): @@ -2000,6 +2081,34 @@ class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase): self.POST, "/uri?t=bogus") 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): # Fetch the welcome page. d = self.GET("/") diff --git a/src/allmydata/web/common.py b/src/allmydata/web/common.py index 5089367f..a9bfa8c2 100644 --- a/src/allmydata/web/common.py +++ b/src/allmydata/web/common.py @@ -7,7 +7,8 @@ from nevow import loaders, appserver from nevow.inevow import IRequest from nevow.util import resource_filename from allmydata.interfaces import ExistingChildError, NoSuchChildError, \ - FileTooLargeError, NotEnoughSharesError, NoSharesError + FileTooLargeError, NotEnoughSharesError, NoSharesError, \ + NotDeepImmutableError from allmydata.mutable.common import UnrecoverableFileError 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): """I convert the JSON output of GET?t=json into the dict-of-nodes input to both dirnode.create_subdirectory() and - client.create_directory(initial_children=).""" - initial_children = {} + client.create_directory(initial_children=). This is used by + t=mkdir-with-children and t=mkdir-immutable""" + children = {} if children_json: data = simplejson.loads(children_json) for (name, (ctype, propdict)) in data.iteritems(): @@ -71,8 +73,8 @@ def convert_children_json(nodemaker, children_json): readcap = str(readcap) metadata = propdict.get("metadata", {}) childnode = nodemaker.create_from_cap(writecap, readcap) - initial_children[name] = (childnode, metadata) - return initial_children + children[name] = (childnode, metadata) + return children def abbreviate_time(data): # 1.23s, 790ms, 132us @@ -176,6 +178,10 @@ def humanize_failure(f): "failure, or disk corruption. You should perform a filecheck on " "this object to learn more.") 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): return (f.value.text, f.value.code) if f.check(FileTooLargeError): diff --git a/src/allmydata/web/directory.py b/src/allmydata/web/directory.py index 0e5b5d60..e554479b 100644 --- a/src/allmydata/web/directory.py +++ b/src/allmydata/web/directory.py @@ -94,15 +94,21 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin): if DEBUG: print " terminal" # terminal node 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" # final directory kids = {} - if (method,t) == ("POST", "mkdir-with-children"): - kids_json = get_arg(req, "children", "") + if t in ("mkdir-with-children", "mkdir-immutable"): + 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 = True + if t == "mkdir-immutable": + mutable = False + d = self.node.create_subdirectory(name, kids, + mutable=mutable) d.addCallback(make_handler_for, self.client, self.node, name) return d @@ -186,6 +192,8 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin): d = self._POST_mkdir(req) elif t == "mkdir-with-children": d = self._POST_mkdir_with_children(req) + elif t == "mkdir-immutable": + d = self._POST_mkdir_immutable(req) elif t == "mkdir-p": # TODO: docs, tests 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 name = name.decode("utf-8") 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) d = self.node.create_subdirectory(name, kids, overwrite=replace) d.addCallback(lambda child: child.get_uri()) # TODO: urlencode 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): path = get_arg(req, "path") if not path: diff --git a/src/allmydata/web/root.py b/src/allmydata/web/root.py index 97773bd6..04e4b112 100644 --- a/src/allmydata/web/root.py +++ b/src/allmydata/web/root.py @@ -73,6 +73,9 @@ class URIHandler(RenderMixin, rend.Page): elif t == "mkdir-with-children": return unlinked.POSTUnlinkedCreateDirectoryWithChildren(req, self.client) + elif t == "mkdir-immutable": + return unlinked.POSTUnlinkedCreateImmutableDirectory(req, + self.client) errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, " "and POST?t=mkdir") raise WebError(errmsg, http.BAD_REQUEST) diff --git a/src/allmydata/web/unlinked.py b/src/allmydata/web/unlinked.py index 71472da3..70faab96 100644 --- a/src/allmydata/web/unlinked.py +++ b/src/allmydata/web/unlinked.py @@ -91,8 +91,9 @@ def POSTUnlinkedSSK(req, client): def POSTUnlinkedCreateDirectory(req, client): # "POST /uri?t=mkdir", to create an unlinked directory. - kids_json = get_arg(req, "children", None) - if kids_json is not None: + req.content.seek(0) + kids_json = req.content.read() + if kids_json: raise WebError("t=mkdir does not accept children=, " "try t=mkdir-with-children instead", http.BAD_REQUEST) @@ -112,7 +113,8 @@ def POSTUnlinkedCreateDirectory(req, client): def POSTUnlinkedCreateDirectoryWithChildren(req, client): # "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) d = client.create_dirnode(initial_children=kids) redirect = get_arg(req, "redirect_to_result", "false") @@ -128,3 +130,21 @@ def POSTUnlinkedCreateDirectoryWithChildren(req, client): d.addCallback(lambda dirnode: dirnode.get_uri()) 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