3 from zope.interface import implements
4 from twisted.trial import unittest
5 from allmydata import uri, dirnode, upload
6 from allmydata.interfaces import IURI, IClient, IMutableFileNode, \
7 INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode
8 from allmydata.util import hashutil, testutil
9 from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \
10 FakeDirectoryNode, create_chk_filenode
12 # to test dirnode.py, we want to construct a tree of real DirectoryNodes that
13 # contain pointers to fake files. We start with a fake MutableFileNode that
14 # stores all of its data in a static table.
17 implements(IFileNode, IMutableFileNode) # sure, why not
18 def __init__(self, nodeuri):
19 if not isinstance(nodeuri, str):
20 nodeuri = nodeuri.to_string()
21 self.nodeuri = nodeuri
22 si = hashutil.tagged_hash("tag1", nodeuri)[:16]
23 fp = hashutil.tagged_hash("tag2", nodeuri)
24 self.verifieruri = uri.SSKVerifierURI(storage_index=si,
25 fingerprint=fp).to_string()
28 def get_readonly_uri(self):
30 def get_verifier(self):
31 return self.verifieruri
33 # dirnode requires three methods from the client: upload(),
34 # create_node_from_uri(), and create_empty_dirnode(). Of these, upload() is
35 # only used by the convenience composite method add_file().
40 def upload(self, uploadable):
41 d = uploadable.get_size()
42 d.addCallback(lambda size: uploadable.read(size))
45 n = create_chk_filenode(self, data)
46 results = upload.UploadResults()
47 results.uri = n.get_uri()
49 d.addCallback(_got_data)
52 def create_node_from_uri(self, u):
54 if (INewDirectoryURI.providedBy(u)
55 or IReadonlyNewDirectoryURI.providedBy(u)):
56 return FakeDirectoryNode(self).init_from_uri(u)
57 return Marker(u.to_string())
59 def create_empty_dirnode(self):
60 n = FakeDirectoryNode(self)
62 d.addCallback(lambda res: n)
66 class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
68 self.client = FakeClient()
71 d = self.client.create_empty_dirnode()
73 self.failUnless(isinstance(res, FakeDirectoryNode))
75 self.failUnless("RW" in rep)
79 def test_corrupt(self):
80 d = self.client.create_empty_dirnode()
82 u = make_mutable_file_uri()
83 d = dn.set_uri(u"child", u, {})
84 d.addCallback(lambda res: dn.list())
85 def _check1(children):
86 self.failUnless(u"child" in children)
87 d.addCallback(_check1)
88 d.addCallback(lambda res:
89 self.shouldFail(KeyError, "get bogus", None,
93 si = IURI(filenode.get_uri()).storage_index
94 old_contents = filenode.all_contents[si]
95 # we happen to know that the writecap is encrypted near the
96 # end of the string. Flip one of its bits and make sure we
97 # detect the corruption.
98 new_contents = testutil.flip_bit(old_contents, -10)
99 # TODO: also test flipping bits in the other portions
100 filenode.all_contents[si] = new_contents
101 d.addCallback(_corrupt)
103 self.shouldFail(hashutil.IntegrityCheckError, "corrupt",
104 "HMAC does not match, crypttext is corrupted",
106 d.addCallback(_check2)
108 d.addCallback(_created)
111 def test_check(self):
112 d = self.client.create_empty_dirnode()
113 d.addCallback(lambda dn: dn.check())
119 def test_readonly(self):
120 fileuri = make_chk_file_uri(1234)
121 filenode = self.client.create_node_from_uri(fileuri)
122 uploadable = upload.Data("some data", convergence="some convergence string")
124 d = self.client.create_empty_dirnode()
126 d2 = rw_dn.set_uri(u"child", fileuri)
127 d2.addCallback(lambda res: rw_dn)
129 d.addCallback(_created)
132 ro_uri = rw_dn.get_readonly_uri()
133 ro_dn = self.client.create_node_from_uri(ro_uri)
134 self.failUnless(ro_dn.is_readonly())
135 self.failUnless(ro_dn.is_mutable())
137 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
138 ro_dn.set_uri, u"newchild", fileuri)
139 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
140 ro_dn.set_node, u"newchild", filenode)
141 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
142 ro_dn.add_file, u"newchild", uploadable)
143 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
144 ro_dn.delete, u"child")
145 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
146 ro_dn.create_empty_directory, u"newchild")
147 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
148 ro_dn.move_child_to, u"child", rw_dn)
149 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
150 rw_dn.move_child_to, u"child", ro_dn)
152 d.addCallback(_ready)
153 def _listed(children):
154 self.failUnless(u"child" in children)
155 d.addCallback(_listed)
158 def failUnlessGreaterThan(self, a, b):
159 self.failUnless(a > b, "%r should be > %r" % (a, b))
161 def failUnlessGreaterOrEqualThan(self, a, b):
162 self.failUnless(a >= b, "%r should be >= %r" % (a, b))
164 def test_create(self):
165 self.expected_manifest = []
167 d = self.client.create_empty_dirnode()
169 self.failUnless(n.is_mutable())
172 self.failUnless(u.startswith("URI:DIR2:"), u)
173 u_ro = n.get_readonly_uri()
174 self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
175 u_v = n.get_verifier()
176 self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
177 self.expected_manifest.append(u_v)
180 d.addCallback(lambda res: self.failUnlessEqual(res, {}))
181 d.addCallback(lambda res: n.has_child(u"missing"))
182 d.addCallback(lambda res: self.failIf(res))
183 fake_file_uri = make_mutable_file_uri()
184 m = Marker(fake_file_uri)
185 ffu_v = m.get_verifier()
186 assert isinstance(ffu_v, str)
187 self.expected_manifest.append(ffu_v)
188 d.addCallback(lambda res: n.set_uri(u"child", fake_file_uri))
190 d.addCallback(lambda res: n.create_empty_directory(u"subdir"))
191 def _created(subdir):
192 self.failUnless(isinstance(subdir, FakeDirectoryNode))
194 new_v = subdir.get_verifier()
195 assert isinstance(new_v, str)
196 self.expected_manifest.append(new_v)
197 d.addCallback(_created)
199 d.addCallback(lambda res: n.list())
200 d.addCallback(lambda children:
201 self.failUnlessEqual(sorted(children.keys()),
202 sorted([u"child", u"subdir"])))
204 d.addCallback(lambda res: n.build_manifest())
205 def _check_manifest(manifest):
206 self.failUnlessEqual(sorted(manifest),
207 sorted(self.expected_manifest))
208 d.addCallback(_check_manifest)
210 d.addCallback(lambda res: n.deep_stats())
211 def _check_deepstats(stats):
212 self.failUnless(isinstance(stats, dict))
213 expected = {"count-immutable-files": 0,
214 "count-mutable-files": 1,
215 "count-literal-files": 0,
217 "count-directories": 2,
218 "size-immutable-files": 0,
219 "size-literal-files": 0,
220 #"size-directories": 616, # varies
221 #"largest-directory": 616,
222 "largest-directory-children": 2,
223 "largest-immutable-file": 0,
225 for k,v in expected.iteritems():
226 self.failUnlessEqual(stats[k], v,
227 "stats[%s] was %s, not %s" %
229 self.failUnless(stats["size-directories"] > 600,
230 stats["size-directories"])
231 self.failUnless(stats["largest-directory"] > 600,
232 stats["largest-directory"])
233 d.addCallback(_check_deepstats)
235 def _add_subsubdir(res):
236 return self.subdir.create_empty_directory(u"subsubdir")
237 d.addCallback(_add_subsubdir)
238 d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
239 d.addCallback(lambda subsubdir:
240 self.failUnless(isinstance(subsubdir,
242 d.addCallback(lambda res: n.get_child_at_path(u""))
243 d.addCallback(lambda res: self.failUnlessEqual(res.get_uri(),
246 d.addCallback(lambda res: n.get_metadata_for(u"child"))
247 d.addCallback(lambda metadata:
248 self.failUnlessEqual(sorted(metadata.keys()),
252 # it should be possible to add a child without any metadata
253 d.addCallback(lambda res: n.set_uri(u"c2", fake_file_uri, {}))
254 d.addCallback(lambda res: n.get_metadata_for(u"c2"))
255 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
257 # if we don't set any defaults, the child should get timestamps
258 d.addCallback(lambda res: n.set_uri(u"c3", fake_file_uri))
259 d.addCallback(lambda res: n.get_metadata_for(u"c3"))
260 d.addCallback(lambda metadata:
261 self.failUnlessEqual(sorted(metadata.keys()),
264 # or we can add specific metadata at set_uri() time, which
265 # overrides the timestamps
266 d.addCallback(lambda res: n.set_uri(u"c4", fake_file_uri,
268 d.addCallback(lambda res: n.get_metadata_for(u"c4"))
269 d.addCallback(lambda metadata:
270 self.failUnlessEqual(metadata, {"key": "value"}))
272 d.addCallback(lambda res: n.delete(u"c2"))
273 d.addCallback(lambda res: n.delete(u"c3"))
274 d.addCallback(lambda res: n.delete(u"c4"))
276 # set_node + metadata
277 # it should be possible to add a child without any metadata
278 d.addCallback(lambda res: n.set_node(u"d2", n, {}))
279 d.addCallback(lambda res: n.get_metadata_for(u"d2"))
280 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
282 # if we don't set any defaults, the child should get timestamps
283 d.addCallback(lambda res: n.set_node(u"d3", n))
284 d.addCallback(lambda res: n.get_metadata_for(u"d3"))
285 d.addCallback(lambda metadata:
286 self.failUnlessEqual(sorted(metadata.keys()),
289 # or we can add specific metadata at set_node() time, which
290 # overrides the timestamps
291 d.addCallback(lambda res: n.set_node(u"d4", n,
293 d.addCallback(lambda res: n.get_metadata_for(u"d4"))
294 d.addCallback(lambda metadata:
295 self.failUnlessEqual(metadata, {"key": "value"}))
297 d.addCallback(lambda res: n.delete(u"d2"))
298 d.addCallback(lambda res: n.delete(u"d3"))
299 d.addCallback(lambda res: n.delete(u"d4"))
301 # metadata through set_children()
302 d.addCallback(lambda res: n.set_children([ (u"e1", fake_file_uri),
303 (u"e2", fake_file_uri, {}),
304 (u"e3", fake_file_uri,
307 d.addCallback(lambda res: n.get_metadata_for(u"e1"))
308 d.addCallback(lambda metadata:
309 self.failUnlessEqual(sorted(metadata.keys()),
311 d.addCallback(lambda res: n.get_metadata_for(u"e2"))
312 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
313 d.addCallback(lambda res: n.get_metadata_for(u"e3"))
314 d.addCallback(lambda metadata:
315 self.failUnlessEqual(metadata, {"key": "value"}))
317 d.addCallback(lambda res: n.delete(u"e1"))
318 d.addCallback(lambda res: n.delete(u"e2"))
319 d.addCallback(lambda res: n.delete(u"e3"))
321 # metadata through set_nodes()
322 d.addCallback(lambda res: n.set_nodes([ (u"f1", n),
327 d.addCallback(lambda res: n.get_metadata_for(u"f1"))
328 d.addCallback(lambda metadata:
329 self.failUnlessEqual(sorted(metadata.keys()),
331 d.addCallback(lambda res: n.get_metadata_for(u"f2"))
332 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
333 d.addCallback(lambda res: n.get_metadata_for(u"f3"))
334 d.addCallback(lambda metadata:
335 self.failUnlessEqual(metadata, {"key": "value"}))
337 d.addCallback(lambda res: n.delete(u"f1"))
338 d.addCallback(lambda res: n.delete(u"f2"))
339 d.addCallback(lambda res: n.delete(u"f3"))
342 d.addCallback(lambda res:
343 n.set_metadata_for(u"child",
344 {"tags": ["web2.0-compatible"]}))
345 d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
346 d.addCallback(lambda metadata:
347 self.failUnlessEqual(metadata,
348 {"tags": ["web2.0-compatible"]}))
351 self._start_timestamp = time.time()
352 d.addCallback(_start)
353 # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
354 # floats to hundredeths (it uses str(num) instead of repr(num)).
355 # simplejson-1.7.3 does not have this bug. To prevent this bug
356 # from causing the test to fail, stall for more than a few
357 # hundrededths of a second.
358 d.addCallback(self.stall, 0.1)
359 d.addCallback(lambda res: n.add_file(u"timestamps",
360 upload.Data("stamp me", convergence="some convergence string")))
361 d.addCallback(self.stall, 0.1)
363 self._stop_timestamp = time.time()
366 d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
367 def _check_timestamp1(metadata):
368 self.failUnless("ctime" in metadata)
369 self.failUnless("mtime" in metadata)
370 self.failUnlessGreaterOrEqualThan(metadata["ctime"],
371 self._start_timestamp)
372 self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
374 self.failUnlessGreaterOrEqualThan(metadata["mtime"],
375 self._start_timestamp)
376 self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
378 # Our current timestamp rules say that replacing an existing
379 # child should preserve the 'ctime' but update the mtime
380 self._old_ctime = metadata["ctime"]
381 self._old_mtime = metadata["mtime"]
382 d.addCallback(_check_timestamp1)
383 d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
384 d.addCallback(lambda res: n.set_node(u"timestamps", n))
385 d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
386 def _check_timestamp2(metadata):
387 self.failUnlessEqual(metadata["ctime"], self._old_ctime,
388 "%s != %s" % (metadata["ctime"],
390 self.failUnlessGreaterThan(metadata["mtime"], self._old_mtime)
391 return n.delete(u"timestamps")
392 d.addCallback(_check_timestamp2)
394 # also make sure we can add/update timestamps on a
395 # previously-existing child that didn't have any, since there are
396 # a lot of 0.7.0-generated edges around out there
397 d.addCallback(lambda res: n.set_node(u"no_timestamps", n, {}))
398 d.addCallback(lambda res: n.set_node(u"no_timestamps", n))
399 d.addCallback(lambda res: n.get_metadata_for(u"no_timestamps"))
400 d.addCallback(lambda metadata:
401 self.failUnlessEqual(sorted(metadata.keys()),
403 d.addCallback(lambda res: n.delete(u"no_timestamps"))
405 d.addCallback(lambda res: n.delete(u"subdir"))
406 d.addCallback(lambda old_child:
407 self.failUnlessEqual(old_child.get_uri(),
408 self.subdir.get_uri()))
410 d.addCallback(lambda res: n.list())
411 d.addCallback(lambda children:
412 self.failUnlessEqual(sorted(children.keys()),
415 uploadable = upload.Data("some data", convergence="some convergence string")
416 d.addCallback(lambda res: n.add_file(u"newfile", uploadable))
417 d.addCallback(lambda newnode:
418 self.failUnless(IFileNode.providedBy(newnode)))
419 d.addCallback(lambda res: n.list())
420 d.addCallback(lambda children:
421 self.failUnlessEqual(sorted(children.keys()),
422 sorted([u"child", u"newfile"])))
423 d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
424 d.addCallback(lambda metadata:
425 self.failUnlessEqual(sorted(metadata.keys()),
428 uploadable = upload.Data("some data", convergence="some convergence string")
429 d.addCallback(lambda res: n.add_file(u"newfile-metadata",
432 d.addCallback(lambda newnode:
433 self.failUnless(IFileNode.providedBy(newnode)))
434 d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
435 d.addCallback(lambda metadata:
436 self.failUnlessEqual(metadata, {"key": "value"}))
437 d.addCallback(lambda res: n.delete(u"newfile-metadata"))
439 d.addCallback(lambda res: n.create_empty_directory(u"subdir2"))
440 def _created2(subdir2):
441 self.subdir2 = subdir2
442 d.addCallback(_created2)
444 d.addCallback(lambda res:
445 n.move_child_to(u"child", self.subdir2))
446 d.addCallback(lambda res: n.list())
447 d.addCallback(lambda children:
448 self.failUnlessEqual(sorted(children.keys()),
449 sorted([u"newfile", u"subdir2"])))
450 d.addCallback(lambda res: self.subdir2.list())
451 d.addCallback(lambda children:
452 self.failUnlessEqual(sorted(children.keys()),
462 netstring = hashutil.netstring
463 split_netstring = dirnode.split_netstring
465 class Netstring(unittest.TestCase):
466 def test_split(self):
467 a = netstring("hello") + netstring("world")
468 self.failUnlessEqual(split_netstring(a, 2), ("hello", "world"))
469 self.failUnlessEqual(split_netstring(a, 2, False), ("hello", "world"))
470 self.failUnlessEqual(split_netstring(a, 2, True),
471 ("hello", "world", ""))
472 self.failUnlessRaises(ValueError, split_netstring, a, 3)
473 self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2)
474 self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2, False)
476 def test_extra(self):
477 a = netstring("hello")
478 self.failUnlessEqual(split_netstring(a, 1, True), ("hello", ""))
479 b = netstring("hello") + "extra stuff"
480 self.failUnlessEqual(split_netstring(b, 1, True),
481 ("hello", "extra stuff"))
483 def test_nested(self):
484 a = netstring("hello") + netstring("world") + "extra stuff"
485 b = netstring("a") + netstring("is") + netstring(a) + netstring(".")
486 top = split_netstring(b, 4)
487 self.failUnlessEqual(len(top), 4)
488 self.failUnlessEqual(top[0], "a")
489 self.failUnlessEqual(top[1], "is")
490 self.failUnlessEqual(top[2], a)
491 self.failUnlessEqual(top[3], ".")
492 self.failUnlessRaises(ValueError, split_netstring, a, 2)
493 self.failUnlessRaises(ValueError, split_netstring, a, 2, False)
494 bottom = split_netstring(a, 2, True)
495 self.failUnlessEqual(bottom, ("hello", "world", "extra stuff"))