From: Kevan Carstensen Date: Sun, 7 Aug 2011 00:42:24 +0000 (-0700) Subject: dirnode: teach dirnode to make MDMF directories X-Git-Tag: trac-5200~17 X-Git-Url: https://git.rkrishnan.org/?a=commitdiff_plain;h=cde17ec0419979d48f9735407ed4ee6984d4aa48;p=tahoe-lafs%2Ftahoe-lafs.git dirnode: teach dirnode to make MDMF directories --- diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index e422b67f..b0bb72d2 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -613,14 +613,21 @@ class DirectoryNode: d.addCallback(lambda res: deleter.old_child) return d + # XXX: Too many arguments? Worthwhile to break into mutable/immutable? def create_subdirectory(self, namex, initial_children={}, overwrite=True, - mutable=True, metadata=None): + mutable=True, mutable_version=None, metadata=None): name = normalize(namex) if self.is_readonly(): return defer.fail(NotWriteableError()) if mutable: - d = self._nodemaker.create_new_mutable_directory(initial_children) + if mutable_version: + d = self._nodemaker.create_new_mutable_directory(initial_children, + version=mutable_version) + else: + d = self._nodemaker.create_new_mutable_directory(initial_children) else: + # mutable version doesn't make sense for immmutable directories. + assert mutable_version is None d = self._nodemaker.create_immutable_directory(initial_children) def _created(child): entries = {name: (child, metadata)} diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py index 6f715c99..d6bbc4f0 100644 --- a/src/allmydata/test/test_dirnode.py +++ b/src/allmydata/test/test_dirnode.py @@ -11,7 +11,8 @@ from allmydata.immutable import upload from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \ ExistingChildError, NoSuchChildError, MustNotBeUnknownRWError, \ MustBeDeepImmutableError, MustBeReadonlyError, \ - IDeepCheckResults, IDeepCheckAndRepairResults + IDeepCheckResults, IDeepCheckAndRepairResults, \ + MDMF_VERSION, SDMF_VERSION from allmydata.mutable.filenode import MutableFileNode from allmydata.mutable.common import UncoordinatedWriteError from allmydata.util import hashutil, base32 @@ -57,1082 +58,1208 @@ class Dirnode(GridTestMixin, unittest.TestCase, testutil.ReallyEqualMixin, testutil.ShouldFailMixin, testutil.StallMixin, ErrorMixin): timeout = 480 # It occasionally takes longer than 240 seconds on Francois's arm box. - def test_basic(self): - self.basedir = "dirnode/Dirnode/test_basic" - self.set_up_grid() + def _do_create_test(self, mdmf=False): c = self.g.clients[0] - d = c.create_dirnode() - def _done(res): - self.failUnless(isinstance(res, dirnode.DirectoryNode)) - self.failUnless(res.is_mutable()) - self.failIf(res.is_readonly()) - self.failIf(res.is_unknown()) - self.failIf(res.is_allowed_in_immutable_directory()) - res.raise_error() - rep = str(res) - self.failUnless("RW-MUT" in rep) - d.addCallback(_done) - return d - def test_initial_children(self): - self.basedir = "dirnode/Dirnode/test_initial_children" - self.set_up_grid() - c = self.g.clients[0] - nm = c.nodemaker + self.expected_manifest = [] + self.expected_verifycaps = set() + self.expected_storage_indexes = set() - kids = {one_nfd: (nm.create_from_cap(one_uri), {}), - u"two": (nm.create_from_cap(setup_py_uri), - {"metakey": "metavalue"}), - u"mut": (nm.create_from_cap(mut_write_uri, mut_read_uri), {}), - u"fut": (nm.create_from_cap(future_write_uri, future_read_uri), {}), - u"fro": (nm.create_from_cap(None, future_read_uri), {}), - u"fut-unic": (nm.create_from_cap(future_nonascii_write_uri, future_nonascii_read_uri), {}), - u"fro-unic": (nm.create_from_cap(None, future_nonascii_read_uri), {}), - u"empty_litdir": (nm.create_from_cap(empty_litdir_uri), {}), - u"tiny_litdir": (nm.create_from_cap(tiny_litdir_uri), {}), - } - d = c.create_dirnode(kids) + d = None + if mdmf: + d = c.create_dirnode(version=MDMF_VERSION) + else: + d = c.create_dirnode() + def _then(n): + # / + self.rootnode = n + backing_node = n._node + if mdmf: + self.failUnlessEqual(backing_node.get_version(), + MDMF_VERSION) + else: + self.failUnlessEqual(backing_node.get_version(), + SDMF_VERSION) + self.failUnless(n.is_mutable()) + u = n.get_uri() + self.failUnless(u) + cap_formats = [] + if mdmf: + cap_formats = ["URI:DIR2-MDMF:", + "URI:DIR2-MDMF-RO:", + "URI:DIR2-MDMF-Verifier:"] + else: + cap_formats = ["URI:DIR2:", + "URI:DIR2-RO", + "URI:DIR2-Verifier:"] + rw, ro, v = cap_formats + self.failUnless(u.startswith(rw), u) + u_ro = n.get_readonly_uri() + self.failUnless(u_ro.startswith(ro), u_ro) + u_v = n.get_verify_cap().to_string() + self.failUnless(u_v.startswith(v), u_v) + u_r = n.get_repair_cap().to_string() + self.failUnlessReallyEqual(u_r, u) + self.expected_manifest.append( ((), u) ) + self.expected_verifycaps.add(u_v) + si = n.get_storage_index() + self.expected_storage_indexes.add(base32.b2a(si)) + expected_si = n._uri.get_storage_index() + self.failUnlessReallyEqual(si, expected_si) - def _created(dn): - self.failUnless(isinstance(dn, dirnode.DirectoryNode)) - self.failUnless(dn.is_mutable()) - self.failIf(dn.is_readonly()) - self.failIf(dn.is_unknown()) - self.failIf(dn.is_allowed_in_immutable_directory()) - dn.raise_error() - rep = str(dn) - self.failUnless("RW-MUT" in rep) - return dn.list() - d.addCallback(_created) + d = n.list() + d.addCallback(lambda res: self.failUnlessEqual(res, {})) + d.addCallback(lambda res: n.has_child(u"missing")) + d.addCallback(lambda res: self.failIf(res)) - def _check_kids(children): - self.failUnlessReallyEqual(set(children.keys()), - set([one_nfc, u"two", u"mut", u"fut", u"fro", - u"fut-unic", u"fro-unic", u"empty_litdir", u"tiny_litdir"])) - one_node, one_metadata = children[one_nfc] - two_node, two_metadata = children[u"two"] - mut_node, mut_metadata = children[u"mut"] - fut_node, fut_metadata = children[u"fut"] - fro_node, fro_metadata = children[u"fro"] - futna_node, futna_metadata = children[u"fut-unic"] - frona_node, frona_metadata = children[u"fro-unic"] - emptylit_node, emptylit_metadata = children[u"empty_litdir"] - tinylit_node, tinylit_metadata = children[u"tiny_litdir"] + fake_file_uri = make_mutable_file_uri() + other_file_uri = make_mutable_file_uri() + m = c.nodemaker.create_from_cap(fake_file_uri) + ffu_v = m.get_verify_cap().to_string() + self.expected_manifest.append( ((u"child",) , m.get_uri()) ) + self.expected_verifycaps.add(ffu_v) + self.expected_storage_indexes.add(base32.b2a(m.get_storage_index())) + d.addCallback(lambda res: n.set_uri(u"child", + fake_file_uri, fake_file_uri)) + d.addCallback(lambda res: + self.shouldFail(ExistingChildError, "set_uri-no", + "child 'child' already exists", + n.set_uri, u"child", + other_file_uri, other_file_uri, + overwrite=False)) + # / + # /child = mutable - self.failUnlessReallyEqual(one_node.get_size(), 3) - self.failUnlessReallyEqual(one_node.get_uri(), one_uri) - self.failUnlessReallyEqual(one_node.get_readonly_uri(), one_uri) - self.failUnless(isinstance(one_metadata, dict), one_metadata) + d.addCallback(lambda res: n.create_subdirectory(u"subdir")) - self.failUnlessReallyEqual(two_node.get_size(), 14861) - self.failUnlessReallyEqual(two_node.get_uri(), setup_py_uri) - self.failUnlessReallyEqual(two_node.get_readonly_uri(), setup_py_uri) - self.failUnlessEqual(two_metadata["metakey"], "metavalue") + # / + # /child = mutable + # /subdir = directory + def _created(subdir): + self.failUnless(isinstance(subdir, dirnode.DirectoryNode)) + self.subdir = subdir + new_v = subdir.get_verify_cap().to_string() + assert isinstance(new_v, str) + self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) ) + self.expected_verifycaps.add(new_v) + si = subdir.get_storage_index() + self.expected_storage_indexes.add(base32.b2a(si)) + d.addCallback(_created) - self.failUnlessReallyEqual(mut_node.get_uri(), mut_write_uri) - self.failUnlessReallyEqual(mut_node.get_readonly_uri(), mut_read_uri) - self.failUnless(isinstance(mut_metadata, dict), mut_metadata) + d.addCallback(lambda res: + self.shouldFail(ExistingChildError, "mkdir-no", + "child 'subdir' already exists", + n.create_subdirectory, u"subdir", + overwrite=False)) - self.failUnless(fut_node.is_unknown()) - self.failUnlessReallyEqual(fut_node.get_uri(), future_write_uri) - self.failUnlessReallyEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri) - self.failUnless(isinstance(fut_metadata, dict), fut_metadata) + d.addCallback(lambda res: n.list()) + d.addCallback(lambda children: + self.failUnlessReallyEqual(set(children.keys()), + set([u"child", u"subdir"]))) - self.failUnless(futna_node.is_unknown()) - self.failUnlessReallyEqual(futna_node.get_uri(), future_nonascii_write_uri) - self.failUnlessReallyEqual(futna_node.get_readonly_uri(), "ro." + future_nonascii_read_uri) - self.failUnless(isinstance(futna_metadata, dict), futna_metadata) + d.addCallback(lambda res: n.start_deep_stats().when_done()) + def _check_deepstats(stats): + self.failUnless(isinstance(stats, dict)) + expected = {"count-immutable-files": 0, + "count-mutable-files": 1, + "count-literal-files": 0, + "count-files": 1, + "count-directories": 2, + "size-immutable-files": 0, + "size-literal-files": 0, + #"size-directories": 616, # varies + #"largest-directory": 616, + "largest-directory-children": 2, + "largest-immutable-file": 0, + } + for k,v in expected.iteritems(): + self.failUnlessReallyEqual(stats[k], v, + "stats[%s] was %s, not %s" % + (k, stats[k], v)) + self.failUnless(stats["size-directories"] > 500, + stats["size-directories"]) + self.failUnless(stats["largest-directory"] > 500, + stats["largest-directory"]) + self.failUnlessReallyEqual(stats["size-files-histogram"], []) + d.addCallback(_check_deepstats) - self.failUnless(fro_node.is_unknown()) - self.failUnlessReallyEqual(fro_node.get_uri(), "ro." + future_read_uri) - self.failUnlessReallyEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri) - self.failUnless(isinstance(fro_metadata, dict), fro_metadata) + d.addCallback(lambda res: n.build_manifest().when_done()) + def _check_manifest(res): + manifest = res["manifest"] + self.failUnlessReallyEqual(sorted(manifest), + sorted(self.expected_manifest)) + stats = res["stats"] + _check_deepstats(stats) + self.failUnlessReallyEqual(self.expected_verifycaps, + res["verifycaps"]) + self.failUnlessReallyEqual(self.expected_storage_indexes, + res["storage-index"]) + d.addCallback(_check_manifest) - self.failUnless(frona_node.is_unknown()) - self.failUnlessReallyEqual(frona_node.get_uri(), "ro." + future_nonascii_read_uri) - self.failUnlessReallyEqual(futna_node.get_readonly_uri(), "ro." + future_nonascii_read_uri) - self.failUnless(isinstance(frona_metadata, dict), frona_metadata) + def _add_subsubdir(res): + return self.subdir.create_subdirectory(u"subsubdir") + d.addCallback(_add_subsubdir) + # / + # /child = mutable + # /subdir = directory + # /subdir/subsubdir = directory + d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir")) + d.addCallback(lambda subsubdir: + self.failUnless(isinstance(subsubdir, + dirnode.DirectoryNode))) + d.addCallback(lambda res: n.get_child_at_path(u"")) + d.addCallback(lambda res: self.failUnlessReallyEqual(res.get_uri(), + n.get_uri())) - self.failIf(emptylit_node.is_unknown()) - self.failUnlessReallyEqual(emptylit_node.get_storage_index(), None) - self.failIf(tinylit_node.is_unknown()) - self.failUnlessReallyEqual(tinylit_node.get_storage_index(), None) + d.addCallback(lambda res: n.get_metadata_for(u"child")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), + set(["tahoe"]))) - d2 = defer.succeed(None) - d2.addCallback(lambda ignored: emptylit_node.list()) - d2.addCallback(lambda children: self.failUnlessEqual(children, {})) - d2.addCallback(lambda ignored: tinylit_node.list()) - d2.addCallback(lambda children: self.failUnlessReallyEqual(set(children.keys()), - set([u"short"]))) - d2.addCallback(lambda ignored: tinylit_node.list()) - d2.addCallback(lambda children: children[u"short"][0].read(MemAccum())) - d2.addCallback(lambda accum: self.failUnlessReallyEqual(accum.data, "The end.")) - return d2 + d.addCallback(lambda res: + self.shouldFail(NoSuchChildError, "gcamap-no", + "nope", + n.get_child_and_metadata_at_path, + u"subdir/nope")) + d.addCallback(lambda res: + n.get_child_and_metadata_at_path(u"")) + def _check_child_and_metadata1(res): + child, metadata = res + self.failUnless(isinstance(child, dirnode.DirectoryNode)) + # edge-metadata needs at least one path segment + self.failUnlessEqual(set(metadata.keys()), set([])) + d.addCallback(_check_child_and_metadata1) + d.addCallback(lambda res: + n.get_child_and_metadata_at_path(u"child")) - d.addCallback(_check_kids) + def _check_child_and_metadata2(res): + child, metadata = res + self.failUnlessReallyEqual(child.get_uri(), + fake_file_uri) + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])) + d.addCallback(_check_child_and_metadata2) - d.addCallback(lambda ign: nm.create_new_mutable_directory(kids)) - d.addCallback(lambda dn: dn.list()) - d.addCallback(_check_kids) + d.addCallback(lambda res: + n.get_child_and_metadata_at_path(u"subdir/subsubdir")) + def _check_child_and_metadata3(res): + child, metadata = res + self.failUnless(isinstance(child, dirnode.DirectoryNode)) + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])) + d.addCallback(_check_child_and_metadata3) - bad_future_node = UnknownNode(future_write_uri, None) - bad_kids1 = {one_nfd: (bad_future_node, {})} - # This should fail because we don't know how to diminish the future_write_uri - # cap (given in a write slot and not prefixed with "ro." or "imm.") to a readcap. - d.addCallback(lambda ign: - self.shouldFail(MustNotBeUnknownRWError, "bad_kids1", - "cannot attach unknown", - nm.create_new_mutable_directory, - bad_kids1)) - bad_kids2 = {one_nfd: (nm.create_from_cap(one_uri), None)} - d.addCallback(lambda ign: - self.shouldFail(AssertionError, "bad_kids2", - "requires metadata to be a dict", - nm.create_new_mutable_directory, - bad_kids2)) - return d + # set_uri + metadata + # it should be possible to add a child without any metadata + d.addCallback(lambda res: n.set_uri(u"c2", + fake_file_uri, fake_file_uri, + {})) + d.addCallback(lambda res: n.get_metadata_for(u"c2")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - def test_immutable(self): - self.basedir = "dirnode/Dirnode/test_immutable" - self.set_up_grid() - c = self.g.clients[0] - nm = c.nodemaker + # You can't override the link timestamps. + d.addCallback(lambda res: n.set_uri(u"c2", + fake_file_uri, fake_file_uri, + { 'tahoe': {'linkcrtime': "bogus"}})) + d.addCallback(lambda res: n.get_metadata_for(u"c2")) + def _has_good_linkcrtime(metadata): + self.failUnless(metadata.has_key('tahoe')) + self.failUnless(metadata['tahoe'].has_key('linkcrtime')) + self.failIfEqual(metadata['tahoe']['linkcrtime'], 'bogus') + d.addCallback(_has_good_linkcrtime) - kids = {one_nfd: (nm.create_from_cap(one_uri), {}), - u"two": (nm.create_from_cap(setup_py_uri), - {"metakey": "metavalue"}), - u"fut": (nm.create_from_cap(None, future_read_uri), {}), - u"futna": (nm.create_from_cap(None, future_nonascii_read_uri), {}), - u"empty_litdir": (nm.create_from_cap(empty_litdir_uri), {}), - u"tiny_litdir": (nm.create_from_cap(tiny_litdir_uri), {}), - } - d = c.create_immutable_dirnode(kids) + # if we don't set any defaults, the child should get timestamps + d.addCallback(lambda res: n.set_uri(u"c3", + fake_file_uri, fake_file_uri)) + d.addCallback(lambda res: n.get_metadata_for(u"c3")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - def _created(dn): - self.failUnless(isinstance(dn, dirnode.DirectoryNode)) - self.failIf(dn.is_mutable()) - self.failUnless(dn.is_readonly()) - self.failIf(dn.is_unknown()) - self.failUnless(dn.is_allowed_in_immutable_directory()) - dn.raise_error() - rep = str(dn) - self.failUnless("RO-IMM" in rep) - cap = dn.get_cap() - self.failUnlessIn("CHK", cap.to_string()) - self.cap = cap - return dn.list() - d.addCallback(_created) + # 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"})) + d.addCallback(lambda res: n.get_metadata_for(u"c4")) + d.addCallback(lambda metadata: + self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and + (metadata['key'] == "value"), metadata)) - def _check_kids(children): - self.failUnlessReallyEqual(set(children.keys()), - set([one_nfc, u"two", u"fut", u"futna", u"empty_litdir", u"tiny_litdir"])) - one_node, one_metadata = children[one_nfc] - two_node, two_metadata = children[u"two"] - fut_node, fut_metadata = children[u"fut"] - futna_node, futna_metadata = children[u"futna"] - emptylit_node, emptylit_metadata = children[u"empty_litdir"] - tinylit_node, tinylit_metadata = children[u"tiny_litdir"] + d.addCallback(lambda res: n.delete(u"c2")) + d.addCallback(lambda res: n.delete(u"c3")) + d.addCallback(lambda res: n.delete(u"c4")) - self.failUnlessReallyEqual(one_node.get_size(), 3) - self.failUnlessReallyEqual(one_node.get_uri(), one_uri) - self.failUnlessReallyEqual(one_node.get_readonly_uri(), one_uri) - self.failUnless(isinstance(one_metadata, dict), one_metadata) + # set_node + 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: + self.shouldFail(ExistingChildError, "set_node-no", + "child 'd2' already exists", + n.set_node, u"d2", n2, + overwrite=False)) + d.addCallback(lambda res: n.get_metadata_for(u"d2")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - self.failUnlessReallyEqual(two_node.get_size(), 14861) - self.failUnlessReallyEqual(two_node.get_uri(), setup_py_uri) - self.failUnlessReallyEqual(two_node.get_readonly_uri(), setup_py_uri) - self.failUnlessEqual(two_metadata["metakey"], "metavalue") + # if we don't set any defaults, the child should get timestamps + d.addCallback(lambda res: n.set_node(u"d3", n)) + d.addCallback(lambda res: n.get_metadata_for(u"d3")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - self.failUnless(fut_node.is_unknown()) - self.failUnlessReallyEqual(fut_node.get_uri(), "imm." + future_read_uri) - self.failUnlessReallyEqual(fut_node.get_readonly_uri(), "imm." + future_read_uri) - self.failUnless(isinstance(fut_metadata, dict), fut_metadata) + # 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")) + d.addCallback(lambda metadata: + self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and + (metadata["key"] == "value"), metadata)) - self.failUnless(futna_node.is_unknown()) - self.failUnlessReallyEqual(futna_node.get_uri(), "imm." + future_nonascii_read_uri) - self.failUnlessReallyEqual(futna_node.get_readonly_uri(), "imm." + future_nonascii_read_uri) - self.failUnless(isinstance(futna_metadata, dict), futna_metadata) + d.addCallback(lambda res: n.delete(u"d2")) + d.addCallback(lambda res: n.delete(u"d3")) + d.addCallback(lambda res: n.delete(u"d4")) - self.failIf(emptylit_node.is_unknown()) - self.failUnlessReallyEqual(emptylit_node.get_storage_index(), None) - self.failIf(tinylit_node.is_unknown()) - self.failUnlessReallyEqual(tinylit_node.get_storage_index(), None) + # metadata through set_children() + d.addCallback(lambda res: + n.set_children({ + u"e1": (fake_file_uri, fake_file_uri), + u"e2": (fake_file_uri, fake_file_uri, {}), + u"e3": (fake_file_uri, fake_file_uri, + {"key": "value"}), + })) + d.addCallback(lambda n2: self.failUnlessIdentical(n2, n)) + d.addCallback(lambda res: + self.shouldFail(ExistingChildError, "set_children-no", + "child 'e1' already exists", + n.set_children, + { u"e1": (other_file_uri, + other_file_uri), + u"new": (other_file_uri, + other_file_uri), + }, + overwrite=False)) + # and 'new' should not have been created + d.addCallback(lambda res: n.list()) + d.addCallback(lambda children: self.failIf(u"new" in children)) + d.addCallback(lambda res: n.get_metadata_for(u"e1")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + d.addCallback(lambda res: n.get_metadata_for(u"e2")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + d.addCallback(lambda res: n.get_metadata_for(u"e3")) + d.addCallback(lambda metadata: + self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and + (metadata["key"] == "value"), metadata)) - d2 = defer.succeed(None) - d2.addCallback(lambda ignored: emptylit_node.list()) - d2.addCallback(lambda children: self.failUnlessEqual(children, {})) - d2.addCallback(lambda ignored: tinylit_node.list()) - d2.addCallback(lambda children: self.failUnlessReallyEqual(set(children.keys()), - set([u"short"]))) - d2.addCallback(lambda ignored: tinylit_node.list()) - d2.addCallback(lambda children: children[u"short"][0].read(MemAccum())) - d2.addCallback(lambda accum: self.failUnlessReallyEqual(accum.data, "The end.")) - return d2 + d.addCallback(lambda res: n.delete(u"e1")) + d.addCallback(lambda res: n.delete(u"e2")) + d.addCallback(lambda res: n.delete(u"e3")) - d.addCallback(_check_kids) + # metadata through set_nodes() + d.addCallback(lambda res: + n.set_nodes({ u"f1": (n, None), + u"f2": (n, {}), + u"f3": (n, {"key": "value"}), + })) + d.addCallback(lambda n2: self.failUnlessIdentical(n2, n)) + d.addCallback(lambda res: + self.shouldFail(ExistingChildError, "set_nodes-no", + "child 'f1' already exists", + n.set_nodes, { u"f1": (n, None), + u"new": (n, None), }, + overwrite=False)) + # and 'new' should not have been created + d.addCallback(lambda res: n.list()) + d.addCallback(lambda children: self.failIf(u"new" in children)) + d.addCallback(lambda res: n.get_metadata_for(u"f1")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + d.addCallback(lambda res: n.get_metadata_for(u"f2")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + d.addCallback(lambda res: n.get_metadata_for(u"f3")) + d.addCallback(lambda metadata: + self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and + (metadata["key"] == "value"), metadata)) - d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) - d.addCallback(lambda dn: dn.list()) - d.addCallback(_check_kids) + d.addCallback(lambda res: n.delete(u"f1")) + d.addCallback(lambda res: n.delete(u"f2")) + d.addCallback(lambda res: n.delete(u"f3")) - bad_future_node1 = UnknownNode(future_write_uri, None) - bad_kids1 = {one_nfd: (bad_future_node1, {})} - d.addCallback(lambda ign: - self.shouldFail(MustNotBeUnknownRWError, "bad_kids1", - "cannot attach unknown", - c.create_immutable_dirnode, - bad_kids1)) - bad_future_node2 = UnknownNode(future_write_uri, future_read_uri) - bad_kids2 = {one_nfd: (bad_future_node2, {})} - d.addCallback(lambda ign: - self.shouldFail(MustBeDeepImmutableError, "bad_kids2", - "is not allowed in an immutable directory", - c.create_immutable_dirnode, - bad_kids2)) - bad_kids3 = {one_nfd: (nm.create_from_cap(one_uri), None)} - d.addCallback(lambda ign: - self.shouldFail(AssertionError, "bad_kids3", - "requires metadata to be a dict", - c.create_immutable_dirnode, - bad_kids3)) - bad_kids4 = {one_nfd: (nm.create_from_cap(mut_write_uri), {})} - d.addCallback(lambda ign: - self.shouldFail(MustBeDeepImmutableError, "bad_kids4", - "is not allowed in an immutable directory", - c.create_immutable_dirnode, - bad_kids4)) - bad_kids5 = {one_nfd: (nm.create_from_cap(mut_read_uri), {})} - d.addCallback(lambda ign: - self.shouldFail(MustBeDeepImmutableError, "bad_kids5", - "is not allowed in an immutable directory", - c.create_immutable_dirnode, - bad_kids5)) - d.addCallback(lambda ign: c.create_immutable_dirnode({})) - def _created_empty(dn): - self.failUnless(isinstance(dn, dirnode.DirectoryNode)) - self.failIf(dn.is_mutable()) - self.failUnless(dn.is_readonly()) - self.failIf(dn.is_unknown()) - self.failUnless(dn.is_allowed_in_immutable_directory()) - dn.raise_error() - rep = str(dn) - self.failUnless("RO-IMM" in rep) - cap = dn.get_cap() - self.failUnlessIn("LIT", cap.to_string()) - self.failUnlessReallyEqual(cap.to_string(), "URI:DIR2-LIT:") - self.cap = cap - return dn.list() - d.addCallback(_created_empty) - d.addCallback(lambda kids: self.failUnlessEqual(kids, {})) - smallkids = {u"o": (nm.create_from_cap(one_uri), {})} - d.addCallback(lambda ign: c.create_immutable_dirnode(smallkids)) - def _created_small(dn): - self.failUnless(isinstance(dn, dirnode.DirectoryNode)) - self.failIf(dn.is_mutable()) - self.failUnless(dn.is_readonly()) - self.failIf(dn.is_unknown()) - self.failUnless(dn.is_allowed_in_immutable_directory()) - dn.raise_error() - rep = str(dn) - self.failUnless("RO-IMM" in rep) - cap = dn.get_cap() - self.failUnlessIn("LIT", cap.to_string()) - self.failUnlessReallyEqual(cap.to_string(), - "URI:DIR2-LIT:gi4tumj2n4wdcmz2kvjesosmjfkdu3rvpbtwwlbqhiwdeot3puwcy") - self.cap = cap - return dn.list() - d.addCallback(_created_small) - d.addCallback(lambda kids: self.failUnlessReallyEqual(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.failUnlessReallyEqual(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 = {one_nfd: (nm.create_from_cap(mut_write_uri), {})} - d.addCallback(lambda ign: - self.shouldFail(MustBeDeepImmutableError, "YZ", - "is not allowed in an immutable directory", - n.create_subdirectory, - u"sub2", bad_kids, mutable=False)) - return d - d.addCallback(_made_parent) - return d - def test_directory_representation(self): - self.basedir = "dirnode/Dirnode/test_directory_representation" - self.set_up_grid() - c = self.g.clients[0] - nm = c.nodemaker + d.addCallback(lambda res: + n.set_metadata_for(u"child", + {"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}})) + d.addCallback(lambda n1: n1.get_metadata_for(u"child")) + d.addCallback(lambda metadata: + self.failUnless((set(metadata.keys()) == set(["tags", "tahoe"])) and + metadata["tags"] == ["web2.0-compatible"] and + "bad" not in metadata["tahoe"], metadata)) - # This test checks that any trailing spaces in URIs are retained in the - # encoded directory, but stripped when we get them out of the directory. - # See ticket #925 for why we want that. - # It also tests that we store child names as UTF-8 NFC, and normalize - # them again when retrieving them. + d.addCallback(lambda res: + self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "", + n.set_metadata_for, u"nosuch", {})) - stripped_write_uri = "lafs://from_the_future\t" - stripped_read_uri = "lafs://readonly_from_the_future\t" - spacedout_write_uri = stripped_write_uri + " " - spacedout_read_uri = stripped_read_uri + " " - child = nm.create_from_cap(spacedout_write_uri, spacedout_read_uri) - self.failUnlessReallyEqual(child.get_write_uri(), spacedout_write_uri) - self.failUnlessReallyEqual(child.get_readonly_uri(), "ro." + spacedout_read_uri) + def _start(res): + self._start_timestamp = time.time() + d.addCallback(_start) + # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all + # floats to hundredeths (it uses str(num) instead of repr(num)). + # simplejson-1.7.3 does not have this bug. To prevent this bug + # from causing the test to fail, stall for more than a few + # hundrededths of a second. + d.addCallback(self.stall, 0.1) + d.addCallback(lambda res: n.add_file(u"timestamps", + upload.Data("stamp me", convergence="some convergence string"))) + d.addCallback(self.stall, 0.1) + def _stop(res): + self._stop_timestamp = time.time() + d.addCallback(_stop) - child_dottedi = u"ch\u0131\u0307ld" + d.addCallback(lambda res: n.get_metadata_for(u"timestamps")) + def _check_timestamp1(metadata): + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])) + tahoe_md = metadata["tahoe"] + self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"])) - kids_in = {child_dottedi: (child, {}), one_nfd: (child, {})} - kids_out = {child_dottedi: (child, {}), one_nfc: (child, {})} - kids_norm = {u"child": (child, {}), one_nfc: (child, {})} - d = c.create_dirnode(kids_in) + self.failUnlessGreaterOrEqualThan(tahoe_md["linkcrtime"], + self._start_timestamp) + self.failUnlessGreaterOrEqualThan(self._stop_timestamp, + tahoe_md["linkcrtime"]) + self.failUnlessGreaterOrEqualThan(tahoe_md["linkmotime"], + self._start_timestamp) + self.failUnlessGreaterOrEqualThan(self._stop_timestamp, + tahoe_md["linkmotime"]) + # Our current timestamp rules say that replacing an existing + # child should preserve the 'linkcrtime' but update the + # 'linkmotime' + self._old_linkcrtime = tahoe_md["linkcrtime"] + self._old_linkmotime = tahoe_md["linkmotime"] + d.addCallback(_check_timestamp1) + d.addCallback(self.stall, 2.0) # accomodate low-res timestamps + d.addCallback(lambda res: n.set_node(u"timestamps", n)) + d.addCallback(lambda res: n.get_metadata_for(u"timestamps")) + def _check_timestamp2(metadata): + self.failUnlessIn("tahoe", metadata) + tahoe_md = metadata["tahoe"] + self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"])) - def _created(dn): - self.failUnless(isinstance(dn, dirnode.DirectoryNode)) - self.failUnless(dn.is_mutable()) - self.failIf(dn.is_readonly()) - dn.raise_error() - self.cap = dn.get_cap() - self.rootnode = dn - return dn._node.download_best_version() - d.addCallback(_created) + self.failUnlessReallyEqual(tahoe_md["linkcrtime"], self._old_linkcrtime) + self.failUnlessGreaterThan(tahoe_md["linkmotime"], self._old_linkmotime) + return n.delete(u"timestamps") + d.addCallback(_check_timestamp2) - def _check_data(data): - # Decode the netstring representation of the directory to check that the - # spaces are retained when the URIs are stored, and that the names are stored - # as NFC. - position = 0 - numkids = 0 - while position < len(data): - entries, position = split_netstring(data, 1, position) - entry = entries[0] - (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4) - name = name_utf8.decode("utf-8") - rw_uri = self.rootnode._decrypt_rwcapdata(rwcapdata) - self.failUnlessIn(name, kids_out) - (expected_child, ign) = kids_out[name] - self.failUnlessReallyEqual(rw_uri, expected_child.get_write_uri()) - self.failUnlessReallyEqual("ro." + ro_uri, expected_child.get_readonly_uri()) - numkids += 1 + d.addCallback(lambda res: n.delete(u"subdir")) + d.addCallback(lambda old_child: + self.failUnlessReallyEqual(old_child.get_uri(), + self.subdir.get_uri())) - self.failUnlessReallyEqual(numkids, len(kids_out)) - return self.rootnode - d.addCallback(_check_data) + d.addCallback(lambda res: n.list()) + d.addCallback(lambda children: + self.failUnlessReallyEqual(set(children.keys()), + set([u"child"]))) - # Mock up a hypothetical future version of Unicode that adds a canonical equivalence - # between dotless-i + dot-above, and 'i'. That would actually be prohibited by the - # stability rules, but similar additions involving currently-unassigned characters - # would not be. - old_normalize = unicodedata.normalize - def future_normalize(form, s): - assert form == 'NFC', form - return old_normalize(form, s).replace(u"\u0131\u0307", u"i") + uploadable1 = upload.Data("some data", convergence="converge") + d.addCallback(lambda res: n.add_file(u"newfile", uploadable1)) + d.addCallback(lambda newnode: + self.failUnless(IImmutableFileNode.providedBy(newnode))) + uploadable2 = upload.Data("some data", convergence="stuff") + d.addCallback(lambda res: + self.shouldFail(ExistingChildError, "add_file-no", + "child 'newfile' already exists", + n.add_file, u"newfile", + uploadable2, + overwrite=False)) + d.addCallback(lambda res: n.list()) + d.addCallback(lambda children: + self.failUnlessReallyEqual(set(children.keys()), + set([u"child", u"newfile"]))) + d.addCallback(lambda res: n.get_metadata_for(u"newfile")) + d.addCallback(lambda metadata: + self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - def _list(node): - unicodedata.normalize = future_normalize - d2 = node.list() - def _undo_mock(res): - unicodedata.normalize = old_normalize - return res - d2.addBoth(_undo_mock) - return d2 - d.addCallback(_list) + uploadable3 = upload.Data("some data", convergence="converge") + d.addCallback(lambda res: n.add_file(u"newfile-metadata", + uploadable3, + {"key": "value"})) + d.addCallback(lambda newnode: + self.failUnless(IImmutableFileNode.providedBy(newnode))) + d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata")) + d.addCallback(lambda metadata: + self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and + (metadata['key'] == "value"), metadata)) + d.addCallback(lambda res: n.delete(u"newfile-metadata")) - def _check_kids(children): - # Now when we use the real directory listing code, the trailing spaces - # should have been stripped (and "ro." should have been prepended to the - # ro_uri, since it's unknown). Also the dotless-i + dot-above should have been - # normalized to 'i'. + d.addCallback(lambda res: n.create_subdirectory(u"subdir2")) + def _created2(subdir2): + self.subdir2 = subdir2 + # put something in the way, to make sure it gets overwritten + return subdir2.add_file(u"child", upload.Data("overwrite me", + "converge")) + d.addCallback(_created2) - self.failUnlessReallyEqual(set(children.keys()), set(kids_norm.keys())) - child_node, child_metadata = children[u"child"] + d.addCallback(lambda res: + n.move_child_to(u"child", self.subdir2)) + d.addCallback(lambda res: n.list()) + d.addCallback(lambda children: + self.failUnlessReallyEqual(set(children.keys()), + set([u"newfile", u"subdir2"]))) + d.addCallback(lambda res: self.subdir2.list()) + d.addCallback(lambda children: + self.failUnlessReallyEqual(set(children.keys()), + set([u"child"]))) + d.addCallback(lambda res: self.subdir2.get(u"child")) + d.addCallback(lambda child: + self.failUnlessReallyEqual(child.get_uri(), + fake_file_uri)) - self.failUnlessReallyEqual(child_node.get_write_uri(), stripped_write_uri) - self.failUnlessReallyEqual(child_node.get_readonly_uri(), "ro." + stripped_read_uri) - d.addCallback(_check_kids) + # move it back, using new_child_name= + d.addCallback(lambda res: + self.subdir2.move_child_to(u"child", n, u"newchild")) + d.addCallback(lambda res: n.list()) + d.addCallback(lambda children: + self.failUnlessReallyEqual(set(children.keys()), + set([u"newchild", u"newfile", + u"subdir2"]))) + d.addCallback(lambda res: self.subdir2.list()) + d.addCallback(lambda children: + self.failUnlessReallyEqual(set(children.keys()), set([]))) - d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) - d.addCallback(_list) - d.addCallback(_check_kids) # again with dirnode recreated from cap - return d + # now make sure that we honor overwrite=False + d.addCallback(lambda res: + self.subdir2.set_uri(u"newchild", + other_file_uri, other_file_uri)) - def test_check(self): - self.basedir = "dirnode/Dirnode/test_check" - self.set_up_grid() - c = self.g.clients[0] - d = c.create_dirnode() - d.addCallback(lambda dn: dn.check(Monitor())) - def _done(res): - self.failUnless(res.is_healthy()) - d.addCallback(_done) - return d + d.addCallback(lambda res: + self.shouldFail(ExistingChildError, "move_child_to-no", + "child 'newchild' already exists", + n.move_child_to, u"newchild", + self.subdir2, + overwrite=False)) + d.addCallback(lambda res: self.subdir2.get(u"newchild")) + d.addCallback(lambda child: + self.failUnlessReallyEqual(child.get_uri(), + other_file_uri)) - def _test_deepcheck_create(self): - # create a small tree with a loop, and some non-directories - # root/ - # root/subdir/ - # root/subdir/file1 - # root/subdir/link -> root - # root/rodir - c = self.g.clients[0] - d = c.create_dirnode() - def _created_root(rootnode): - self._rootnode = rootnode - return rootnode.create_subdirectory(u"subdir") - d.addCallback(_created_root) - def _created_subdir(subdir): - self._subdir = subdir - d = subdir.add_file(u"file1", upload.Data("data"*100, None)) - d.addCallback(lambda res: subdir.set_node(u"link", self._rootnode)) - d.addCallback(lambda res: c.create_dirnode()) - d.addCallback(lambda dn: - self._rootnode.set_uri(u"rodir", - dn.get_uri(), - dn.get_readonly_uri())) - return d - d.addCallback(_created_subdir) - def _done(res): - return self._rootnode - d.addCallback(_done) - return d - def test_deepcheck(self): - self.basedir = "dirnode/Dirnode/test_deepcheck" - self.set_up_grid() - d = self._test_deepcheck_create() - d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done()) - def _check_results(r): - self.failUnless(IDeepCheckResults.providedBy(r)) - c = r.get_counters() - self.failUnlessReallyEqual(c, - {"count-objects-checked": 4, - "count-objects-healthy": 4, - "count-objects-unhealthy": 0, - "count-objects-unrecoverable": 0, - "count-corrupt-shares": 0, - }) - self.failIf(r.get_corrupt_shares()) - self.failUnlessReallyEqual(len(r.get_all_results()), 4) - d.addCallback(_check_results) - return d + # Setting the no-write field should diminish a mutable cap to read-only + # (for both files and directories). - def test_deepcheck_and_repair(self): - self.basedir = "dirnode/Dirnode/test_deepcheck_and_repair" - self.set_up_grid() - d = self._test_deepcheck_create() - d.addCallback(lambda rootnode: - rootnode.start_deep_check_and_repair().when_done()) - def _check_results(r): - self.failUnless(IDeepCheckAndRepairResults.providedBy(r)) - c = r.get_counters() - self.failUnlessReallyEqual(c, - {"count-objects-checked": 4, - "count-objects-healthy-pre-repair": 4, - "count-objects-unhealthy-pre-repair": 0, - "count-objects-unrecoverable-pre-repair": 0, - "count-corrupt-shares-pre-repair": 0, - "count-objects-healthy-post-repair": 4, - "count-objects-unhealthy-post-repair": 0, - "count-objects-unrecoverable-post-repair": 0, - "count-corrupt-shares-post-repair": 0, - "count-repairs-attempted": 0, - "count-repairs-successful": 0, - "count-repairs-unsuccessful": 0, - }) - self.failIf(r.get_corrupt_shares()) - self.failIf(r.get_remaining_corrupt_shares()) - self.failUnlessReallyEqual(len(r.get_all_results()), 4) - d.addCallback(_check_results) - return d + 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)) - def _mark_file_bad(self, rootnode): - self.delete_shares_numbered(rootnode.get_uri(), [0]) - return rootnode + 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)) - def test_deepcheck_problems(self): - self.basedir = "dirnode/Dirnode/test_deepcheck_problems" - self.set_up_grid() - d = self._test_deepcheck_create() - d.addCallback(lambda rootnode: self._mark_file_bad(rootnode)) - d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done()) - def _check_results(r): - c = r.get_counters() - self.failUnlessReallyEqual(c, - {"count-objects-checked": 4, - "count-objects-healthy": 3, - "count-objects-unhealthy": 1, - "count-objects-unrecoverable": 0, - "count-corrupt-shares": 0, - }) - #self.failUnlessReallyEqual(len(r.get_problems()), 1) # TODO - d.addCallback(_check_results) + 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) + + d.addErrback(self.explain_error) return d - def test_readonly(self): - self.basedir = "dirnode/Dirnode/test_readonly" - self.set_up_grid() + + def _do_initial_children_test(self, mdmf=False): c = self.g.clients[0] nm = c.nodemaker - filecap = make_chk_file_uri(1234) - filenode = nm.create_from_cap(filecap) - uploadable = upload.Data("some data", convergence="some convergence string") - d = c.create_dirnode() - def _created(rw_dn): - d2 = rw_dn.set_uri(u"child", filecap, filecap) - d2.addCallback(lambda res: rw_dn) - return d2 + kids = {one_nfd: (nm.create_from_cap(one_uri), {}), + u"two": (nm.create_from_cap(setup_py_uri), + {"metakey": "metavalue"}), + u"mut": (nm.create_from_cap(mut_write_uri, mut_read_uri), {}), + u"fut": (nm.create_from_cap(future_write_uri, future_read_uri), {}), + u"fro": (nm.create_from_cap(None, future_read_uri), {}), + u"fut-unic": (nm.create_from_cap(future_nonascii_write_uri, future_nonascii_read_uri), {}), + u"fro-unic": (nm.create_from_cap(None, future_nonascii_read_uri), {}), + u"empty_litdir": (nm.create_from_cap(empty_litdir_uri), {}), + u"tiny_litdir": (nm.create_from_cap(tiny_litdir_uri), {}), + } + d = c.create_dirnode(kids) + + def _created(dn): + self.failUnless(isinstance(dn, dirnode.DirectoryNode)) + backing_node = dn._node + if mdmf: + self.failUnlessEqual(backing_node.get_version(), + MDMF_VERSION) + else: + self.failUnlessEqual(backing_node.get_version(), + SDMF_VERSION) + self.failUnless(dn.is_mutable()) + self.failIf(dn.is_readonly()) + self.failIf(dn.is_unknown()) + self.failIf(dn.is_allowed_in_immutable_directory()) + dn.raise_error() + rep = str(dn) + self.failUnless("RW-MUT" in rep) + return dn.list() d.addCallback(_created) + + def _check_kids(children): + self.failUnlessReallyEqual(set(children.keys()), + set([one_nfc, u"two", u"mut", u"fut", u"fro", + u"fut-unic", u"fro-unic", u"empty_litdir", u"tiny_litdir"])) + one_node, one_metadata = children[one_nfc] + two_node, two_metadata = children[u"two"] + mut_node, mut_metadata = children[u"mut"] + fut_node, fut_metadata = children[u"fut"] + fro_node, fro_metadata = children[u"fro"] + futna_node, futna_metadata = children[u"fut-unic"] + frona_node, frona_metadata = children[u"fro-unic"] + emptylit_node, emptylit_metadata = children[u"empty_litdir"] + tinylit_node, tinylit_metadata = children[u"tiny_litdir"] - def _ready(rw_dn): - ro_uri = rw_dn.get_readonly_uri() - ro_dn = c.create_node_from_uri(ro_uri) - self.failUnless(ro_dn.is_readonly()) - self.failUnless(ro_dn.is_mutable()) - self.failIf(ro_dn.is_unknown()) - self.failIf(ro_dn.is_allowed_in_immutable_directory()) - ro_dn.raise_error() + self.failUnlessReallyEqual(one_node.get_size(), 3) + self.failUnlessReallyEqual(one_node.get_uri(), one_uri) + self.failUnlessReallyEqual(one_node.get_readonly_uri(), one_uri) + self.failUnless(isinstance(one_metadata, dict), one_metadata) - self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, - ro_dn.set_uri, u"newchild", filecap, filecap) - self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, - ro_dn.set_node, u"newchild", filenode) - self.shouldFail(dirnode.NotWriteableError, "set_nodes ro", None, - ro_dn.set_nodes, { u"newchild": (filenode, None) }) - self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, - ro_dn.add_file, u"newchild", uploadable) - self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, - ro_dn.delete, u"child") - self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, - ro_dn.create_subdirectory, u"newchild") - self.shouldFail(dirnode.NotWriteableError, "set_metadata_for ro", None, - ro_dn.set_metadata_for, u"child", {}) - self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, - ro_dn.move_child_to, u"child", rw_dn) - self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, - rw_dn.move_child_to, u"child", ro_dn) - return ro_dn.list() - d.addCallback(_ready) - def _listed(children): - self.failUnless(u"child" in children) - d.addCallback(_listed) - return d + self.failUnlessReallyEqual(two_node.get_size(), 14861) + self.failUnlessReallyEqual(two_node.get_uri(), setup_py_uri) + self.failUnlessReallyEqual(two_node.get_readonly_uri(), setup_py_uri) + self.failUnlessEqual(two_metadata["metakey"], "metavalue") - def failUnlessGreaterThan(self, a, b): - self.failUnless(a > b, "%r should be > %r" % (a, b)) + self.failUnlessReallyEqual(mut_node.get_uri(), mut_write_uri) + self.failUnlessReallyEqual(mut_node.get_readonly_uri(), mut_read_uri) + self.failUnless(isinstance(mut_metadata, dict), mut_metadata) - def failUnlessGreaterOrEqualThan(self, a, b): - self.failUnless(a >= b, "%r should be >= %r" % (a, b)) + self.failUnless(fut_node.is_unknown()) + self.failUnlessReallyEqual(fut_node.get_uri(), future_write_uri) + self.failUnlessReallyEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri) + self.failUnless(isinstance(fut_metadata, dict), fut_metadata) - def test_create(self): - self.basedir = "dirnode/Dirnode/test_create" - self.set_up_grid() - c = self.g.clients[0] + self.failUnless(futna_node.is_unknown()) + self.failUnlessReallyEqual(futna_node.get_uri(), future_nonascii_write_uri) + self.failUnlessReallyEqual(futna_node.get_readonly_uri(), "ro." + future_nonascii_read_uri) + self.failUnless(isinstance(futna_metadata, dict), futna_metadata) - self.expected_manifest = [] - self.expected_verifycaps = set() - self.expected_storage_indexes = set() + self.failUnless(fro_node.is_unknown()) + self.failUnlessReallyEqual(fro_node.get_uri(), "ro." + future_read_uri) + self.failUnlessReallyEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri) + self.failUnless(isinstance(fro_metadata, dict), fro_metadata) - d = c.create_dirnode() - def _then(n): - # / - self.rootnode = n - self.failUnless(n.is_mutable()) - u = n.get_uri() - self.failUnless(u) - self.failUnless(u.startswith("URI:DIR2:"), u) - u_ro = n.get_readonly_uri() - self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro) - u_v = n.get_verify_cap().to_string() - self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v) - u_r = n.get_repair_cap().to_string() - self.failUnlessReallyEqual(u_r, u) - self.expected_manifest.append( ((), u) ) - self.expected_verifycaps.add(u_v) - si = n.get_storage_index() - self.expected_storage_indexes.add(base32.b2a(si)) - expected_si = n._uri.get_storage_index() - self.failUnlessReallyEqual(si, expected_si) + self.failUnless(frona_node.is_unknown()) + self.failUnlessReallyEqual(frona_node.get_uri(), "ro." + future_nonascii_read_uri) + self.failUnlessReallyEqual(futna_node.get_readonly_uri(), "ro." + future_nonascii_read_uri) + self.failUnless(isinstance(frona_metadata, dict), frona_metadata) - d = n.list() - d.addCallback(lambda res: self.failUnlessEqual(res, {})) - d.addCallback(lambda res: n.has_child(u"missing")) - d.addCallback(lambda res: self.failIf(res)) + self.failIf(emptylit_node.is_unknown()) + self.failUnlessReallyEqual(emptylit_node.get_storage_index(), None) + self.failIf(tinylit_node.is_unknown()) + self.failUnlessReallyEqual(tinylit_node.get_storage_index(), None) - fake_file_uri = make_mutable_file_uri() - other_file_uri = make_mutable_file_uri() - m = c.nodemaker.create_from_cap(fake_file_uri) - ffu_v = m.get_verify_cap().to_string() - self.expected_manifest.append( ((u"child",) , m.get_uri()) ) - self.expected_verifycaps.add(ffu_v) - self.expected_storage_indexes.add(base32.b2a(m.get_storage_index())) - d.addCallback(lambda res: n.set_uri(u"child", - fake_file_uri, fake_file_uri)) - d.addCallback(lambda res: - self.shouldFail(ExistingChildError, "set_uri-no", - "child 'child' already exists", - n.set_uri, u"child", - other_file_uri, other_file_uri, - overwrite=False)) - # / - # /child = mutable + d2 = defer.succeed(None) + d2.addCallback(lambda ignored: emptylit_node.list()) + d2.addCallback(lambda children: self.failUnlessEqual(children, {})) + d2.addCallback(lambda ignored: tinylit_node.list()) + d2.addCallback(lambda children: self.failUnlessReallyEqual(set(children.keys()), + set([u"short"]))) + d2.addCallback(lambda ignored: tinylit_node.list()) + d2.addCallback(lambda children: children[u"short"][0].read(MemAccum())) + d2.addCallback(lambda accum: self.failUnlessReallyEqual(accum.data, "The end.")) + return d2 + d.addCallback(_created) + d.addCallback(_check_kids) - d.addCallback(lambda res: n.create_subdirectory(u"subdir")) + d.addCallback(lambda ign: nm.create_new_mutable_directory(kids)) + d.addCallback(lambda dn: dn.list()) + d.addCallback(_check_kids) - # / - # /child = mutable - # /subdir = directory - def _created(subdir): - self.failUnless(isinstance(subdir, dirnode.DirectoryNode)) - self.subdir = subdir - new_v = subdir.get_verify_cap().to_string() - assert isinstance(new_v, str) - self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) ) - self.expected_verifycaps.add(new_v) - si = subdir.get_storage_index() - self.expected_storage_indexes.add(base32.b2a(si)) - d.addCallback(_created) + bad_future_node = UnknownNode(future_write_uri, None) + bad_kids1 = {one_nfd: (bad_future_node, {})} + # This should fail because we don't know how to diminish the future_write_uri + # cap (given in a write slot and not prefixed with "ro." or "imm.") to a readcap. + d.addCallback(lambda ign: + self.shouldFail(MustNotBeUnknownRWError, "bad_kids1", + "cannot attach unknown", + nm.create_new_mutable_directory, + bad_kids1)) + bad_kids2 = {one_nfd: (nm.create_from_cap(one_uri), None)} + d.addCallback(lambda ign: + self.shouldFail(AssertionError, "bad_kids2", + "requires metadata to be a dict", + nm.create_new_mutable_directory, + bad_kids2)) + return d - d.addCallback(lambda res: - self.shouldFail(ExistingChildError, "mkdir-no", - "child 'subdir' already exists", - n.create_subdirectory, u"subdir", - overwrite=False)) + def _do_basic_test(self, mdmf=False): + c = self.g.clients[0] + d = None + if mdmf: + d = c.create_dirnode(version=MDMF_VERSION) + else: + d = c.create_dirnode() + def _done(res): + self.failUnless(isinstance(res, dirnode.DirectoryNode)) + self.failUnless(res.is_mutable()) + self.failIf(res.is_readonly()) + self.failIf(res.is_unknown()) + self.failIf(res.is_allowed_in_immutable_directory()) + res.raise_error() + rep = str(res) + self.failUnless("RW-MUT" in rep) + d.addCallback(_done) + return d - d.addCallback(lambda res: n.list()) - d.addCallback(lambda children: - self.failUnlessReallyEqual(set(children.keys()), - set([u"child", u"subdir"]))) + def test_basic(self): + self.basedir = "dirnode/Dirnode/test_basic" + self.set_up_grid() + return self._do_basic_test() - d.addCallback(lambda res: n.start_deep_stats().when_done()) - def _check_deepstats(stats): - self.failUnless(isinstance(stats, dict)) - expected = {"count-immutable-files": 0, - "count-mutable-files": 1, - "count-literal-files": 0, - "count-files": 1, - "count-directories": 2, - "size-immutable-files": 0, - "size-literal-files": 0, - #"size-directories": 616, # varies - #"largest-directory": 616, - "largest-directory-children": 2, - "largest-immutable-file": 0, - } - for k,v in expected.iteritems(): - self.failUnlessReallyEqual(stats[k], v, - "stats[%s] was %s, not %s" % - (k, stats[k], v)) - self.failUnless(stats["size-directories"] > 500, - stats["size-directories"]) - self.failUnless(stats["largest-directory"] > 500, - stats["largest-directory"]) - self.failUnlessReallyEqual(stats["size-files-histogram"], []) - d.addCallback(_check_deepstats) + def test_basic_mdmf(self): + self.basedir = "dirnode/Dirnode/test_basic_mdmf" + self.set_up_grid() + return self._do_basic_test(mdmf=True) - d.addCallback(lambda res: n.build_manifest().when_done()) - def _check_manifest(res): - manifest = res["manifest"] - self.failUnlessReallyEqual(sorted(manifest), - sorted(self.expected_manifest)) - stats = res["stats"] - _check_deepstats(stats) - self.failUnlessReallyEqual(self.expected_verifycaps, - res["verifycaps"]) - self.failUnlessReallyEqual(self.expected_storage_indexes, - res["storage-index"]) - d.addCallback(_check_manifest) + def test_initial_children(self): + self.basedir = "dirnode/Dirnode/test_initial_children" + self.set_up_grid() + return self._do_initial_children_test() - def _add_subsubdir(res): - return self.subdir.create_subdirectory(u"subsubdir") - d.addCallback(_add_subsubdir) - # / - # /child = mutable - # /subdir = directory - # /subdir/subsubdir = directory - d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir")) - d.addCallback(lambda subsubdir: - self.failUnless(isinstance(subsubdir, - dirnode.DirectoryNode))) - d.addCallback(lambda res: n.get_child_at_path(u"")) - d.addCallback(lambda res: self.failUnlessReallyEqual(res.get_uri(), - n.get_uri())) + def test_immutable(self): + self.basedir = "dirnode/Dirnode/test_immutable" + self.set_up_grid() + c = self.g.clients[0] + nm = c.nodemaker - d.addCallback(lambda res: n.get_metadata_for(u"child")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), - set(["tahoe"]))) + kids = {one_nfd: (nm.create_from_cap(one_uri), {}), + u"two": (nm.create_from_cap(setup_py_uri), + {"metakey": "metavalue"}), + u"fut": (nm.create_from_cap(None, future_read_uri), {}), + u"futna": (nm.create_from_cap(None, future_nonascii_read_uri), {}), + u"empty_litdir": (nm.create_from_cap(empty_litdir_uri), {}), + u"tiny_litdir": (nm.create_from_cap(tiny_litdir_uri), {}), + } + d = c.create_immutable_dirnode(kids) - d.addCallback(lambda res: - self.shouldFail(NoSuchChildError, "gcamap-no", - "nope", - n.get_child_and_metadata_at_path, - u"subdir/nope")) - d.addCallback(lambda res: - n.get_child_and_metadata_at_path(u"")) - def _check_child_and_metadata1(res): - child, metadata = res - self.failUnless(isinstance(child, dirnode.DirectoryNode)) - # edge-metadata needs at least one path segment - self.failUnlessEqual(set(metadata.keys()), set([])) - d.addCallback(_check_child_and_metadata1) - d.addCallback(lambda res: - n.get_child_and_metadata_at_path(u"child")) + def _created(dn): + self.failUnless(isinstance(dn, dirnode.DirectoryNode)) + self.failIf(dn.is_mutable()) + self.failUnless(dn.is_readonly()) + self.failIf(dn.is_unknown()) + self.failUnless(dn.is_allowed_in_immutable_directory()) + dn.raise_error() + rep = str(dn) + self.failUnless("RO-IMM" in rep) + cap = dn.get_cap() + self.failUnlessIn("CHK", cap.to_string()) + self.cap = cap + return dn.list() + d.addCallback(_created) - def _check_child_and_metadata2(res): - child, metadata = res - self.failUnlessReallyEqual(child.get_uri(), - fake_file_uri) - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])) - d.addCallback(_check_child_and_metadata2) + def _check_kids(children): + self.failUnlessReallyEqual(set(children.keys()), + set([one_nfc, u"two", u"fut", u"futna", u"empty_litdir", u"tiny_litdir"])) + one_node, one_metadata = children[one_nfc] + two_node, two_metadata = children[u"two"] + fut_node, fut_metadata = children[u"fut"] + futna_node, futna_metadata = children[u"futna"] + emptylit_node, emptylit_metadata = children[u"empty_litdir"] + tinylit_node, tinylit_metadata = children[u"tiny_litdir"] - d.addCallback(lambda res: - n.get_child_and_metadata_at_path(u"subdir/subsubdir")) - def _check_child_and_metadata3(res): - child, metadata = res - self.failUnless(isinstance(child, dirnode.DirectoryNode)) - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])) - d.addCallback(_check_child_and_metadata3) + self.failUnlessReallyEqual(one_node.get_size(), 3) + self.failUnlessReallyEqual(one_node.get_uri(), one_uri) + self.failUnlessReallyEqual(one_node.get_readonly_uri(), one_uri) + self.failUnless(isinstance(one_metadata, dict), one_metadata) - # set_uri + metadata - # it should be possible to add a child without any metadata - d.addCallback(lambda res: n.set_uri(u"c2", - fake_file_uri, fake_file_uri, - {})) - d.addCallback(lambda res: n.get_metadata_for(u"c2")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + self.failUnlessReallyEqual(two_node.get_size(), 14861) + self.failUnlessReallyEqual(two_node.get_uri(), setup_py_uri) + self.failUnlessReallyEqual(two_node.get_readonly_uri(), setup_py_uri) + self.failUnlessEqual(two_metadata["metakey"], "metavalue") - # You can't override the link timestamps. - d.addCallback(lambda res: n.set_uri(u"c2", - fake_file_uri, fake_file_uri, - { 'tahoe': {'linkcrtime': "bogus"}})) - d.addCallback(lambda res: n.get_metadata_for(u"c2")) - def _has_good_linkcrtime(metadata): - self.failUnless(metadata.has_key('tahoe')) - self.failUnless(metadata['tahoe'].has_key('linkcrtime')) - self.failIfEqual(metadata['tahoe']['linkcrtime'], 'bogus') - d.addCallback(_has_good_linkcrtime) + self.failUnless(fut_node.is_unknown()) + self.failUnlessReallyEqual(fut_node.get_uri(), "imm." + future_read_uri) + self.failUnlessReallyEqual(fut_node.get_readonly_uri(), "imm." + future_read_uri) + self.failUnless(isinstance(fut_metadata, dict), fut_metadata) - # if we don't set any defaults, the child should get timestamps - d.addCallback(lambda res: n.set_uri(u"c3", - fake_file_uri, fake_file_uri)) - d.addCallback(lambda res: n.get_metadata_for(u"c3")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + self.failUnless(futna_node.is_unknown()) + self.failUnlessReallyEqual(futna_node.get_uri(), "imm." + future_nonascii_read_uri) + self.failUnlessReallyEqual(futna_node.get_readonly_uri(), "imm." + future_nonascii_read_uri) + self.failUnless(isinstance(futna_metadata, dict), futna_metadata) - # 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"})) - d.addCallback(lambda res: n.get_metadata_for(u"c4")) - d.addCallback(lambda metadata: - self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and - (metadata['key'] == "value"), metadata)) + self.failIf(emptylit_node.is_unknown()) + self.failUnlessReallyEqual(emptylit_node.get_storage_index(), None) + self.failIf(tinylit_node.is_unknown()) + self.failUnlessReallyEqual(tinylit_node.get_storage_index(), None) - d.addCallback(lambda res: n.delete(u"c2")) - d.addCallback(lambda res: n.delete(u"c3")) - d.addCallback(lambda res: n.delete(u"c4")) + d2 = defer.succeed(None) + d2.addCallback(lambda ignored: emptylit_node.list()) + d2.addCallback(lambda children: self.failUnlessEqual(children, {})) + d2.addCallback(lambda ignored: tinylit_node.list()) + d2.addCallback(lambda children: self.failUnlessReallyEqual(set(children.keys()), + set([u"short"]))) + d2.addCallback(lambda ignored: tinylit_node.list()) + d2.addCallback(lambda children: children[u"short"][0].read(MemAccum())) + d2.addCallback(lambda accum: self.failUnlessReallyEqual(accum.data, "The end.")) + return d2 - # set_node + 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: - self.shouldFail(ExistingChildError, "set_node-no", - "child 'd2' already exists", - n.set_node, u"d2", n2, - overwrite=False)) - d.addCallback(lambda res: n.get_metadata_for(u"d2")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + d.addCallback(_check_kids) - # if we don't set any defaults, the child should get timestamps - d.addCallback(lambda res: n.set_node(u"d3", n)) - d.addCallback(lambda res: n.get_metadata_for(u"d3")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) + d.addCallback(lambda dn: dn.list()) + d.addCallback(_check_kids) - # 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")) - d.addCallback(lambda metadata: - self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and - (metadata["key"] == "value"), metadata)) + bad_future_node1 = UnknownNode(future_write_uri, None) + bad_kids1 = {one_nfd: (bad_future_node1, {})} + d.addCallback(lambda ign: + self.shouldFail(MustNotBeUnknownRWError, "bad_kids1", + "cannot attach unknown", + c.create_immutable_dirnode, + bad_kids1)) + bad_future_node2 = UnknownNode(future_write_uri, future_read_uri) + bad_kids2 = {one_nfd: (bad_future_node2, {})} + d.addCallback(lambda ign: + self.shouldFail(MustBeDeepImmutableError, "bad_kids2", + "is not allowed in an immutable directory", + c.create_immutable_dirnode, + bad_kids2)) + bad_kids3 = {one_nfd: (nm.create_from_cap(one_uri), None)} + d.addCallback(lambda ign: + self.shouldFail(AssertionError, "bad_kids3", + "requires metadata to be a dict", + c.create_immutable_dirnode, + bad_kids3)) + bad_kids4 = {one_nfd: (nm.create_from_cap(mut_write_uri), {})} + d.addCallback(lambda ign: + self.shouldFail(MustBeDeepImmutableError, "bad_kids4", + "is not allowed in an immutable directory", + c.create_immutable_dirnode, + bad_kids4)) + bad_kids5 = {one_nfd: (nm.create_from_cap(mut_read_uri), {})} + d.addCallback(lambda ign: + self.shouldFail(MustBeDeepImmutableError, "bad_kids5", + "is not allowed in an immutable directory", + c.create_immutable_dirnode, + bad_kids5)) + d.addCallback(lambda ign: c.create_immutable_dirnode({})) + def _created_empty(dn): + self.failUnless(isinstance(dn, dirnode.DirectoryNode)) + self.failIf(dn.is_mutable()) + self.failUnless(dn.is_readonly()) + self.failIf(dn.is_unknown()) + self.failUnless(dn.is_allowed_in_immutable_directory()) + dn.raise_error() + rep = str(dn) + self.failUnless("RO-IMM" in rep) + cap = dn.get_cap() + self.failUnlessIn("LIT", cap.to_string()) + self.failUnlessReallyEqual(cap.to_string(), "URI:DIR2-LIT:") + self.cap = cap + return dn.list() + d.addCallback(_created_empty) + d.addCallback(lambda kids: self.failUnlessEqual(kids, {})) + smallkids = {u"o": (nm.create_from_cap(one_uri), {})} + d.addCallback(lambda ign: c.create_immutable_dirnode(smallkids)) + def _created_small(dn): + self.failUnless(isinstance(dn, dirnode.DirectoryNode)) + self.failIf(dn.is_mutable()) + self.failUnless(dn.is_readonly()) + self.failIf(dn.is_unknown()) + self.failUnless(dn.is_allowed_in_immutable_directory()) + dn.raise_error() + rep = str(dn) + self.failUnless("RO-IMM" in rep) + cap = dn.get_cap() + self.failUnlessIn("LIT", cap.to_string()) + self.failUnlessReallyEqual(cap.to_string(), + "URI:DIR2-LIT:gi4tumj2n4wdcmz2kvjesosmjfkdu3rvpbtwwlbqhiwdeot3puwcy") + self.cap = cap + return dn.list() + d.addCallback(_created_small) + d.addCallback(lambda kids: self.failUnlessReallyEqual(kids.keys(), [u"o"])) - d.addCallback(lambda res: n.delete(u"d2")) - d.addCallback(lambda res: n.delete(u"d3")) - d.addCallback(lambda res: n.delete(u"d4")) + # 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.failUnlessReallyEqual(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 = {one_nfd: (nm.create_from_cap(mut_write_uri), {})} + d.addCallback(lambda ign: + self.shouldFail(MustBeDeepImmutableError, "YZ", + "is not allowed in an immutable directory", + n.create_subdirectory, + u"sub2", bad_kids, mutable=False)) + return d + d.addCallback(_made_parent) + return d - # metadata through set_children() - d.addCallback(lambda res: - n.set_children({ - u"e1": (fake_file_uri, fake_file_uri), - u"e2": (fake_file_uri, fake_file_uri, {}), - u"e3": (fake_file_uri, fake_file_uri, - {"key": "value"}), - })) - d.addCallback(lambda n2: self.failUnlessIdentical(n2, n)) - d.addCallback(lambda res: - self.shouldFail(ExistingChildError, "set_children-no", - "child 'e1' already exists", - n.set_children, - { u"e1": (other_file_uri, - other_file_uri), - u"new": (other_file_uri, - other_file_uri), - }, - overwrite=False)) - # and 'new' should not have been created - d.addCallback(lambda res: n.list()) - d.addCallback(lambda children: self.failIf(u"new" in children)) - d.addCallback(lambda res: n.get_metadata_for(u"e1")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - d.addCallback(lambda res: n.get_metadata_for(u"e2")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - d.addCallback(lambda res: n.get_metadata_for(u"e3")) - d.addCallback(lambda metadata: - self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and - (metadata["key"] == "value"), metadata)) + def test_directory_representation(self): + self.basedir = "dirnode/Dirnode/test_directory_representation" + self.set_up_grid() + c = self.g.clients[0] + nm = c.nodemaker - d.addCallback(lambda res: n.delete(u"e1")) - d.addCallback(lambda res: n.delete(u"e2")) - d.addCallback(lambda res: n.delete(u"e3")) + # This test checks that any trailing spaces in URIs are retained in the + # encoded directory, but stripped when we get them out of the directory. + # See ticket #925 for why we want that. + # It also tests that we store child names as UTF-8 NFC, and normalize + # them again when retrieving them. - # metadata through set_nodes() - d.addCallback(lambda res: - n.set_nodes({ u"f1": (n, None), - u"f2": (n, {}), - u"f3": (n, {"key": "value"}), - })) - d.addCallback(lambda n2: self.failUnlessIdentical(n2, n)) - d.addCallback(lambda res: - self.shouldFail(ExistingChildError, "set_nodes-no", - "child 'f1' already exists", - n.set_nodes, { u"f1": (n, None), - u"new": (n, None), }, - overwrite=False)) - # and 'new' should not have been created - d.addCallback(lambda res: n.list()) - d.addCallback(lambda children: self.failIf(u"new" in children)) - d.addCallback(lambda res: n.get_metadata_for(u"f1")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - d.addCallback(lambda res: n.get_metadata_for(u"f2")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) - d.addCallback(lambda res: n.get_metadata_for(u"f3")) - d.addCallback(lambda metadata: - self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and - (metadata["key"] == "value"), metadata)) + stripped_write_uri = "lafs://from_the_future\t" + stripped_read_uri = "lafs://readonly_from_the_future\t" + spacedout_write_uri = stripped_write_uri + " " + spacedout_read_uri = stripped_read_uri + " " - d.addCallback(lambda res: n.delete(u"f1")) - d.addCallback(lambda res: n.delete(u"f2")) - d.addCallback(lambda res: n.delete(u"f3")) + child = nm.create_from_cap(spacedout_write_uri, spacedout_read_uri) + self.failUnlessReallyEqual(child.get_write_uri(), spacedout_write_uri) + self.failUnlessReallyEqual(child.get_readonly_uri(), "ro." + spacedout_read_uri) + child_dottedi = u"ch\u0131\u0307ld" - d.addCallback(lambda res: - n.set_metadata_for(u"child", - {"tags": ["web2.0-compatible"], "tahoe": {"bad": "mojo"}})) - d.addCallback(lambda n1: n1.get_metadata_for(u"child")) - d.addCallback(lambda metadata: - self.failUnless((set(metadata.keys()) == set(["tags", "tahoe"])) and - metadata["tags"] == ["web2.0-compatible"] and - "bad" not in metadata["tahoe"], metadata)) + kids_in = {child_dottedi: (child, {}), one_nfd: (child, {})} + kids_out = {child_dottedi: (child, {}), one_nfc: (child, {})} + kids_norm = {u"child": (child, {}), one_nfc: (child, {})} + d = c.create_dirnode(kids_in) - d.addCallback(lambda res: - self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "", - n.set_metadata_for, u"nosuch", {})) + def _created(dn): + self.failUnless(isinstance(dn, dirnode.DirectoryNode)) + self.failUnless(dn.is_mutable()) + self.failIf(dn.is_readonly()) + dn.raise_error() + self.cap = dn.get_cap() + self.rootnode = dn + return dn._node.download_best_version() + d.addCallback(_created) + def _check_data(data): + # Decode the netstring representation of the directory to check that the + # spaces are retained when the URIs are stored, and that the names are stored + # as NFC. + position = 0 + numkids = 0 + while position < len(data): + entries, position = split_netstring(data, 1, position) + entry = entries[0] + (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4) + name = name_utf8.decode("utf-8") + rw_uri = self.rootnode._decrypt_rwcapdata(rwcapdata) + self.failUnlessIn(name, kids_out) + (expected_child, ign) = kids_out[name] + self.failUnlessReallyEqual(rw_uri, expected_child.get_write_uri()) + self.failUnlessReallyEqual("ro." + ro_uri, expected_child.get_readonly_uri()) + numkids += 1 - def _start(res): - self._start_timestamp = time.time() - d.addCallback(_start) - # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all - # floats to hundredeths (it uses str(num) instead of repr(num)). - # simplejson-1.7.3 does not have this bug. To prevent this bug - # from causing the test to fail, stall for more than a few - # hundrededths of a second. - d.addCallback(self.stall, 0.1) - d.addCallback(lambda res: n.add_file(u"timestamps", - upload.Data("stamp me", convergence="some convergence string"))) - d.addCallback(self.stall, 0.1) - def _stop(res): - self._stop_timestamp = time.time() - d.addCallback(_stop) + self.failUnlessReallyEqual(numkids, len(kids_out)) + return self.rootnode + d.addCallback(_check_data) - d.addCallback(lambda res: n.get_metadata_for(u"timestamps")) - def _check_timestamp1(metadata): - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"])) - tahoe_md = metadata["tahoe"] - self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"])) + # Mock up a hypothetical future version of Unicode that adds a canonical equivalence + # between dotless-i + dot-above, and 'i'. That would actually be prohibited by the + # stability rules, but similar additions involving currently-unassigned characters + # would not be. + old_normalize = unicodedata.normalize + def future_normalize(form, s): + assert form == 'NFC', form + return old_normalize(form, s).replace(u"\u0131\u0307", u"i") - self.failUnlessGreaterOrEqualThan(tahoe_md["linkcrtime"], - self._start_timestamp) - self.failUnlessGreaterOrEqualThan(self._stop_timestamp, - tahoe_md["linkcrtime"]) - self.failUnlessGreaterOrEqualThan(tahoe_md["linkmotime"], - self._start_timestamp) - self.failUnlessGreaterOrEqualThan(self._stop_timestamp, - tahoe_md["linkmotime"]) - # Our current timestamp rules say that replacing an existing - # child should preserve the 'linkcrtime' but update the - # 'linkmotime' - self._old_linkcrtime = tahoe_md["linkcrtime"] - self._old_linkmotime = tahoe_md["linkmotime"] - d.addCallback(_check_timestamp1) - d.addCallback(self.stall, 2.0) # accomodate low-res timestamps - d.addCallback(lambda res: n.set_node(u"timestamps", n)) - d.addCallback(lambda res: n.get_metadata_for(u"timestamps")) - def _check_timestamp2(metadata): - self.failUnlessIn("tahoe", metadata) - tahoe_md = metadata["tahoe"] - self.failUnlessEqual(set(tahoe_md.keys()), set(["linkcrtime", "linkmotime"])) + def _list(node): + unicodedata.normalize = future_normalize + d2 = node.list() + def _undo_mock(res): + unicodedata.normalize = old_normalize + return res + d2.addBoth(_undo_mock) + return d2 + d.addCallback(_list) - self.failUnlessReallyEqual(tahoe_md["linkcrtime"], self._old_linkcrtime) - self.failUnlessGreaterThan(tahoe_md["linkmotime"], self._old_linkmotime) - return n.delete(u"timestamps") - d.addCallback(_check_timestamp2) + def _check_kids(children): + # Now when we use the real directory listing code, the trailing spaces + # should have been stripped (and "ro." should have been prepended to the + # ro_uri, since it's unknown). Also the dotless-i + dot-above should have been + # normalized to 'i'. - d.addCallback(lambda res: n.delete(u"subdir")) - d.addCallback(lambda old_child: - self.failUnlessReallyEqual(old_child.get_uri(), - self.subdir.get_uri())) + self.failUnlessReallyEqual(set(children.keys()), set(kids_norm.keys())) + child_node, child_metadata = children[u"child"] - d.addCallback(lambda res: n.list()) - d.addCallback(lambda children: - self.failUnlessReallyEqual(set(children.keys()), - set([u"child"]))) + self.failUnlessReallyEqual(child_node.get_write_uri(), stripped_write_uri) + self.failUnlessReallyEqual(child_node.get_readonly_uri(), "ro." + stripped_read_uri) + d.addCallback(_check_kids) - uploadable1 = upload.Data("some data", convergence="converge") - d.addCallback(lambda res: n.add_file(u"newfile", uploadable1)) - d.addCallback(lambda newnode: - self.failUnless(IImmutableFileNode.providedBy(newnode))) - uploadable2 = upload.Data("some data", convergence="stuff") - d.addCallback(lambda res: - self.shouldFail(ExistingChildError, "add_file-no", - "child 'newfile' already exists", - n.add_file, u"newfile", - uploadable2, - overwrite=False)) - d.addCallback(lambda res: n.list()) - d.addCallback(lambda children: - self.failUnlessReallyEqual(set(children.keys()), - set([u"child", u"newfile"]))) - d.addCallback(lambda res: n.get_metadata_for(u"newfile")) - d.addCallback(lambda metadata: - self.failUnlessEqual(set(metadata.keys()), set(["tahoe"]))) + d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) + d.addCallback(_list) + d.addCallback(_check_kids) # again with dirnode recreated from cap + return d - uploadable3 = upload.Data("some data", convergence="converge") - d.addCallback(lambda res: n.add_file(u"newfile-metadata", - uploadable3, - {"key": "value"})) - d.addCallback(lambda newnode: - self.failUnless(IImmutableFileNode.providedBy(newnode))) - d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata")) - d.addCallback(lambda metadata: - self.failUnless((set(metadata.keys()) == set(["key", "tahoe"])) and - (metadata['key'] == "value"), metadata)) - d.addCallback(lambda res: n.delete(u"newfile-metadata")) + def test_check(self): + self.basedir = "dirnode/Dirnode/test_check" + self.set_up_grid() + c = self.g.clients[0] + d = c.create_dirnode() + d.addCallback(lambda dn: dn.check(Monitor())) + def _done(res): + self.failUnless(res.is_healthy()) + d.addCallback(_done) + return d + + def _test_deepcheck_create(self, version=SDMF_VERSION): + # create a small tree with a loop, and some non-directories + # root/ + # root/subdir/ + # root/subdir/file1 + # root/subdir/link -> root + # root/rodir + c = self.g.clients[0] + d = c.create_dirnode(version=version) + def _created_root(rootnode): + self._rootnode = rootnode + self.failUnlessEqual(rootnode._node.get_version(), version) + return rootnode.create_subdirectory(u"subdir") + d.addCallback(_created_root) + def _created_subdir(subdir): + self._subdir = subdir + d = subdir.add_file(u"file1", upload.Data("data"*100, None)) + d.addCallback(lambda res: subdir.set_node(u"link", self._rootnode)) + d.addCallback(lambda res: c.create_dirnode()) + d.addCallback(lambda dn: + self._rootnode.set_uri(u"rodir", + dn.get_uri(), + dn.get_readonly_uri())) + return d + d.addCallback(_created_subdir) + def _done(res): + return self._rootnode + d.addCallback(_done) + return d + + def test_deepcheck(self): + self.basedir = "dirnode/Dirnode/test_deepcheck" + self.set_up_grid() + d = self._test_deepcheck_create() + d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done()) + def _check_results(r): + self.failUnless(IDeepCheckResults.providedBy(r)) + c = r.get_counters() + self.failUnlessReallyEqual(c, + {"count-objects-checked": 4, + "count-objects-healthy": 4, + "count-objects-unhealthy": 0, + "count-objects-unrecoverable": 0, + "count-corrupt-shares": 0, + }) + self.failIf(r.get_corrupt_shares()) + self.failUnlessReallyEqual(len(r.get_all_results()), 4) + d.addCallback(_check_results) + return d - d.addCallback(lambda res: n.create_subdirectory(u"subdir2")) - def _created2(subdir2): - self.subdir2 = subdir2 - # put something in the way, to make sure it gets overwritten - return subdir2.add_file(u"child", upload.Data("overwrite me", - "converge")) - d.addCallback(_created2) + def test_deepcheck_mdmf(self): + self.basedir = "dirnode/Dirnode/test_deepcheck_mdmf" + self.set_up_grid() + d = self._test_deepcheck_create(MDMF_VERSION) + d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done()) + def _check_results(r): + self.failUnless(IDeepCheckResults.providedBy(r)) + c = r.get_counters() + self.failUnlessReallyEqual(c, + {"count-objects-checked": 4, + "count-objects-healthy": 4, + "count-objects-unhealthy": 0, + "count-objects-unrecoverable": 0, + "count-corrupt-shares": 0, + }) + self.failIf(r.get_corrupt_shares()) + self.failUnlessReallyEqual(len(r.get_all_results()), 4) + d.addCallback(_check_results) + return d - d.addCallback(lambda res: - n.move_child_to(u"child", self.subdir2)) - d.addCallback(lambda res: n.list()) - d.addCallback(lambda children: - self.failUnlessReallyEqual(set(children.keys()), - set([u"newfile", u"subdir2"]))) - d.addCallback(lambda res: self.subdir2.list()) - d.addCallback(lambda children: - self.failUnlessReallyEqual(set(children.keys()), - set([u"child"]))) - d.addCallback(lambda res: self.subdir2.get(u"child")) - d.addCallback(lambda child: - self.failUnlessReallyEqual(child.get_uri(), - fake_file_uri)) + def test_deepcheck_and_repair(self): + self.basedir = "dirnode/Dirnode/test_deepcheck_and_repair" + self.set_up_grid() + d = self._test_deepcheck_create() + d.addCallback(lambda rootnode: + rootnode.start_deep_check_and_repair().when_done()) + def _check_results(r): + self.failUnless(IDeepCheckAndRepairResults.providedBy(r)) + c = r.get_counters() + self.failUnlessReallyEqual(c, + {"count-objects-checked": 4, + "count-objects-healthy-pre-repair": 4, + "count-objects-unhealthy-pre-repair": 0, + "count-objects-unrecoverable-pre-repair": 0, + "count-corrupt-shares-pre-repair": 0, + "count-objects-healthy-post-repair": 4, + "count-objects-unhealthy-post-repair": 0, + "count-objects-unrecoverable-post-repair": 0, + "count-corrupt-shares-post-repair": 0, + "count-repairs-attempted": 0, + "count-repairs-successful": 0, + "count-repairs-unsuccessful": 0, + }) + self.failIf(r.get_corrupt_shares()) + self.failIf(r.get_remaining_corrupt_shares()) + self.failUnlessReallyEqual(len(r.get_all_results()), 4) + d.addCallback(_check_results) + return d - # move it back, using new_child_name= - d.addCallback(lambda res: - self.subdir2.move_child_to(u"child", n, u"newchild")) - d.addCallback(lambda res: n.list()) - d.addCallback(lambda children: - self.failUnlessReallyEqual(set(children.keys()), - set([u"newchild", u"newfile", - u"subdir2"]))) - d.addCallback(lambda res: self.subdir2.list()) - d.addCallback(lambda children: - self.failUnlessReallyEqual(set(children.keys()), set([]))) + def test_deepcheck_and_repair_mdmf(self): + self.basedir = "dirnode/Dirnode/test_deepcheck_and_repair_mdmf" + self.set_up_grid() + d = self._test_deepcheck_create(version=MDMF_VERSION) + d.addCallback(lambda rootnode: + rootnode.start_deep_check_and_repair().when_done()) + def _check_results(r): + self.failUnless(IDeepCheckAndRepairResults.providedBy(r)) + c = r.get_counters() + self.failUnlessReallyEqual(c, + {"count-objects-checked": 4, + "count-objects-healthy-pre-repair": 4, + "count-objects-unhealthy-pre-repair": 0, + "count-objects-unrecoverable-pre-repair": 0, + "count-corrupt-shares-pre-repair": 0, + "count-objects-healthy-post-repair": 4, + "count-objects-unhealthy-post-repair": 0, + "count-objects-unrecoverable-post-repair": 0, + "count-corrupt-shares-post-repair": 0, + "count-repairs-attempted": 0, + "count-repairs-successful": 0, + "count-repairs-unsuccessful": 0, + }) + self.failIf(r.get_corrupt_shares()) + self.failIf(r.get_remaining_corrupt_shares()) + self.failUnlessReallyEqual(len(r.get_all_results()), 4) + d.addCallback(_check_results) + return d - # now make sure that we honor overwrite=False - d.addCallback(lambda res: - self.subdir2.set_uri(u"newchild", - other_file_uri, other_file_uri)) + def _mark_file_bad(self, rootnode): + self.delete_shares_numbered(rootnode.get_uri(), [0]) + return rootnode - d.addCallback(lambda res: - self.shouldFail(ExistingChildError, "move_child_to-no", - "child 'newchild' already exists", - n.move_child_to, u"newchild", - self.subdir2, - overwrite=False)) - d.addCallback(lambda res: self.subdir2.get(u"newchild")) - d.addCallback(lambda child: - self.failUnlessReallyEqual(child.get_uri(), - other_file_uri)) + def test_deepcheck_problems(self): + self.basedir = "dirnode/Dirnode/test_deepcheck_problems" + self.set_up_grid() + d = self._test_deepcheck_create() + d.addCallback(lambda rootnode: self._mark_file_bad(rootnode)) + d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done()) + def _check_results(r): + c = r.get_counters() + self.failUnlessReallyEqual(c, + {"count-objects-checked": 4, + "count-objects-healthy": 3, + "count-objects-unhealthy": 1, + "count-objects-unrecoverable": 0, + "count-corrupt-shares": 0, + }) + #self.failUnlessReallyEqual(len(r.get_problems()), 1) # TODO + d.addCallback(_check_results) + return d + def test_deepcheck_problems_mdmf(self): + self.basedir = "dirnode/Dirnode/test_deepcheck_problems_mdmf" + self.set_up_grid() + d = self._test_deepcheck_create(version=MDMF_VERSION) + d.addCallback(lambda rootnode: self._mark_file_bad(rootnode)) + d.addCallback(lambda rootnode: rootnode.start_deep_check().when_done()) + def _check_results(r): + c = r.get_counters() + self.failUnlessReallyEqual(c, + {"count-objects-checked": 4, + "count-objects-healthy": 3, + "count-objects-unhealthy": 1, + "count-objects-unrecoverable": 0, + "count-corrupt-shares": 0, + }) + #self.failUnlessReallyEqual(len(r.get_problems()), 1) # TODO + d.addCallback(_check_results) + return d - # Setting the no-write field should diminish a mutable cap to read-only - # (for both files and directories). + def _do_readonly_test(self, version=SDMF_VERSION): + c = self.g.clients[0] + nm = c.nodemaker + filecap = make_chk_file_uri(1234) + filenode = nm.create_from_cap(filecap) + uploadable = upload.Data("some data", convergence="some convergence string") - 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 = c.create_dirnode(version=version) + def _created(rw_dn): + backing_node = rw_dn._node + self.failUnlessEqual(backing_node.get_version(), version) + d2 = rw_dn.set_uri(u"child", filecap, filecap) + d2.addCallback(lambda res: rw_dn) + return d2 + d.addCallback(_created) - 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)) + def _ready(rw_dn): + ro_uri = rw_dn.get_readonly_uri() + ro_dn = c.create_node_from_uri(ro_uri) + self.failUnless(ro_dn.is_readonly()) + self.failUnless(ro_dn.is_mutable()) + self.failIf(ro_dn.is_unknown()) + self.failIf(ro_dn.is_allowed_in_immutable_directory()) + ro_dn.raise_error() - 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)) + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, + ro_dn.set_uri, u"newchild", filecap, filecap) + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, + ro_dn.set_node, u"newchild", filenode) + self.shouldFail(dirnode.NotWriteableError, "set_nodes ro", None, + ro_dn.set_nodes, { u"newchild": (filenode, None) }) + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, + ro_dn.add_file, u"newchild", uploadable) + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, + ro_dn.delete, u"child") + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, + ro_dn.create_subdirectory, u"newchild") + self.shouldFail(dirnode.NotWriteableError, "set_metadata_for ro", None, + ro_dn.set_metadata_for, u"child", {}) + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, + ro_dn.move_child_to, u"child", rw_dn) + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, + rw_dn.move_child_to, u"child", ro_dn) + return ro_dn.list() + d.addCallback(_ready) + def _listed(children): + self.failUnless(u"child" in children) + d.addCallback(_listed) + return d - 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)) + def test_readonly(self): + self.basedir = "dirnode/Dirnode/test_readonly" + self.set_up_grid() + return self._do_readonly_test() - return d + def test_readonly_mdmf(self): + self.basedir = "dirnode/Dirnode/test_readonly_mdmf" + self.set_up_grid() + return self._do_readonly_test(version=MDMF_VERSION) - d.addCallback(_then) + def failUnlessGreaterThan(self, a, b): + self.failUnless(a > b, "%r should be > %r" % (a, b)) - d.addErrback(self.explain_error) - return d + def failUnlessGreaterOrEqualThan(self, a, b): + self.failUnless(a >= b, "%r should be >= %r" % (a, b)) + + def test_create(self): + self.basedir = "dirnode/Dirnode/test_create" + self.set_up_grid() + return self._do_create_test() def test_update_metadata(self): (t1, t2, t3) = (626644800.0, 634745640.0, 892226160.0) @@ -1151,13 +1278,11 @@ class Dirnode(GridTestMixin, unittest.TestCase, self.failUnlessEqual(md4, {"bool": True, "number": 42, "tahoe":{"linkcrtime": t1, "linkmotime": t1}}) - def test_create_subdirectory(self): - self.basedir = "dirnode/Dirnode/test_create_subdirectory" - self.set_up_grid() + def _do_create_subdirectory_test(self, version=SDMF_VERSION): c = self.g.clients[0] nm = c.nodemaker - d = c.create_dirnode() + d = c.create_dirnode(version=version) def _then(n): # / self.rootnode = n @@ -1167,7 +1292,8 @@ class Dirnode(GridTestMixin, unittest.TestCase, kids = {u"kid1": (nm.create_from_cap(fake_file_uri), {}), u"kid2": (nm.create_from_cap(other_file_uri), md), } - d = n.create_subdirectory(u"subdir", kids) + d = n.create_subdirectory(u"subdir", kids, + mutable_version=version) def _check(sub): d = n.get_child_at_path(u"subdir") d.addCallback(lambda sub2: self.failUnlessReallyEqual(sub2.get_uri(), @@ -1183,6 +1309,26 @@ class Dirnode(GridTestMixin, unittest.TestCase, d.addCallback(_then) return d + def test_create_subdirectory(self): + self.basedir = "dirnode/Dirnode/test_create_subdirectory" + self.set_up_grid() + return self._do_create_subdirectory_test() + + def test_create_subdirectory_mdmf(self): + self.basedir = "dirnode/Dirnode/test_create_subdirectory_mdmf" + self.set_up_grid() + return self._do_create_subdirectory_test(version=MDMF_VERSION) + + def test_create_mdmf(self): + self.basedir = "dirnode/Dirnode/test_mdmf" + self.set_up_grid() + return self._do_create_test(mdmf=True) + + def test_mdmf_initial_children(self): + self.basedir = "dirnode/Dirnode/test_mdmf" + self.set_up_grid() + return self._do_initial_children_test(mdmf=True) + class MinimalFakeMutableFile: def get_writekey(self): return "writekey" @@ -1301,7 +1447,10 @@ class FakeMutableFile: implements(IMutableFileNode) counter = 0 def __init__(self, initial_contents=""): - self.data = self._get_initial_contents(initial_contents) + data = self._get_initial_contents(initial_contents) + self.data = data.read(data.get_size()) + self.data = "".join(self.data) + counter = FakeMutableFile.counter FakeMutableFile.counter += 1 writekey = hashutil.ssk_writekey_hash(str(counter)) @@ -1348,11 +1497,12 @@ class FakeMutableFile: pass def modify(self, modifier): - self.data = modifier(self.data, None, True) + data = modifier(self.data, None, True) + self.data = data return defer.succeed(None) class FakeNodeMaker(NodeMaker): - def create_mutable_file(self, contents="", keysize=None): + def create_mutable_file(self, contents="", keysize=None, version=None): return defer.succeed(FakeMutableFile(contents)) class FakeClient2(Client): @@ -1551,6 +1701,7 @@ class Dirnode2(testutil.ReallyEqualMixin, testutil.ShouldFailMixin, unittest.Tes self.failUnless(n.get_readonly_uri().startswith("imm."), i) + class DeepStats(testutil.ReallyEqualMixin, unittest.TestCase): timeout = 240 # It takes longer than 120 seconds on Francois's arm box. def test_stats(self):