From: david-sarah <david-sarah@jacaranda.org>
Date: Wed, 2 Jun 2010 03:26:41 +0000 (-0700)
Subject: dirnode.py: fix a bug in the no-write change for Adder, and improve test coverage... 
X-Git-Url: https://git.rkrishnan.org/pf/content/en/service/reedownlee?a=commitdiff_plain;h=29a06457d25bcea84ffc205fd534139d77783a22;p=tahoe-lafs%2Ftahoe-lafs.git

dirnode.py: fix a bug in the no-write change for Adder, and improve test coverage. Add a 'metadata' argument to create_subdirectory, with documentation. Also update some comments in test_dirnode.py made stale by the ctime/mtime change.
---

diff --git a/docs/frontends/webapi.txt b/docs/frontends/webapi.txt
index 9678e010..9d5afd81 100644
--- a/docs/frontends/webapi.txt
+++ b/docs/frontends/webapi.txt
@@ -415,9 +415,11 @@ POST /uri?t=mkdir-with-children
  it is acceptable to set "rw_uri" to that cap and omit "ro_uri". The
  client must not put a write cap into a "ro_uri" field.
 
- A file may have a "no-write" metadata field that affects how writes to
- it are handled via the SFTP frontend; see docs/frontends/FTP-and-SFTP.txt
- for details.
+ The metadata may have a "no-write" field. If this is set to true in the
+ metadata of a link, it will not be possible to open that link for writing
+ via the SFTP frontend; see docs/frontends/FTP-and-SFTP.txt for details.
+ Also, if the "no-write" field is set to true in the metadata of a link to
+ a mutable child, it will cause the link to be diminished to read-only.
 
  Note that the webapi-using client application must not provide the
  "Content-Type: multipart/form-data" header that usually accompanies HTML
@@ -669,7 +671,9 @@ GET /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=json
 
   In Tahoe earlier than v1.4.0, only the 'mtime'/'ctime' keys were populated.
   Starting in Tahoe v1.4.0, the 'linkmotime'/'linkcrtime' keys in the 'tahoe'
-  sub-dict are also populated.
+  sub-dict are also populated. However, prior to v1.7.0, a bug caused the
+  'tahoe' sub-dict to be deleted by webapi requests in which new metadata
+  is specified, and not to be added to existing child links that lack it.
 
   The reason we added the new values in Tahoe v1.4.0 is that there is a
   "set_children" API (described below) which you can use to overwrite the
diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py
index bfc151a3..6150fb23 100644
--- a/src/allmydata/dirnode.py
+++ b/src/allmydata/dirnode.py
@@ -122,9 +122,10 @@ class MetadataSetter:
             raise NoSuchChildError(name)
 
         now = time.time()
-        metadata = update_metadata(children[name][1].copy(), self.metadata, now)
         child = children[name][0]
-        if self.create_readonly_node and metadata and metadata.get('no-write', False):
+
+        metadata = update_metadata(children[name][1].copy(), self.metadata, now)
+        if self.create_readonly_node and metadata.get('no-write', False):
             child = self.create_readonly_node(child, name)
 
         children[name] = (child, metadata)
@@ -167,10 +168,11 @@ class Adder:
                     raise ExistingChildError("child '%s' already exists" % name)
                 metadata = children[name][1].copy()
 
-            if self.create_readonly_node and metadata and metadata.get('no-write', False):
+            metadata = update_metadata(metadata, new_metadata, now)
+            if self.create_readonly_node and metadata.get('no-write', False):
                 child = self.create_readonly_node(child, name)
 
-            children[name] = (child, update_metadata(metadata, new_metadata, now))
+            children[name] = (child, metadata)
         new_contents = self.node._pack_contents(children)
         return new_contents
 
@@ -591,7 +593,7 @@ class DirectoryNode:
         return d
 
     def create_subdirectory(self, name, initial_children={}, overwrite=True,
-                            mutable=True):
+                            mutable=True, metadata=None):
         assert isinstance(name, unicode)
         if self.is_readonly():
             return defer.fail(NotWriteableError())
@@ -600,7 +602,7 @@ class DirectoryNode:
         else:
             d = self._nodemaker.create_immutable_directory(initial_children)
         def _created(child):
-            entries = {name: (child, None)}
+            entries = {name: (child, metadata)}
             a = Adder(self, entries, overwrite=overwrite,
                       create_readonly_node=self._create_readonly_node)
             d = self._node.modify(a.modify)
diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py
index 0aab97fe..4cfe9c90 100644
--- a/src/allmydata/interfaces.py
+++ b/src/allmydata/interfaces.py
@@ -985,7 +985,7 @@ class IDirectoryNode(IFilesystemNode):
         is a file, or if must_be_file is True and the child is a directory,
         I raise ChildOfWrongTypeError."""
 
-    def create_subdirectory(name, initial_children={}, overwrite=True):
+    def create_subdirectory(name, initial_children={}, overwrite=True, metadata=None):
         """I create and attach a directory at the given name. The new
         directory can be empty, or it can be populated with children
         according to 'initial_children', which takes a dictionary in the same
diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py
index 46497ae6..56c9316f 100644
--- a/src/allmydata/test/test_dirnode.py
+++ b/src/allmydata/test/test_dirnode.py
@@ -775,8 +775,7 @@ class Dirnode(GridTestMixin, unittest.TestCase,
             d.addCallback(lambda metadata:
                           self.failUnlessEqual(set(metadata.keys()), set(["tahoe", "ctime", "mtime"])))
 
-            # or we can add specific metadata at set_uri() time, which
-            # overrides the timestamps
+            # we can also add specific metadata at set_uri() time
             d.addCallback(lambda res: n.set_uri(u"c4",
                                                 fake_file_uri, fake_file_uri,
                                                 {"key": "value"}))
@@ -790,7 +789,7 @@ class Dirnode(GridTestMixin, unittest.TestCase,
             d.addCallback(lambda res: n.delete(u"c4"))
 
             # set_node + metadata
-            # it should be possible to add a child without any metadata
+            # it should be possible to add a child without any metadata except for timestamps
             d.addCallback(lambda res: n.set_node(u"d2", n, {}))
             d.addCallback(lambda res: c.create_dirnode())
             d.addCallback(lambda n2:
@@ -808,8 +807,7 @@ class Dirnode(GridTestMixin, unittest.TestCase,
             d.addCallback(lambda metadata:
                           self.failUnlessEqual(set(metadata.keys()), set(["tahoe", "ctime", "mtime"])))
 
-            # or we can add specific metadata at set_node() time, which
-            # overrides the timestamps
+            # we can also add specific metadata at set_node() time
             d.addCallback(lambda res: n.set_node(u"d4", n,
                                                 {"key": "value"}))
             d.addCallback(lambda res: n.get_metadata_for(u"d4"))
@@ -899,6 +897,11 @@ class Dirnode(GridTestMixin, unittest.TestCase,
                                           metadata["tags"] == ["web2.0-compatible"] and
                                           "bad" not in metadata["tahoe"], metadata))
 
+            d.addCallback(lambda res:
+                          self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "",
+                                          n.set_metadata_for, u"nosuch", {}))
+
+
             def _start(res):
                 self._start_timestamp = time.time()
             d.addCallback(_start)
@@ -1047,6 +1050,35 @@ class Dirnode(GridTestMixin, unittest.TestCase,
                           self.failUnlessEqual(child.get_uri(),
                                                other_file_uri))
 
+
+            # Setting the no-write field should diminish a mutable cap to read-only
+            # (for both files and directories).
+
+            d.addCallback(lambda ign: n.set_uri(u"mutable", other_file_uri, other_file_uri))
+            d.addCallback(lambda ign: n.get(u"mutable"))
+            d.addCallback(lambda mutable: self.failIf(mutable.is_readonly(), mutable))
+            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
+            d.addCallback(lambda ign: n.get(u"mutable"))
+            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
+            d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
+            d.addCallback(lambda ign: n.get(u"mutable"))
+            d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
+
+            d.addCallback(lambda ign: n.get(u"subdir2"))
+            d.addCallback(lambda subdir2: self.failIf(subdir2.is_readonly()))
+            d.addCallback(lambda ign: n.set_metadata_for(u"subdir2", {"no-write": True}))
+            d.addCallback(lambda ign: n.get(u"subdir2"))
+            d.addCallback(lambda subdir2: self.failUnless(subdir2.is_readonly(), subdir2))
+
+            d.addCallback(lambda ign: n.set_uri(u"mutable_ro", other_file_uri, other_file_uri,
+                                                metadata={"no-write": True}))
+            d.addCallback(lambda ign: n.get(u"mutable_ro"))
+            d.addCallback(lambda mutable_ro: self.failUnless(mutable_ro.is_readonly(), mutable_ro))
+
+            d.addCallback(lambda ign: n.create_subdirectory(u"subdir_ro", metadata={"no-write": True}))
+            d.addCallback(lambda ign: n.get(u"subdir_ro"))
+            d.addCallback(lambda subdir_ro: self.failUnless(subdir_ro.is_readonly(), subdir_ro))
+
             return d
 
         d.addCallback(_then)