From 18ab5ce83786455e93a06c633d375237db3c0eab Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 26 Jun 2007 19:41:20 -0700 Subject: [PATCH] dirnode: add build_manifest() and introduce 'refresh capabilities' --- src/allmydata/dirnode.py | 37 +++++++++++++++++++++++ src/allmydata/interfaces.py | 30 ++++++++++++++++--- src/allmydata/test/test_dirnode.py | 47 ++++++++++++++++++++++++++++-- 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index 8a519c51..d655035b 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -329,6 +329,39 @@ class ImmutableDirectoryNode: d.addCallback(lambda child: self.delete(current_child_name)) return d + def build_manifest(self): + # given a dirnode, construct a list refresh-capabilities for all the + # nodes it references. + + # this is just a tree-walker, except that following each edge + # requires a Deferred. + + manifest = set() + manifest.add(self.get_refresh_capability()) + + d = self._build_manifest_from_node(self, manifest) + d.addCallback(lambda res: manifest) + return d + + def _build_manifest_from_node(self, node, manifest): + d = node.list() + def _got_list(res): + dl = [] + for name, child in res.iteritems(): + manifest.add(child.get_refresh_capability()) + if IDirectoryNode.providedBy(child): + dl.append(self._build_manifest_from_node(child, manifest)) + if dl: + return defer.DeferredList(dl) + d.addCallback(_got_list) + return d + + def get_refresh_capability(self): + ro_uri = self.get_immutable_uri() + furl, rk = uri.unpack_dirnode_uri(ro_uri) + wk, we, rk, index = hashutil.generate_dirnode_keys_from_readkey(rk) + return "DIR-REFRESH:%s" % idlib.b2a(index) + class MutableDirectoryNode(ImmutableDirectoryNode): implements(IDirectoryNode) @@ -372,6 +405,10 @@ class FileNode: return cmp(self.__class__, them.__class__) return cmp(self.uri, them.uri) + def get_refresh_capability(self): + d = uri.unpack_uri(self.uri) + return "CHK-REFRESH:%s" % idlib.b2a(d['storage_index']) + def download(self, target): downloader = self._client.getServiceNamed("downloader") return downloader.download(self.uri, target) diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index a9ccde8c..db39c68a 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -206,10 +206,22 @@ class IFileNode(Interface): def download_to_data(): pass + def get_uri(): + """Return the URI that can be used by others to get access to this + file. + """ + + def get_refresh_capability(): + """Return a string that represents the 'refresh capability' for this + node. The holder of this capability will be able to renew the lease + for this node, protecting it from garbage-collection. + """ + class IDirectoryNode(Interface): def is_mutable(): """Return True if this directory is mutable, False if it is read-only. """ + def get_uri(): """Return the directory URI that can be used by others to get access to this directory node. If this node is read-only, the URI will only @@ -219,10 +231,11 @@ class IDirectoryNode(Interface): If you have read-write access to a directory and wish to share merely read-only access with others, use get_immutable_uri(). - The dirnode ('1') URI returned by this method can be used in set() on - a different directory ('2') to 'mount' a reference to this directory - ('1') under the other ('2'). This URI is just a string, so it can be - passed around through email or other out-of-band protocol. + The dirnode ('1') URI returned by this method can be used in + set_uri() on a different directory ('2') to 'mount' a reference to + this directory ('1') under the other ('2'). This URI is just a + string, so it can be passed around through email or other out-of-band + protocol. """ def get_immutable_uri(): @@ -234,6 +247,12 @@ class IDirectoryNode(Interface): get_immutable_uri() will return the same thing as get_uri(). """ + def get_refresh_capability(): + """Return a string that represents the 'refresh capability' for this + node. The holder of this capability will be able to renew the lease + for this node, protecting it from garbage-collection. + """ + def list(): """I return a Deferred that fires with a dictionary mapping child name to an IFileNode or IDirectoryNode.""" @@ -280,6 +299,9 @@ class IDirectoryNode(Interface): 'new_child_name', which defaults to 'current_child_name'. I return a Deferred that fires when the operation finishes.""" + def build_manifest(): + """Return a set of refresh-capabilities for all nodes (directories + and files) reachable from this one.""" class ICodecEncoder(Interface): def set_params(data_size, required_shares, max_shares): diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py index c1c734fd..43b9b87c 100644 --- a/src/allmydata/test/test_dirnode.py +++ b/src/allmydata/test/test_dirnode.py @@ -173,8 +173,8 @@ class Test(unittest.TestCase): self.failUnlessEqual(res, {}) d.addCallback(_listed) - file1 = uri.pack_uri("i"*32, "k"*16, "e"*32, 25, 100, 12345) - file2 = uri.pack_uri("i"*31 + "2", "k"*16, "e"*32, 25, 100, 12345) + file1 = uri.pack_uri("11" + " "*30, "k"*16, "e"*32, 25, 100, 12345) + file2 = uri.pack_uri("2i" + " "*30, "k"*16, "e"*32, 25, 100, 12345) file2_node = dirnode.FileNode(file2, None) d.addCallback(lambda res: rootnode.set_uri("foo", file1)) # root/ @@ -184,6 +184,7 @@ class Test(unittest.TestCase): def _listed2(res): self.failUnlessEqual(res.keys(), ["foo"]) file1_node = res["foo"] + self.file1_node = file1_node self.failUnless(isinstance(file1_node, dirnode.FileNode)) self.failUnlessEqual(file1_node.uri, file1) d.addCallback(_listed2) @@ -238,6 +239,10 @@ class Test(unittest.TestCase): d.addCallback(self.failUnlessIdentical, file2_node) # and a directory d.addCallback(lambda res: self.bar_node.create_empty_directory("baz")) + def _added_baz(baz_node): + self.failUnless(IDirectoryNode.providedBy(baz_node)) + self.baz_node = baz_node + d.addCallback(_added_baz) # root/ # root/foo =file1 # root/bar/ @@ -257,6 +262,21 @@ class Test(unittest.TestCase): d.addCallback(lambda res: self.failIf(res["baz"].is_mutable())) + # test the manifest + d.addCallback(lambda res: self.rootnode.build_manifest()) + def _check_manifest(manifest): + manifest = sorted(list(manifest)) + self.failUnlessEqual(len(manifest), 5) + expected = [self.rootnode.get_refresh_capability(), + self.bar_node.get_refresh_capability(), + self.file1_node.get_refresh_capability(), + file2_node.get_refresh_capability(), + self.baz_node.get_refresh_capability(), + ] + expected.sort() + self.failUnlessEqual(manifest, expected) + d.addCallback(_check_manifest) + # try to add a file to bar-ro, should get exception d.addCallback(lambda res: self.bar_node_readonly.set_uri("file3", file2)) @@ -290,7 +310,7 @@ class Test(unittest.TestCase): self.bar_node.move_child_to("file2", self.rootnode, "file4")) # root/ - # root/file4 = file4 + # root/file4 = file2 # root/bar/ # root/bar/baz/ # root/bar-ro/ (read-only) @@ -327,6 +347,27 @@ class Test(unittest.TestCase): d.addCallback(self.failUnlessKeysMatch, ["baz", "file4"]) d.addCallback(lambda res:self.bar_node_readonly.list()) d.addCallback(self.failUnlessKeysMatch, ["baz", "file4"]) + # root/ + # root/bar/ + # root/bar/file4 = file2 + # root/bar/baz/ + # root/bar-ro/ (read-only) + # root/bar-ro/file4 = file2 + # root/bar-ro/baz/ + + # test the manifest + d.addCallback(lambda res: self.rootnode.build_manifest()) + def _check_manifest2(manifest): + manifest = sorted(list(manifest)) + self.failUnlessEqual(len(manifest), 4) + expected = [self.rootnode.get_refresh_capability(), + self.bar_node.get_refresh_capability(), + file2_node.get_refresh_capability(), + self.baz_node.get_refresh_capability(), + ] + expected.sort() + self.failUnlessEqual(manifest, expected) + d.addCallback(_check_manifest2) d.addCallback(self._test_one_3) return d -- 2.45.2