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_nodes ro", None,
142 ro_dn.set_nodes, [ (u"newchild", filenode) ])
143 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
144 ro_dn.add_file, u"newchild", uploadable)
145 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
146 ro_dn.delete, u"child")
147 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
148 ro_dn.create_empty_directory, u"newchild")
149 self.shouldFail(dirnode.NotMutableError, "set_metadata_for ro", None,
150 ro_dn.set_metadata_for, u"child", {})
151 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
152 ro_dn.move_child_to, u"child", rw_dn)
153 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
154 rw_dn.move_child_to, u"child", ro_dn)
156 d.addCallback(_ready)
157 def _listed(children):
158 self.failUnless(u"child" in children)
159 d.addCallback(_listed)
162 def failUnlessGreaterThan(self, a, b):
163 self.failUnless(a > b, "%r should be > %r" % (a, b))
165 def failUnlessGreaterOrEqualThan(self, a, b):
166 self.failUnless(a >= b, "%r should be >= %r" % (a, b))
168 def test_create(self):
169 self.expected_manifest = []
171 d = self.client.create_empty_dirnode()
174 self.failUnless(n.is_mutable())
177 self.failUnless(u.startswith("URI:DIR2:"), u)
178 u_ro = n.get_readonly_uri()
179 self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
180 u_v = n.get_verifier()
181 self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
182 self.expected_manifest.append(u_v)
185 d.addCallback(lambda res: self.failUnlessEqual(res, {}))
186 d.addCallback(lambda res: n.has_child(u"missing"))
187 d.addCallback(lambda res: self.failIf(res))
188 fake_file_uri = make_mutable_file_uri()
189 m = Marker(fake_file_uri)
190 ffu_v = m.get_verifier()
191 assert isinstance(ffu_v, str)
192 self.expected_manifest.append(ffu_v)
193 d.addCallback(lambda res: n.set_uri(u"child", fake_file_uri))
197 d.addCallback(lambda res: n.create_empty_directory(u"subdir"))
200 # /subdir = directory
201 def _created(subdir):
202 self.failUnless(isinstance(subdir, FakeDirectoryNode))
204 new_v = subdir.get_verifier()
205 assert isinstance(new_v, str)
206 self.expected_manifest.append(new_v)
207 d.addCallback(_created)
209 d.addCallback(lambda res: n.list())
210 d.addCallback(lambda children:
211 self.failUnlessEqual(sorted(children.keys()),
212 sorted([u"child", u"subdir"])))
214 d.addCallback(lambda res: n.build_manifest())
215 def _check_manifest(manifest):
216 self.failUnlessEqual(sorted(manifest),
217 sorted(self.expected_manifest))
218 d.addCallback(_check_manifest)
220 d.addCallback(lambda res: n.deep_stats())
221 def _check_deepstats(stats):
222 self.failUnless(isinstance(stats, dict))
223 expected = {"count-immutable-files": 0,
224 "count-mutable-files": 1,
225 "count-literal-files": 0,
227 "count-directories": 2,
228 "size-immutable-files": 0,
229 "size-literal-files": 0,
230 #"size-directories": 616, # varies
231 #"largest-directory": 616,
232 "largest-directory-children": 2,
233 "largest-immutable-file": 0,
235 for k,v in expected.iteritems():
236 self.failUnlessEqual(stats[k], v,
237 "stats[%s] was %s, not %s" %
239 self.failUnless(stats["size-directories"] > 500,
240 stats["size-directories"])
241 self.failUnless(stats["largest-directory"] > 500,
242 stats["largest-directory"])
243 self.failUnlessEqual(stats["size-files-histogram"], [])
244 d.addCallback(_check_deepstats)
246 def _add_subsubdir(res):
247 return self.subdir.create_empty_directory(u"subsubdir")
248 d.addCallback(_add_subsubdir)
249 d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
250 d.addCallback(lambda subsubdir:
251 self.failUnless(isinstance(subsubdir,
253 d.addCallback(lambda res: n.get_child_at_path(u""))
254 d.addCallback(lambda res: self.failUnlessEqual(res.get_uri(),
257 d.addCallback(lambda res: n.get_metadata_for(u"child"))
258 d.addCallback(lambda metadata:
259 self.failUnlessEqual(sorted(metadata.keys()),
263 # it should be possible to add a child without any metadata
264 d.addCallback(lambda res: n.set_uri(u"c2", fake_file_uri, {}))
265 d.addCallback(lambda res: n.get_metadata_for(u"c2"))
266 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
268 # if we don't set any defaults, the child should get timestamps
269 d.addCallback(lambda res: n.set_uri(u"c3", fake_file_uri))
270 d.addCallback(lambda res: n.get_metadata_for(u"c3"))
271 d.addCallback(lambda metadata:
272 self.failUnlessEqual(sorted(metadata.keys()),
275 # or we can add specific metadata at set_uri() time, which
276 # overrides the timestamps
277 d.addCallback(lambda res: n.set_uri(u"c4", fake_file_uri,
279 d.addCallback(lambda res: n.get_metadata_for(u"c4"))
280 d.addCallback(lambda metadata:
281 self.failUnlessEqual(metadata, {"key": "value"}))
283 d.addCallback(lambda res: n.delete(u"c2"))
284 d.addCallback(lambda res: n.delete(u"c3"))
285 d.addCallback(lambda res: n.delete(u"c4"))
287 # set_node + metadata
288 # it should be possible to add a child without any metadata
289 d.addCallback(lambda res: n.set_node(u"d2", n, {}))
290 d.addCallback(lambda res: n.get_metadata_for(u"d2"))
291 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
293 # if we don't set any defaults, the child should get timestamps
294 d.addCallback(lambda res: n.set_node(u"d3", n))
295 d.addCallback(lambda res: n.get_metadata_for(u"d3"))
296 d.addCallback(lambda metadata:
297 self.failUnlessEqual(sorted(metadata.keys()),
300 # or we can add specific metadata at set_node() time, which
301 # overrides the timestamps
302 d.addCallback(lambda res: n.set_node(u"d4", n,
304 d.addCallback(lambda res: n.get_metadata_for(u"d4"))
305 d.addCallback(lambda metadata:
306 self.failUnlessEqual(metadata, {"key": "value"}))
308 d.addCallback(lambda res: n.delete(u"d2"))
309 d.addCallback(lambda res: n.delete(u"d3"))
310 d.addCallback(lambda res: n.delete(u"d4"))
312 # metadata through set_children()
313 d.addCallback(lambda res: n.set_children([ (u"e1", fake_file_uri),
314 (u"e2", fake_file_uri, {}),
315 (u"e3", fake_file_uri,
318 d.addCallback(lambda res: n.get_metadata_for(u"e1"))
319 d.addCallback(lambda metadata:
320 self.failUnlessEqual(sorted(metadata.keys()),
322 d.addCallback(lambda res: n.get_metadata_for(u"e2"))
323 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
324 d.addCallback(lambda res: n.get_metadata_for(u"e3"))
325 d.addCallback(lambda metadata:
326 self.failUnlessEqual(metadata, {"key": "value"}))
328 d.addCallback(lambda res: n.delete(u"e1"))
329 d.addCallback(lambda res: n.delete(u"e2"))
330 d.addCallback(lambda res: n.delete(u"e3"))
332 # metadata through set_nodes()
333 d.addCallback(lambda res: n.set_nodes([ (u"f1", n),
338 d.addCallback(lambda res: n.get_metadata_for(u"f1"))
339 d.addCallback(lambda metadata:
340 self.failUnlessEqual(sorted(metadata.keys()),
342 d.addCallback(lambda res: n.get_metadata_for(u"f2"))
343 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
344 d.addCallback(lambda res: n.get_metadata_for(u"f3"))
345 d.addCallback(lambda metadata:
346 self.failUnlessEqual(metadata, {"key": "value"}))
348 d.addCallback(lambda res: n.delete(u"f1"))
349 d.addCallback(lambda res: n.delete(u"f2"))
350 d.addCallback(lambda res: n.delete(u"f3"))
353 d.addCallback(lambda res:
354 n.set_metadata_for(u"child",
355 {"tags": ["web2.0-compatible"]}))
356 d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
357 d.addCallback(lambda metadata:
358 self.failUnlessEqual(metadata,
359 {"tags": ["web2.0-compatible"]}))
362 self._start_timestamp = time.time()
363 d.addCallback(_start)
364 # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
365 # floats to hundredeths (it uses str(num) instead of repr(num)).
366 # simplejson-1.7.3 does not have this bug. To prevent this bug
367 # from causing the test to fail, stall for more than a few
368 # hundrededths of a second.
369 d.addCallback(self.stall, 0.1)
370 d.addCallback(lambda res: n.add_file(u"timestamps",
371 upload.Data("stamp me", convergence="some convergence string")))
372 d.addCallback(self.stall, 0.1)
374 self._stop_timestamp = time.time()
377 d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
378 def _check_timestamp1(metadata):
379 self.failUnless("ctime" in metadata)
380 self.failUnless("mtime" in metadata)
381 self.failUnlessGreaterOrEqualThan(metadata["ctime"],
382 self._start_timestamp)
383 self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
385 self.failUnlessGreaterOrEqualThan(metadata["mtime"],
386 self._start_timestamp)
387 self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
389 # Our current timestamp rules say that replacing an existing
390 # child should preserve the 'ctime' but update the mtime
391 self._old_ctime = metadata["ctime"]
392 self._old_mtime = metadata["mtime"]
393 d.addCallback(_check_timestamp1)
394 d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
395 d.addCallback(lambda res: n.set_node(u"timestamps", n))
396 d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
397 def _check_timestamp2(metadata):
398 self.failUnlessEqual(metadata["ctime"], self._old_ctime,
399 "%s != %s" % (metadata["ctime"],
401 self.failUnlessGreaterThan(metadata["mtime"], self._old_mtime)
402 return n.delete(u"timestamps")
403 d.addCallback(_check_timestamp2)
405 # also make sure we can add/update timestamps on a
406 # previously-existing child that didn't have any, since there are
407 # a lot of 0.7.0-generated edges around out there
408 d.addCallback(lambda res: n.set_node(u"no_timestamps", n, {}))
409 d.addCallback(lambda res: n.set_node(u"no_timestamps", n))
410 d.addCallback(lambda res: n.get_metadata_for(u"no_timestamps"))
411 d.addCallback(lambda metadata:
412 self.failUnlessEqual(sorted(metadata.keys()),
414 d.addCallback(lambda res: n.delete(u"no_timestamps"))
416 d.addCallback(lambda res: n.delete(u"subdir"))
417 d.addCallback(lambda old_child:
418 self.failUnlessEqual(old_child.get_uri(),
419 self.subdir.get_uri()))
421 d.addCallback(lambda res: n.list())
422 d.addCallback(lambda children:
423 self.failUnlessEqual(sorted(children.keys()),
426 uploadable = upload.Data("some data", convergence="some convergence string")
427 d.addCallback(lambda res: n.add_file(u"newfile", uploadable))
428 d.addCallback(lambda newnode:
429 self.failUnless(IFileNode.providedBy(newnode)))
430 d.addCallback(lambda res: n.list())
431 d.addCallback(lambda children:
432 self.failUnlessEqual(sorted(children.keys()),
433 sorted([u"child", u"newfile"])))
434 d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
435 d.addCallback(lambda metadata:
436 self.failUnlessEqual(sorted(metadata.keys()),
439 uploadable = upload.Data("some data", convergence="some convergence string")
440 d.addCallback(lambda res: n.add_file(u"newfile-metadata",
443 d.addCallback(lambda newnode:
444 self.failUnless(IFileNode.providedBy(newnode)))
445 d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
446 d.addCallback(lambda metadata:
447 self.failUnlessEqual(metadata, {"key": "value"}))
448 d.addCallback(lambda res: n.delete(u"newfile-metadata"))
450 d.addCallback(lambda res: n.create_empty_directory(u"subdir2"))
451 def _created2(subdir2):
452 self.subdir2 = subdir2
453 d.addCallback(_created2)
455 d.addCallback(lambda res:
456 n.move_child_to(u"child", self.subdir2))
457 d.addCallback(lambda res: n.list())
458 d.addCallback(lambda children:
459 self.failUnlessEqual(sorted(children.keys()),
460 sorted([u"newfile", u"subdir2"])))
461 d.addCallback(lambda res: self.subdir2.list())
462 d.addCallback(lambda children:
463 self.failUnlessEqual(sorted(children.keys()),
472 class DeepStats(unittest.TestCase):
473 def test_stats(self):
474 ds = dirnode.DeepStats()
475 ds.add("count-files")
476 ds.add("size-immutable-files", 123)
477 ds.histogram("size-files-histogram", 123)
478 ds.max("largest-directory", 444)
481 self.failUnlessEqual(s["count-files"], 1)
482 self.failUnlessEqual(s["size-immutable-files"], 123)
483 self.failUnlessEqual(s["largest-directory"], 444)
484 self.failUnlessEqual(s["count-literal-files"], 0)
486 ds.add("count-files")
487 ds.add("size-immutable-files", 321)
488 ds.histogram("size-files-histogram", 321)
489 ds.max("largest-directory", 2)
492 self.failUnlessEqual(s["count-files"], 2)
493 self.failUnlessEqual(s["size-immutable-files"], 444)
494 self.failUnlessEqual(s["largest-directory"], 444)
495 self.failUnlessEqual(s["count-literal-files"], 0)
496 self.failUnlessEqual(s["size-files-histogram"],
497 [ (101, 316, 1), (317, 1000, 1) ])
499 ds = dirnode.DeepStats()
500 for i in range(1, 1100):
501 ds.histogram("size-files-histogram", i)
502 ds.histogram("size-files-histogram", 4*1000*1000*1000*1000) # 4TB
504 self.failUnlessEqual(s["size-files-histogram"],
512 (3162277660169L, 10000000000000L, 1),
516 netstring = hashutil.netstring
517 split_netstring = dirnode.split_netstring
519 class Netstring(unittest.TestCase):
520 def test_split(self):
521 a = netstring("hello") + netstring("world")
522 self.failUnlessEqual(split_netstring(a, 2), ("hello", "world"))
523 self.failUnlessEqual(split_netstring(a, 2, False), ("hello", "world"))
524 self.failUnlessEqual(split_netstring(a, 2, True),
525 ("hello", "world", ""))
526 self.failUnlessRaises(ValueError, split_netstring, a, 3)
527 self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2)
528 self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2, False)
530 def test_extra(self):
531 a = netstring("hello")
532 self.failUnlessEqual(split_netstring(a, 1, True), ("hello", ""))
533 b = netstring("hello") + "extra stuff"
534 self.failUnlessEqual(split_netstring(b, 1, True),
535 ("hello", "extra stuff"))
537 def test_nested(self):
538 a = netstring("hello") + netstring("world") + "extra stuff"
539 b = netstring("a") + netstring("is") + netstring(a) + netstring(".")
540 top = split_netstring(b, 4)
541 self.failUnlessEqual(len(top), 4)
542 self.failUnlessEqual(top[0], "a")
543 self.failUnlessEqual(top[1], "is")
544 self.failUnlessEqual(top[2], a)
545 self.failUnlessEqual(top[3], ".")
546 self.failUnlessRaises(ValueError, split_netstring, a, 2)
547 self.failUnlessRaises(ValueError, split_netstring, a, 2, False)
548 bottom = split_netstring(a, 2, True)
549 self.failUnlessEqual(bottom, ("hello", "world", "extra stuff"))