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
11 from twisted.internet import defer, reactor
13 # to test dirnode.py, we want to construct a tree of real DirectoryNodes that
14 # contain pointers to fake files. We start with a fake MutableFileNode that
15 # stores all of its data in a static table.
18 implements(IFileNode, IMutableFileNode) # sure, why not
19 def __init__(self, nodeuri):
20 if not isinstance(nodeuri, str):
21 nodeuri = nodeuri.to_string()
22 self.nodeuri = nodeuri
23 si = hashutil.tagged_hash("tag1", nodeuri)[:16]
24 fp = hashutil.tagged_hash("tag2", nodeuri)
25 self.verifieruri = uri.SSKVerifierURI(storage_index=si,
26 fingerprint=fp).to_string()
29 def get_readonly_uri(self):
31 def get_verifier(self):
32 return self.verifieruri
34 # dirnode requires three methods from the client: upload(),
35 # create_node_from_uri(), and create_empty_dirnode(). Of these, upload() is
36 # only used by the convenience composite method add_file().
41 def upload(self, uploadable):
42 d = uploadable.get_size()
43 d.addCallback(lambda size: uploadable.read(size))
46 n = create_chk_filenode(self, data)
47 results = upload.UploadResults()
48 results.uri = n.get_uri()
50 d.addCallback(_got_data)
53 def create_node_from_uri(self, u):
55 if (INewDirectoryURI.providedBy(u)
56 or IReadonlyNewDirectoryURI.providedBy(u)):
57 return FakeDirectoryNode(self).init_from_uri(u)
58 return Marker(u.to_string())
60 def create_empty_dirnode(self):
61 n = FakeDirectoryNode(self)
63 d.addCallback(lambda res: n)
67 class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
69 self.client = FakeClient()
72 d = self.client.create_empty_dirnode()
74 self.failUnless(isinstance(res, FakeDirectoryNode))
76 self.failUnless("RW" in rep)
80 def test_corrupt(self):
81 d = self.client.create_empty_dirnode()
83 u = make_mutable_file_uri()
84 d = dn.set_uri(u"child", u, {})
85 d.addCallback(lambda res: dn.list())
86 def _check1(children):
87 self.failUnless(u"child" in children)
88 d.addCallback(_check1)
89 d.addCallback(lambda res:
90 self.shouldFail(KeyError, "get bogus", None,
94 si = IURI(filenode.get_uri()).storage_index
95 old_contents = filenode.all_contents[si]
96 # we happen to know that the writecap is encrypted near the
97 # end of the string. Flip one of its bits and make sure we
98 # detect the corruption.
99 new_contents = testutil.flip_bit(old_contents, -10)
100 # TODO: also test flipping bits in the other portions
101 filenode.all_contents[si] = new_contents
102 d.addCallback(_corrupt)
104 self.shouldFail(hashutil.IntegrityCheckError, "corrupt",
105 "HMAC does not match, crypttext is corrupted",
107 d.addCallback(_check2)
109 d.addCallback(_created)
112 def test_check(self):
113 d = self.client.create_empty_dirnode()
114 d.addCallback(lambda dn: dn.check())
120 def test_readonly(self):
121 fileuri = make_chk_file_uri(1234)
122 filenode = self.client.create_node_from_uri(fileuri)
123 uploadable = upload.Data("some data")
125 d = self.client.create_empty_dirnode()
127 d2 = rw_dn.set_uri(u"child", fileuri)
128 d2.addCallback(lambda res: rw_dn)
130 d.addCallback(_created)
133 ro_uri = rw_dn.get_readonly_uri()
134 ro_dn = self.client.create_node_from_uri(ro_uri)
135 self.failUnless(ro_dn.is_readonly())
136 self.failUnless(ro_dn.is_mutable())
138 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
139 ro_dn.set_uri, u"newchild", fileuri)
140 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
141 ro_dn.set_node, u"newchild", filenode)
142 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
143 ro_dn.add_file, u"newchild", uploadable)
144 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
145 ro_dn.delete, u"child")
146 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
147 ro_dn.create_empty_directory, u"newchild")
148 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
149 ro_dn.move_child_to, u"child", rw_dn)
150 self.shouldFail(dirnode.NotMutableError, "set_uri ro", None,
151 rw_dn.move_child_to, u"child", ro_dn)
153 d.addCallback(_ready)
154 def _listed(children):
155 self.failUnless(u"child" in children)
156 d.addCallback(_listed)
159 def failUnlessGreaterThan(self, a, b):
160 self.failUnless(a > b, "%r should be > %r" % (a, b))
162 def failUnlessGreaterOrEqualThan(self, a, b):
163 self.failUnless(a >= b, "%r should be >= %r" % (a, b))
165 def stall(self, res, delay=1.0):
167 reactor.callLater(delay, d.callback, res)
170 def test_create(self):
171 self.expected_manifest = []
173 d = self.client.create_empty_dirnode()
175 self.failUnless(n.is_mutable())
178 self.failUnless(u.startswith("URI:DIR2:"), u)
179 u_ro = n.get_readonly_uri()
180 self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
181 u_v = n.get_verifier()
182 self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
183 self.expected_manifest.append(u_v)
186 d.addCallback(lambda res: self.failUnlessEqual(res, {}))
187 d.addCallback(lambda res: n.has_child(u"missing"))
188 d.addCallback(lambda res: self.failIf(res))
189 fake_file_uri = make_mutable_file_uri()
190 m = Marker(fake_file_uri)
191 ffu_v = m.get_verifier()
192 assert isinstance(ffu_v, str)
193 self.expected_manifest.append(ffu_v)
194 d.addCallback(lambda res: n.set_uri(u"child", fake_file_uri))
196 d.addCallback(lambda res: n.create_empty_directory(u"subdir"))
197 def _created(subdir):
198 self.failUnless(isinstance(subdir, FakeDirectoryNode))
200 new_v = subdir.get_verifier()
201 assert isinstance(new_v, str)
202 self.expected_manifest.append(new_v)
203 d.addCallback(_created)
205 d.addCallback(lambda res: n.list())
206 d.addCallback(lambda children:
207 self.failUnlessEqual(sorted(children.keys()),
208 sorted([u"child", u"subdir"])))
210 d.addCallback(lambda res: n.build_manifest())
211 def _check_manifest(manifest):
212 self.failUnlessEqual(sorted(manifest),
213 sorted(self.expected_manifest))
214 d.addCallback(_check_manifest)
216 def _add_subsubdir(res):
217 return self.subdir.create_empty_directory(u"subsubdir")
218 d.addCallback(_add_subsubdir)
219 d.addCallback(lambda res: n.get_child_at_path(u"subdir/subsubdir"))
220 d.addCallback(lambda subsubdir:
221 self.failUnless(isinstance(subsubdir,
223 d.addCallback(lambda res: n.get_child_at_path(u""))
224 d.addCallback(lambda res: self.failUnlessEqual(res.get_uri(),
227 d.addCallback(lambda res: n.get_metadata_for(u"child"))
228 d.addCallback(lambda metadata:
229 self.failUnlessEqual(sorted(metadata.keys()),
233 # it should be possible to add a child without any metadata
234 d.addCallback(lambda res: n.set_uri(u"c2", fake_file_uri, {}))
235 d.addCallback(lambda res: n.get_metadata_for(u"c2"))
236 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
238 # if we don't set any defaults, the child should get timestamps
239 d.addCallback(lambda res: n.set_uri(u"c3", fake_file_uri))
240 d.addCallback(lambda res: n.get_metadata_for(u"c3"))
241 d.addCallback(lambda metadata:
242 self.failUnlessEqual(sorted(metadata.keys()),
245 # or we can add specific metadata at set_uri() time, which
246 # overrides the timestamps
247 d.addCallback(lambda res: n.set_uri(u"c4", fake_file_uri,
249 d.addCallback(lambda res: n.get_metadata_for(u"c4"))
250 d.addCallback(lambda metadata:
251 self.failUnlessEqual(metadata, {"key": "value"}))
253 d.addCallback(lambda res: n.delete(u"c2"))
254 d.addCallback(lambda res: n.delete(u"c3"))
255 d.addCallback(lambda res: n.delete(u"c4"))
257 # set_node + metadata
258 # it should be possible to add a child without any metadata
259 d.addCallback(lambda res: n.set_node(u"d2", n, {}))
260 d.addCallback(lambda res: n.get_metadata_for(u"d2"))
261 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
263 # if we don't set any defaults, the child should get timestamps
264 d.addCallback(lambda res: n.set_node(u"d3", n))
265 d.addCallback(lambda res: n.get_metadata_for(u"d3"))
266 d.addCallback(lambda metadata:
267 self.failUnlessEqual(sorted(metadata.keys()),
270 # or we can add specific metadata at set_node() time, which
271 # overrides the timestamps
272 d.addCallback(lambda res: n.set_node(u"d4", n,
274 d.addCallback(lambda res: n.get_metadata_for(u"d4"))
275 d.addCallback(lambda metadata:
276 self.failUnlessEqual(metadata, {"key": "value"}))
278 d.addCallback(lambda res: n.delete(u"d2"))
279 d.addCallback(lambda res: n.delete(u"d3"))
280 d.addCallback(lambda res: n.delete(u"d4"))
282 # metadata through set_children()
283 d.addCallback(lambda res: n.set_children([ (u"e1", fake_file_uri),
284 (u"e2", fake_file_uri, {}),
285 (u"e3", fake_file_uri,
288 d.addCallback(lambda res: n.get_metadata_for(u"e1"))
289 d.addCallback(lambda metadata:
290 self.failUnlessEqual(sorted(metadata.keys()),
292 d.addCallback(lambda res: n.get_metadata_for(u"e2"))
293 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
294 d.addCallback(lambda res: n.get_metadata_for(u"e3"))
295 d.addCallback(lambda metadata:
296 self.failUnlessEqual(metadata, {"key": "value"}))
298 d.addCallback(lambda res: n.delete(u"e1"))
299 d.addCallback(lambda res: n.delete(u"e2"))
300 d.addCallback(lambda res: n.delete(u"e3"))
302 # metadata through set_nodes()
303 d.addCallback(lambda res: n.set_nodes([ (u"f1", n),
308 d.addCallback(lambda res: n.get_metadata_for(u"f1"))
309 d.addCallback(lambda metadata:
310 self.failUnlessEqual(sorted(metadata.keys()),
312 d.addCallback(lambda res: n.get_metadata_for(u"f2"))
313 d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
314 d.addCallback(lambda res: n.get_metadata_for(u"f3"))
315 d.addCallback(lambda metadata:
316 self.failUnlessEqual(metadata, {"key": "value"}))
318 d.addCallback(lambda res: n.delete(u"f1"))
319 d.addCallback(lambda res: n.delete(u"f2"))
320 d.addCallback(lambda res: n.delete(u"f3"))
323 d.addCallback(lambda res:
324 n.set_metadata_for(u"child",
325 {"tags": ["web2.0-compatible"]}))
326 d.addCallback(lambda n1: n1.get_metadata_for(u"child"))
327 d.addCallback(lambda metadata:
328 self.failUnlessEqual(metadata,
329 {"tags": ["web2.0-compatible"]}))
332 self._start_timestamp = time.time()
333 d.addCallback(_start)
334 # simplejson-1.7.1 (as shipped on Ubuntu 'gutsy') rounds all
335 # floats to hundredeths (it uses str(num) instead of repr(num)).
336 # simplejson-1.7.3 does not have this bug. To prevent this bug
337 # from causing the test to fail, stall for more than a few
338 # hundrededths of a second.
339 d.addCallback(self.stall, 0.1)
340 d.addCallback(lambda res: n.add_file(u"timestamps",
341 upload.Data("stamp me")))
342 d.addCallback(self.stall, 0.1)
344 self._stop_timestamp = time.time()
347 d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
348 def _check_timestamp1(metadata):
349 self.failUnless("ctime" in metadata)
350 self.failUnless("mtime" in metadata)
351 self.failUnlessGreaterOrEqualThan(metadata["ctime"],
352 self._start_timestamp)
353 self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
355 self.failUnlessGreaterOrEqualThan(metadata["mtime"],
356 self._start_timestamp)
357 self.failUnlessGreaterOrEqualThan(self._stop_timestamp,
359 # Our current timestamp rules say that replacing an existing
360 # child should preserve the 'ctime' but update the mtime
361 self._old_ctime = metadata["ctime"]
362 self._old_mtime = metadata["mtime"]
363 d.addCallback(_check_timestamp1)
364 d.addCallback(self.stall, 2.0) # accomodate low-res timestamps
365 d.addCallback(lambda res: n.set_node(u"timestamps", n))
366 d.addCallback(lambda res: n.get_metadata_for(u"timestamps"))
367 def _check_timestamp2(metadata):
368 self.failUnlessEqual(metadata["ctime"], self._old_ctime,
369 "%s != %s" % (metadata["ctime"],
371 self.failUnlessGreaterThan(metadata["mtime"], self._old_mtime)
372 return n.delete(u"timestamps")
373 d.addCallback(_check_timestamp2)
375 # also make sure we can add/update timestamps on a
376 # previously-existing child that didn't have any, since there are
377 # a lot of 0.7.0-generated edges around out there
378 d.addCallback(lambda res: n.set_node(u"no_timestamps", n, {}))
379 d.addCallback(lambda res: n.set_node(u"no_timestamps", n))
380 d.addCallback(lambda res: n.get_metadata_for(u"no_timestamps"))
381 d.addCallback(lambda metadata:
382 self.failUnlessEqual(sorted(metadata.keys()),
384 d.addCallback(lambda res: n.delete(u"no_timestamps"))
386 d.addCallback(lambda res: n.delete(u"subdir"))
387 d.addCallback(lambda old_child:
388 self.failUnlessEqual(old_child.get_uri(),
389 self.subdir.get_uri()))
391 d.addCallback(lambda res: n.list())
392 d.addCallback(lambda children:
393 self.failUnlessEqual(sorted(children.keys()),
396 uploadable = upload.Data("some data")
397 d.addCallback(lambda res: n.add_file(u"newfile", uploadable))
398 d.addCallback(lambda newnode:
399 self.failUnless(IFileNode.providedBy(newnode)))
400 d.addCallback(lambda res: n.list())
401 d.addCallback(lambda children:
402 self.failUnlessEqual(sorted(children.keys()),
403 sorted([u"child", u"newfile"])))
404 d.addCallback(lambda res: n.get_metadata_for(u"newfile"))
405 d.addCallback(lambda metadata:
406 self.failUnlessEqual(sorted(metadata.keys()),
409 uploadable = upload.Data("some data")
410 d.addCallback(lambda res: n.add_file(u"newfile-metadata",
413 d.addCallback(lambda newnode:
414 self.failUnless(IFileNode.providedBy(newnode)))
415 d.addCallback(lambda res: n.get_metadata_for(u"newfile-metadata"))
416 d.addCallback(lambda metadata:
417 self.failUnlessEqual(metadata, {"key": "value"}))
418 d.addCallback(lambda res: n.delete(u"newfile-metadata"))
420 d.addCallback(lambda res: n.create_empty_directory(u"subdir2"))
421 def _created2(subdir2):
422 self.subdir2 = subdir2
423 d.addCallback(_created2)
425 d.addCallback(lambda res:
426 n.move_child_to(u"child", self.subdir2))
427 d.addCallback(lambda res: n.list())
428 d.addCallback(lambda children:
429 self.failUnlessEqual(sorted(children.keys()),
430 sorted([u"newfile", u"subdir2"])))
431 d.addCallback(lambda res: self.subdir2.list())
432 d.addCallback(lambda children:
433 self.failUnlessEqual(sorted(children.keys()),
443 netstring = hashutil.netstring
444 split_netstring = dirnode.split_netstring
446 class Netstring(unittest.TestCase):
447 def test_split(self):
448 a = netstring("hello") + netstring("world")
449 self.failUnlessEqual(split_netstring(a, 2), ("hello", "world"))
450 self.failUnlessEqual(split_netstring(a, 2, False), ("hello", "world"))
451 self.failUnlessEqual(split_netstring(a, 2, True),
452 ("hello", "world", ""))
453 self.failUnlessRaises(ValueError, split_netstring, a, 3)
454 self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2)
455 self.failUnlessRaises(ValueError, split_netstring, a+" extra", 2, False)
457 def test_extra(self):
458 a = netstring("hello")
459 self.failUnlessEqual(split_netstring(a, 1, True), ("hello", ""))
460 b = netstring("hello") + "extra stuff"
461 self.failUnlessEqual(split_netstring(b, 1, True),
462 ("hello", "extra stuff"))
464 def test_nested(self):
465 a = netstring("hello") + netstring("world") + "extra stuff"
466 b = netstring("a") + netstring("is") + netstring(a) + netstring(".")
467 top = split_netstring(b, 4)
468 self.failUnlessEqual(len(top), 4)
469 self.failUnlessEqual(top[0], "a")
470 self.failUnlessEqual(top[1], "is")
471 self.failUnlessEqual(top[2], a)
472 self.failUnlessEqual(top[3], ".")
473 self.failUnlessRaises(ValueError, split_netstring, a, 2)
474 self.failUnlessRaises(ValueError, split_netstring, a, 2, False)
475 bottom = split_netstring(a, 2, True)
476 self.failUnlessEqual(bottom, ("hello", "world", "extra stuff"))