Add t=mkdir-immutable to the webapi. Closes #607. trac-4100
authorBrian Warner <warner@lothar.com>
Wed, 18 Nov 2009 07:09:00 +0000 (23:09 -0800)
committerBrian Warner <warner@lothar.com>
Wed, 18 Nov 2009 07:09:00 +0000 (23:09 -0800)
* 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

13 files changed:
docs/frontends/webapi.txt
src/allmydata/client.py
src/allmydata/dirnode.py
src/allmydata/interfaces.py
src/allmydata/nodemaker.py
src/allmydata/test/common.py
src/allmydata/test/test_dirnode.py
src/allmydata/test/test_system.py
src/allmydata/test/test_web.py
src/allmydata/web/common.py
src/allmydata/web/directory.py
src/allmydata/web/root.py
src/allmydata/web/unlinked.py

index 7a6abbb959d8ea8c13360248dbe72b630acd04df..b43d836ca8941fc14688f9c488ca1b13e6aa82b5 100644 (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
  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) ===
 
index 1cf1af577da28448678d4eb0adaac3e1db4ba5ac..7bab5577faa12872db062e2077ab5d20e2ac5e04 100644 (file)
@@ -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)
index c8ad5db2b666a63cfe79a1ad1f5c4c17b6adbac4..ddab1cc2b718cc33a5a70ca17622a57e19bc0bdb 100644 (file)
@@ -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)
index ece332c3045333394123970bb83cd28082b32b2b..769cc0289bae4b6a9d1120d0ccc8d1115bcef964 100644 (file)
@@ -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.
index 63cc18ec822c7947735e5acc362db253cd8d1310..db49d3623a317184f5fb7e77bc63ed562bcf072a 100644 (file)
@@ -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)
index 5d31e5f93a1681a7948d900cbf36de3c2b9546df..d058075048867fda289d6d5a42b5f37576fc4f7b 100644 (file)
@@ -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):
index 8d1f1689111b483ebc0cd9051e976abc69295dbd..65f781ba855494992dd010282069081d6366bbd4 100644 (file)
@@ -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)
index f8fcf0ed3254a4c0ddcbd0dd20f87d73569fcced..6fd99a14d247600a51ba6e201eebb253f9d539ec 100644 (file)
@@ -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
index a2f52b1bc86659f1567a99d05e1624d061ff41ae..c96a81960db865b34561acc3b72b31a8df9400ed 100644 (file)
@@ -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("/")
index 5089367f246d20618d7dcfe2cb54bc05dcde7e8d..a9bfa8c2ef2e73685b2749d25a96be5df11791fb 100644 (file)
@@ -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):
index 0e5b5d60428026bb9344e1d9b2b18555bd9b31ce..e554479b77c5db5852b16ec981150cb30cfdfdbf 100644 (file)
@@ -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:
index 97773bd6b2c009e06e56004f94bc88406198d593..04e4b1127005562281523c27ad7a3627a08b4b6e 100644 (file)
@@ -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)
index 71472da3ca0c7459110e919a623b6dcb8fe7496d..70faab963e07dee02302bfbfd6eaf2629df69f17 100644 (file)
@@ -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