dirnode.pack_children(): add deep_immutable= argument
authorBrian Warner <warner@lothar.com>
Mon, 26 Oct 2009 16:28:09 +0000 (09:28 -0700)
committerBrian Warner <warner@lothar.com>
Mon, 26 Oct 2009 16:28:09 +0000 (09:28 -0700)
This will be used by DIR2:CHK to enforce the deep-immutability requirement.

src/allmydata/dirnode.py
src/allmydata/test/test_dirnode.py

index ce05477ba6856838740ca64451c0e0c230c1d000..de271551a4c7634437a476817d0dd10bf8d904cb 100644 (file)
@@ -135,23 +135,35 @@ def _encrypt_rwcap(filenode, rwcap):
     # The MAC is not checked by readers in Tahoe >= 1.3.0, but we still
     # produce it for the sake of older readers.
 
-def pack_children(filenode, children):
+class MustBeDeepImmutable(Exception):
+    """You tried to add a non-deep-immutable node to a deep-immutable
+    directory."""
+
+def pack_children(filenode, children, deep_immutable=False):
     """Take a dict that maps:
          children[unicode_name] = (IFileSystemNode, metadata_dict)
     and pack it into a single string, for use as the contents of the backing
     file. This is the same format as is returned by _unpack_contents. I also
     accept an AuxValueDict, in which case I'll use the auxilliary cached data
     as the pre-packed entry, which is faster than re-packing everything each
-    time."""
+    time.
+
+    If deep_immutable is True, I will require that all my children are deeply
+    immutable, and will raise a MustBeDeepImmutable exception if not.
+    """
+
     has_aux = isinstance(children, AuxValueDict)
     entries = []
     for name in sorted(children.keys()):
         assert isinstance(name, unicode)
         entry = None
+        (child, metadata) = children[name]
+        if deep_immutable and child.is_mutable():
+            # TODO: consider adding IFileSystemNode.is_deep_immutable()
+            raise MustBeDeepImmutable("child '%s' is mutable" % (name,))
         if has_aux:
             entry = children.get_aux(name)
         if not entry:
-            (child, metadata) = children[name]
             assert IFilesystemNode.providedBy(child), (name,child)
             assert isinstance(metadata, dict)
             rwcap = child.get_uri() # might be RO if the child is not writeable
index e4116567fc08f090647e7467a9e596f657792d15..13e7ac47cfd67a38d17316acfb74c660c05cc514 100644 (file)
@@ -760,6 +760,10 @@ class Dirnode(GridTestMixin, unittest.TestCase,
         d.addCallback(_then)
         return d
 
+class MinimalFakeMutableFile:
+    def get_writekey(self):
+        return "writekey"
+
 class Packing(unittest.TestCase):
     # This is a base32-encoded representation of the directory tree
     # root/file1
@@ -823,6 +827,54 @@ class Packing(unittest.TestCase):
         self.failUnlessEqual(file1_rwcap,
                              children[u'file1'][0].get_uri())
 
+    def _make_kids(self, nm, which):
+        caps = {"imm": "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861",
+                "lit": "URI:LIT:n5xgk", # LIT for "one"
+                "write": "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq",
+                "read": "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q",
+                "dirwrite": "URI:DIR2:n6x24zd3seu725yluj75q5boaa:mm6yoqjhl6ueh7iereldqxue4nene4wl7rqfjfybqrehdqmqskvq",
+                "dirread":  "URI:DIR2-RO:b7sr5qsifnicca7cbk3rhrhbvq:mm6yoqjhl6ueh7iereldqxue4nene4wl7rqfjfybqrehdqmqskvq",
+                }
+        kids = {}
+        for name in which:
+            kids[unicode(name)] = (nm.create_from_cap(caps[name]), {})
+        return kids
+
+    def test_deep_immutable(self):
+        nm = NodeMaker(None, None, None, None, None, None, {"k": 3, "n": 10},
+                       None)
+        fn = MinimalFakeMutableFile()
+
+        kids = self._make_kids(nm, ["imm", "lit", "write", "read",
+                                    "dirwrite", "dirread"])
+        packed = dirnode.pack_children(fn, kids, deep_immutable=False)
+        self.failUnlessIn("lit", packed)
+
+        kids = self._make_kids(nm, ["imm", "lit"])
+        packed = dirnode.pack_children(fn, kids, deep_immutable=True)
+        self.failUnlessIn("lit", packed)
+
+        kids = self._make_kids(nm, ["imm", "lit", "write"])
+        e = self.failUnlessRaises(dirnode.MustBeDeepImmutable,
+                                  dirnode.pack_children,
+                                  fn, kids, deep_immutable=True)
+
+        # read-only is not enough: all children must be immutable
+        kids = self._make_kids(nm, ["imm", "lit", "read"])
+        e = self.failUnlessRaises(dirnode.MustBeDeepImmutable,
+                                  dirnode.pack_children,
+                                  fn, kids, deep_immutable=True)
+
+        kids = self._make_kids(nm, ["imm", "lit", "dirwrite"])
+        e = self.failUnlessRaises(dirnode.MustBeDeepImmutable,
+                                  dirnode.pack_children,
+                                  fn, kids, deep_immutable=True)
+
+        kids = self._make_kids(nm, ["imm", "lit", "dirread"])
+        e = self.failUnlessRaises(dirnode.MustBeDeepImmutable,
+                                  dirnode.pack_children,
+                                  fn, kids, deep_immutable=True)
+
 class FakeMutableFile:
     counter = 0
     def __init__(self, initial_contents=""):