2 import os, simplejson, urllib
3 from cStringIO import StringIO
4 from twisted.trial import unittest
5 from twisted.internet import defer
6 from twisted.internet import threads # CLI tests use deferToThread
7 from allmydata.immutable import upload
8 from allmydata.mutable.common import UnrecoverableFileError
9 from allmydata.mutable.publish import MutableData
10 from allmydata.util import idlib
11 from allmydata.util import base32
12 from allmydata.scripts import runner
13 from allmydata.interfaces import ICheckResults, ICheckAndRepairResults, \
14 IDeepCheckResults, IDeepCheckAndRepairResults
15 from allmydata.monitor import Monitor, OperationCancelledError
16 from allmydata.uri import LiteralFileURI
17 from twisted.web.client import getPage
19 from allmydata.test.common import ErrorMixin, _corrupt_mutable_share_data, \
21 from allmydata.test.common_util import StallMixin
22 from allmydata.test.no_network import GridTestMixin
24 timeout = 2400 # One of these took 1046.091s on Zandr's ARM box.
26 class MutableChecker(GridTestMixin, unittest.TestCase, ErrorMixin):
27 def _run_cli(self, argv):
28 stdout, stderr = StringIO(), StringIO()
29 # this can only do synchronous operations
30 assert argv[0] == "debug"
31 runner.runner(argv, run_by_human=False, stdout=stdout, stderr=stderr)
32 return stdout.getvalue()
35 self.basedir = "deepcheck/MutableChecker/good"
37 CONTENTS = "a little bit of data"
38 CONTENTS_uploadable = MutableData(CONTENTS)
39 d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
42 self.fileurl = "uri/" + urllib.quote(node.get_uri())
43 d.addCallback(_created)
44 # now make sure the webapi verifier sees no problems
45 d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true",
47 def _got_results(out):
48 self.failUnless("<span>Healthy : Healthy</span>" in out, out)
49 self.failUnless("Recoverable Versions: 10*seq1-" in out, out)
50 self.failIf("Not Healthy!" in out, out)
51 self.failIf("Unhealthy" in out, out)
52 self.failIf("Corrupt Shares" in out, out)
53 d.addCallback(_got_results)
54 d.addErrback(self.explain_web_error)
57 def test_corrupt(self):
58 self.basedir = "deepcheck/MutableChecker/corrupt"
60 CONTENTS = "a little bit of data"
61 CONTENTS_uploadable = MutableData(CONTENTS)
62 d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
63 def _stash_and_corrupt(node):
65 self.fileurl = "uri/" + urllib.quote(node.get_uri())
66 self.corrupt_shares_numbered(node.get_uri(), [0],
67 _corrupt_mutable_share_data)
68 d.addCallback(_stash_and_corrupt)
69 # now make sure the webapi verifier notices it
70 d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true",
72 def _got_results(out):
73 self.failUnless("Not Healthy!" in out, out)
74 self.failUnless("Unhealthy: best version has only 9 shares (encoding is 3-of-10)" in out, out)
75 self.failUnless("Corrupt Shares:" in out, out)
76 d.addCallback(_got_results)
78 # now make sure the webapi repairer can fix it
79 d.addCallback(lambda ign:
80 self.GET(self.fileurl+"?t=check&verify=true&repair=true",
82 def _got_repair_results(out):
83 self.failUnless("<div>Repair successful</div>" in out, out)
84 d.addCallback(_got_repair_results)
85 d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true",
87 def _got_postrepair_results(out):
88 self.failIf("Not Healthy!" in out, out)
89 self.failUnless("Recoverable Versions: 10*seq" in out, out)
90 d.addCallback(_got_postrepair_results)
91 d.addErrback(self.explain_web_error)
95 def test_delete_share(self):
96 self.basedir = "deepcheck/MutableChecker/delete_share"
98 CONTENTS = "a little bit of data"
99 CONTENTS_uploadable = MutableData(CONTENTS)
100 d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
101 def _stash_and_delete(node):
103 self.fileurl = "uri/" + urllib.quote(node.get_uri())
104 self.delete_shares_numbered(node.get_uri(), [0])
105 d.addCallback(_stash_and_delete)
106 # now make sure the webapi checker notices it
107 d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=false",
109 def _got_results(out):
110 self.failUnless("Not Healthy!" in out, out)
111 self.failUnless("Unhealthy: best version has only 9 shares (encoding is 3-of-10)" in out, out)
112 self.failIf("Corrupt Shares" in out, out)
113 d.addCallback(_got_results)
115 # now make sure the webapi repairer can fix it
116 d.addCallback(lambda ign:
117 self.GET(self.fileurl+"?t=check&verify=false&repair=true",
119 def _got_repair_results(out):
120 self.failUnless("Repair successful" in out)
121 d.addCallback(_got_repair_results)
122 d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=false",
124 def _got_postrepair_results(out):
125 self.failIf("Not Healthy!" in out, out)
126 self.failUnless("Recoverable Versions: 10*seq" in out)
127 d.addCallback(_got_postrepair_results)
128 d.addErrback(self.explain_web_error)
133 class DeepCheckBase(GridTestMixin, ErrorMixin, StallMixin, ShouldFailMixin):
135 def web_json(self, n, **kwargs):
136 kwargs["output"] = "json"
137 d = self.web(n, "POST", **kwargs)
138 d.addCallback(self.decode_json)
141 def decode_json(self, (s,url)):
143 data = simplejson.loads(s)
145 self.fail("%s: not JSON: '%s'" % (url, s))
148 def parse_streamed_json(self, s):
149 for unit in s.split("\n"):
151 # stream should end with a newline, so split returns ""
154 yield simplejson.loads(unit)
155 except ValueError, le:
156 le.args = tuple(le.args + (unit,))
159 def web(self, n, method="GET", **kwargs):
160 # returns (data, url)
161 url = (self.client_baseurls[0] + "uri/%s" % urllib.quote(n.get_uri())
162 + "?" + "&".join(["%s=%s" % (k,v) for (k,v) in kwargs.items()]))
163 d = getPage(url, method=method)
164 d.addCallback(lambda data: (data,url))
167 def wait_for_operation(self, ignored, ophandle):
168 url = self.client_baseurls[0] + "operations/" + ophandle
169 url += "?t=status&output=JSON"
173 data = simplejson.loads(res)
175 self.fail("%s: not JSON: '%s'" % (url, res))
176 if not data["finished"]:
177 d = self.stall(delay=1.0)
178 d.addCallback(self.wait_for_operation, ophandle)
184 def get_operation_results(self, ignored, ophandle, output=None):
185 url = self.client_baseurls[0] + "operations/" + ophandle
188 url += "&output=" + output
191 if output and output.lower() == "json":
193 return simplejson.loads(res)
195 self.fail("%s: not JSON: '%s'" % (url, res))
200 def slow_web(self, n, output=None, **kwargs):
202 handle = base32.b2a(os.urandom(4))
203 d = self.web(n, "POST", ophandle=handle, **kwargs)
204 d.addCallback(self.wait_for_operation, handle)
205 d.addCallback(self.get_operation_results, handle, output=output)
209 class DeepCheckWebGood(DeepCheckBase, unittest.TestCase):
210 # construct a small directory tree (with one dir, one immutable file, one
211 # mutable file, two LIT files, one DIR2:LIT empty dir, one DIR2:LIT tiny
212 # dir, and a loop), and then check/examine it in various ways.
214 def set_up_tree(self):
217 c0 = self.g.clients[0]
218 d = c0.create_dirnode()
219 def _created_root(n):
221 self.root_uri = n.get_uri()
222 d.addCallback(_created_root)
223 d.addCallback(lambda ign:
224 c0.create_mutable_file(MutableData("mutable file contents")))
225 d.addCallback(lambda n: self.root.set_node(u"mutable", n))
226 def _created_mutable(n):
228 self.mutable_uri = n.get_uri()
229 d.addCallback(_created_mutable)
231 large = upload.Data("Lots of data\n" * 1000, None)
232 d.addCallback(lambda ign: self.root.add_file(u"large", large))
233 def _created_large(n):
235 self.large_uri = n.get_uri()
236 d.addCallback(_created_large)
238 small = upload.Data("Small enough for a LIT", None)
239 d.addCallback(lambda ign: self.root.add_file(u"small", small))
240 def _created_small(n):
242 self.small_uri = n.get_uri()
243 d.addCallback(_created_small)
245 small2 = upload.Data("Small enough for a LIT too", None)
246 d.addCallback(lambda ign: self.root.add_file(u"small2", small2))
247 def _created_small2(n):
249 self.small2_uri = n.get_uri()
250 d.addCallback(_created_small2)
252 empty_litdir_uri = "URI:DIR2-LIT:"
253 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
255 d.addCallback(lambda ign: self.root._create_and_validate_node(None, empty_litdir_uri, name=u"test_deepcheck empty_lit_dir"))
256 def _created_empty_lit_dir(n):
257 self.empty_lit_dir = n
258 self.empty_lit_dir_uri = n.get_uri()
259 self.root.set_node(u"empty_lit_dir", n)
260 d.addCallback(_created_empty_lit_dir)
262 d.addCallback(lambda ign: self.root._create_and_validate_node(None, tiny_litdir_uri, name=u"test_deepcheck tiny_lit_dir"))
263 def _created_tiny_lit_dir(n):
264 self.tiny_lit_dir = n
265 self.tiny_lit_dir_uri = n.get_uri()
266 self.root.set_node(u"tiny_lit_dir", n)
267 d.addCallback(_created_tiny_lit_dir)
269 d.addCallback(lambda ign: self.root.set_node(u"loop", self.root))
272 def check_is_healthy(self, cr, n, where, incomplete=False):
273 self.failUnless(ICheckResults.providedBy(cr), where)
274 self.failUnless(cr.is_healthy(), where)
275 self.failUnlessEqual(cr.get_storage_index(), n.get_storage_index(),
277 self.failUnlessEqual(cr.get_storage_index_string(),
278 base32.b2a(n.get_storage_index()), where)
279 num_servers = len(self.g.all_servers)
280 needs_rebalancing = bool( num_servers < 10 )
282 self.failUnlessEqual(cr.needs_rebalancing(), needs_rebalancing,
283 str((where, cr, cr.get_data())))
285 self.failUnlessEqual(d["count-shares-good"], 10, where)
286 self.failUnlessEqual(d["count-shares-needed"], 3, where)
287 self.failUnlessEqual(d["count-shares-expected"], 10, where)
289 self.failUnlessEqual(d["count-good-share-hosts"], num_servers,
291 self.failUnlessEqual(d["count-corrupt-shares"], 0, where)
292 self.failUnlessEqual(d["list-corrupt-shares"], [], where)
294 self.failUnlessEqual(sorted(d["servers-responding"]),
295 sorted(self.g.get_all_serverids()),
297 self.failUnless("sharemap" in d, str((where, d)))
298 all_serverids = set()
299 for (shareid, serverids) in d["sharemap"].items():
300 all_serverids.update(serverids)
301 self.failUnlessEqual(sorted(all_serverids),
302 sorted(self.g.get_all_serverids()),
305 self.failUnlessEqual(d["count-wrong-shares"], 0, where)
306 self.failUnlessEqual(d["count-recoverable-versions"], 1, where)
307 self.failUnlessEqual(d["count-unrecoverable-versions"], 0, where)
310 def check_and_repair_is_healthy(self, cr, n, where, incomplete=False):
311 self.failUnless(ICheckAndRepairResults.providedBy(cr), (where, cr))
312 self.failUnless(cr.get_pre_repair_results().is_healthy(), where)
313 self.check_is_healthy(cr.get_pre_repair_results(), n, where, incomplete)
314 self.failUnless(cr.get_post_repair_results().is_healthy(), where)
315 self.check_is_healthy(cr.get_post_repair_results(), n, where, incomplete)
316 self.failIf(cr.get_repair_attempted(), where)
318 def deep_check_is_healthy(self, cr, num_healthy, where):
319 self.failUnless(IDeepCheckResults.providedBy(cr))
320 self.failUnlessEqual(cr.get_counters()["count-objects-healthy"],
323 def deep_check_and_repair_is_healthy(self, cr, num_healthy, where):
324 self.failUnless(IDeepCheckAndRepairResults.providedBy(cr), where)
325 c = cr.get_counters()
326 self.failUnlessEqual(c["count-objects-healthy-pre-repair"],
328 self.failUnlessEqual(c["count-objects-healthy-post-repair"],
330 self.failUnlessEqual(c["count-repairs-attempted"], 0, where)
333 self.basedir = "deepcheck/DeepCheckWebGood/good"
335 d = self.set_up_tree()
336 d.addCallback(self.do_stats)
337 d.addCallback(self.do_web_stream_manifest)
338 d.addCallback(self.do_web_stream_check)
339 d.addCallback(self.do_test_check_good)
340 d.addCallback(self.do_test_web_good)
341 d.addCallback(self.do_test_cli_good)
342 d.addErrback(self.explain_web_error)
343 d.addErrback(self.explain_error)
346 def do_stats(self, ignored):
347 d = defer.succeed(None)
348 d.addCallback(lambda ign: self.root.start_deep_stats().when_done())
349 d.addCallback(self.check_stats_good)
352 def check_stats_good(self, s):
353 self.failUnlessEqual(s["count-directories"], 3)
354 self.failUnlessEqual(s["count-files"], 5)
355 self.failUnlessEqual(s["count-immutable-files"], 1)
356 self.failUnlessEqual(s["count-literal-files"], 3)
357 self.failUnlessEqual(s["count-mutable-files"], 1)
358 # don't check directories: their size will vary
359 # s["largest-directory"]
360 # s["size-directories"]
361 self.failUnlessEqual(s["largest-directory-children"], 7)
362 self.failUnlessEqual(s["largest-immutable-file"], 13000)
363 # to re-use this function for both the local
364 # dirnode.start_deep_stats() and the webapi t=start-deep-stats, we
365 # coerce the result into a list of tuples. dirnode.start_deep_stats()
366 # returns a list of tuples, but JSON only knows about lists., so
367 # t=start-deep-stats returns a list of lists.
368 histogram = [tuple(stuff) for stuff in s["size-files-histogram"]]
369 self.failUnlessEqual(histogram, [(4, 10, 1), (11, 31, 2),
372 self.failUnlessEqual(s["size-immutable-files"], 13000)
373 self.failUnlessEqual(s["size-literal-files"], 56)
375 def do_web_stream_manifest(self, ignored):
376 d = self.web(self.root, method="POST", t="stream-manifest")
377 d.addCallback(lambda (output,url):
378 self._check_streamed_manifest(output))
381 def _check_streamed_manifest(self, output):
382 units = list(self.parse_streamed_json(output))
383 files = [u for u in units if u["type"] in ("file", "directory")]
384 assert units[-1]["type"] == "stats"
385 stats = units[-1]["stats"]
386 self.failUnlessEqual(len(files), 8)
387 # [root,mutable,large] are distributed, [small,small2,empty_litdir,tiny_litdir] are not
388 self.failUnlessEqual(len([f for f in files
389 if f["verifycap"] != ""]), 3)
390 self.failUnlessEqual(len([f for f in files
391 if f["verifycap"] == ""]), 5)
392 self.failUnlessEqual(len([f for f in files
393 if f["repaircap"] != ""]), 3)
394 self.failUnlessEqual(len([f for f in files
395 if f["repaircap"] == ""]), 5)
396 self.failUnlessEqual(len([f for f in files
397 if f["storage-index"] != ""]), 3)
398 self.failUnlessEqual(len([f for f in files
399 if f["storage-index"] == ""]), 5)
400 # make sure that a mutable file has filecap==repaircap!=verifycap
401 mutable = [f for f in files
402 if f["cap"] is not None
403 and f["cap"].startswith("URI:SSK:")][0]
404 self.failUnlessEqual(mutable["cap"], self.mutable_uri)
405 self.failIfEqual(mutable["cap"], mutable["verifycap"])
406 self.failUnlessEqual(mutable["cap"], mutable["repaircap"])
407 # for immutable file, verifycap==repaircap!=filecap
408 large = [f for f in files
409 if f["cap"] is not None
410 and f["cap"].startswith("URI:CHK:")][0]
411 self.failUnlessEqual(large["cap"], self.large_uri)
412 self.failIfEqual(large["cap"], large["verifycap"])
413 self.failUnlessEqual(large["verifycap"], large["repaircap"])
414 self.check_stats_good(stats)
416 def do_web_stream_check(self, ignored):
419 d = self.web(self.root, t="stream-deep-check")
421 units = list(self.parse_streamed_json(res))
422 #files = [u for u in units if u["type"] in ("file", "directory")]
423 assert units[-1]["type"] == "stats"
424 #stats = units[-1]["stats"]
426 d.addCallback(_check)
429 def do_test_check_good(self, ignored):
430 d = defer.succeed(None)
431 # check the individual items
432 d.addCallback(lambda ign: self.root.check(Monitor()))
433 d.addCallback(self.check_is_healthy, self.root, "root")
434 d.addCallback(lambda ign: self.mutable.check(Monitor()))
435 d.addCallback(self.check_is_healthy, self.mutable, "mutable")
436 d.addCallback(lambda ign: self.large.check(Monitor()))
437 d.addCallback(self.check_is_healthy, self.large, "large")
438 d.addCallback(lambda ign: self.small.check(Monitor()))
439 d.addCallback(self.failUnlessEqual, None, "small")
440 d.addCallback(lambda ign: self.small2.check(Monitor()))
441 d.addCallback(self.failUnlessEqual, None, "small2")
442 d.addCallback(lambda ign: self.empty_lit_dir.check(Monitor()))
443 d.addCallback(self.failUnlessEqual, None, "empty_lit_dir")
444 d.addCallback(lambda ign: self.tiny_lit_dir.check(Monitor()))
445 d.addCallback(self.failUnlessEqual, None, "tiny_lit_dir")
447 # and again with verify=True
448 d.addCallback(lambda ign: self.root.check(Monitor(), verify=True))
449 d.addCallback(self.check_is_healthy, self.root, "root")
450 d.addCallback(lambda ign: self.mutable.check(Monitor(), verify=True))
451 d.addCallback(self.check_is_healthy, self.mutable, "mutable")
452 d.addCallback(lambda ign: self.large.check(Monitor(), verify=True))
453 d.addCallback(self.check_is_healthy, self.large, "large", incomplete=True)
454 d.addCallback(lambda ign: self.small.check(Monitor(), verify=True))
455 d.addCallback(self.failUnlessEqual, None, "small")
456 d.addCallback(lambda ign: self.small2.check(Monitor(), verify=True))
457 d.addCallback(self.failUnlessEqual, None, "small2")
458 d.addCallback(lambda ign: self.empty_lit_dir.check(Monitor(), verify=True))
459 d.addCallback(self.failUnlessEqual, None, "empty_lit_dir")
460 d.addCallback(lambda ign: self.tiny_lit_dir.check(Monitor(), verify=True))
461 d.addCallback(self.failUnlessEqual, None, "tiny_lit_dir")
463 # and check_and_repair(), which should be a nop
464 d.addCallback(lambda ign: self.root.check_and_repair(Monitor()))
465 d.addCallback(self.check_and_repair_is_healthy, self.root, "root")
466 d.addCallback(lambda ign: self.mutable.check_and_repair(Monitor()))
467 d.addCallback(self.check_and_repair_is_healthy, self.mutable, "mutable")
468 d.addCallback(lambda ign: self.large.check_and_repair(Monitor()))
469 d.addCallback(self.check_and_repair_is_healthy, self.large, "large")
470 d.addCallback(lambda ign: self.small.check_and_repair(Monitor()))
471 d.addCallback(self.failUnlessEqual, None, "small")
472 d.addCallback(lambda ign: self.small2.check_and_repair(Monitor()))
473 d.addCallback(self.failUnlessEqual, None, "small2")
474 d.addCallback(lambda ign: self.empty_lit_dir.check_and_repair(Monitor()))
475 d.addCallback(self.failUnlessEqual, None, "empty_lit_dir")
476 d.addCallback(lambda ign: self.tiny_lit_dir.check_and_repair(Monitor()))
478 # check_and_repair(verify=True)
479 d.addCallback(lambda ign: self.root.check_and_repair(Monitor(), verify=True))
480 d.addCallback(self.check_and_repair_is_healthy, self.root, "root")
481 d.addCallback(lambda ign: self.mutable.check_and_repair(Monitor(), verify=True))
482 d.addCallback(self.check_and_repair_is_healthy, self.mutable, "mutable")
483 d.addCallback(lambda ign: self.large.check_and_repair(Monitor(), verify=True))
484 d.addCallback(self.check_and_repair_is_healthy, self.large, "large", incomplete=True)
485 d.addCallback(lambda ign: self.small.check_and_repair(Monitor(), verify=True))
486 d.addCallback(self.failUnlessEqual, None, "small")
487 d.addCallback(lambda ign: self.small2.check_and_repair(Monitor(), verify=True))
488 d.addCallback(self.failUnlessEqual, None, "small2")
489 d.addCallback(self.failUnlessEqual, None, "small2")
490 d.addCallback(lambda ign: self.empty_lit_dir.check_and_repair(Monitor(), verify=True))
491 d.addCallback(self.failUnlessEqual, None, "empty_lit_dir")
492 d.addCallback(lambda ign: self.tiny_lit_dir.check_and_repair(Monitor(), verify=True))
495 # now deep-check the root, with various verify= and repair= options
496 d.addCallback(lambda ign:
497 self.root.start_deep_check().when_done())
498 d.addCallback(self.deep_check_is_healthy, 3, "root")
499 d.addCallback(lambda ign:
500 self.root.start_deep_check(verify=True).when_done())
501 d.addCallback(self.deep_check_is_healthy, 3, "root")
502 d.addCallback(lambda ign:
503 self.root.start_deep_check_and_repair().when_done())
504 d.addCallback(self.deep_check_and_repair_is_healthy, 3, "root")
505 d.addCallback(lambda ign:
506 self.root.start_deep_check_and_repair(verify=True).when_done())
507 d.addCallback(self.deep_check_and_repair_is_healthy, 3, "root")
509 # and finally, start a deep-check, but then cancel it.
510 d.addCallback(lambda ign: self.root.start_deep_check())
511 def _checking(monitor):
513 d = monitor.when_done()
514 # this should fire as soon as the next dirnode.list finishes.
515 # TODO: add a counter to measure how many list() calls are made,
516 # assert that no more than one gets to run before the cancel()
518 def _finished_normally(res):
519 self.fail("this was supposed to fail, not finish normally")
521 f.trap(OperationCancelledError)
522 d.addCallbacks(_finished_normally, _cancelled)
524 d.addCallback(_checking)
528 def json_check_is_healthy(self, data, n, where, incomplete=False):
530 self.failUnlessEqual(data["storage-index"],
531 base32.b2a(n.get_storage_index()), where)
532 self.failUnless("summary" in data, (where, data))
533 self.failUnlessEqual(data["summary"].lower(), "healthy",
534 "%s: '%s'" % (where, data["summary"]))
536 self.failUnlessEqual(r["healthy"], True, where)
537 num_servers = len(self.g.all_servers)
538 needs_rebalancing = bool( num_servers < 10 )
540 self.failUnlessEqual(r["needs-rebalancing"], needs_rebalancing,
542 self.failUnlessEqual(r["count-shares-good"], 10, where)
543 self.failUnlessEqual(r["count-shares-needed"], 3, where)
544 self.failUnlessEqual(r["count-shares-expected"], 10, where)
546 self.failUnlessEqual(r["count-good-share-hosts"], num_servers,
548 self.failUnlessEqual(r["count-corrupt-shares"], 0, where)
549 self.failUnlessEqual(r["list-corrupt-shares"], [], where)
551 self.failUnlessEqual(sorted(r["servers-responding"]),
552 sorted([idlib.nodeid_b2a(sid)
553 for sid in self.g.get_all_serverids()]),
555 self.failUnless("sharemap" in r, where)
556 all_serverids = set()
557 for (shareid, serverids_s) in r["sharemap"].items():
558 all_serverids.update(serverids_s)
559 self.failUnlessEqual(sorted(all_serverids),
560 sorted([idlib.nodeid_b2a(sid)
561 for sid in self.g.get_all_serverids()]),
563 self.failUnlessEqual(r["count-wrong-shares"], 0, where)
564 self.failUnlessEqual(r["count-recoverable-versions"], 1, where)
565 self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where)
567 def json_check_and_repair_is_healthy(self, data, n, where, incomplete=False):
568 self.failUnlessEqual(data["storage-index"],
569 base32.b2a(n.get_storage_index()), where)
570 self.failUnlessEqual(data["repair-attempted"], False, where)
571 self.json_check_is_healthy(data["pre-repair-results"],
572 n, where, incomplete)
573 self.json_check_is_healthy(data["post-repair-results"],
574 n, where, incomplete)
576 def json_full_deepcheck_is_healthy(self, data, n, where):
577 self.failUnlessEqual(data["root-storage-index"],
578 base32.b2a(n.get_storage_index()), where)
579 self.failUnlessEqual(data["count-objects-checked"], 3, where)
580 self.failUnlessEqual(data["count-objects-healthy"], 3, where)
581 self.failUnlessEqual(data["count-objects-unhealthy"], 0, where)
582 self.failUnlessEqual(data["count-corrupt-shares"], 0, where)
583 self.failUnlessEqual(data["list-corrupt-shares"], [], where)
584 self.failUnlessEqual(data["list-unhealthy-files"], [], where)
585 self.json_check_stats_good(data["stats"], where)
587 def json_full_deepcheck_and_repair_is_healthy(self, data, n, where):
588 self.failUnlessEqual(data["root-storage-index"],
589 base32.b2a(n.get_storage_index()), where)
590 self.failUnlessEqual(data["count-objects-checked"], 3, where)
592 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 3, where)
593 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0, where)
594 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0, where)
596 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 3, where)
597 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0, where)
598 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0, where)
600 self.failUnlessEqual(data["list-corrupt-shares"], [], where)
601 self.failUnlessEqual(data["list-remaining-corrupt-shares"], [], where)
602 self.failUnlessEqual(data["list-unhealthy-files"], [], where)
604 self.failUnlessEqual(data["count-repairs-attempted"], 0, where)
605 self.failUnlessEqual(data["count-repairs-successful"], 0, where)
606 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0, where)
609 def json_check_lit(self, data, n, where):
610 self.failUnlessEqual(data["storage-index"], "", where)
611 self.failUnlessEqual(data["results"]["healthy"], True, where)
613 def json_check_stats_good(self, data, where):
614 self.check_stats_good(data)
616 def do_test_web_good(self, ignored):
617 d = defer.succeed(None)
620 d.addCallback(lambda ign:
621 self.slow_web(self.root,
622 t="start-deep-stats", output="json"))
623 d.addCallback(self.json_check_stats_good, "deep-stats")
626 d.addCallback(lambda ign: self.web_json(self.root, t="check"))
627 d.addCallback(self.json_check_is_healthy, self.root, "root")
628 d.addCallback(lambda ign: self.web_json(self.mutable, t="check"))
629 d.addCallback(self.json_check_is_healthy, self.mutable, "mutable")
630 d.addCallback(lambda ign: self.web_json(self.large, t="check"))
631 d.addCallback(self.json_check_is_healthy, self.large, "large")
632 d.addCallback(lambda ign: self.web_json(self.small, t="check"))
633 d.addCallback(self.json_check_lit, self.small, "small")
634 d.addCallback(lambda ign: self.web_json(self.small2, t="check"))
635 d.addCallback(self.json_check_lit, self.small2, "small2")
636 d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check"))
637 d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir")
638 d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check"))
639 d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir")
642 d.addCallback(lambda ign:
643 self.web_json(self.root, t="check", verify="true"))
644 d.addCallback(self.json_check_is_healthy, self.root, "root+v")
645 d.addCallback(lambda ign:
646 self.web_json(self.mutable, t="check", verify="true"))
647 d.addCallback(self.json_check_is_healthy, self.mutable, "mutable+v")
648 d.addCallback(lambda ign:
649 self.web_json(self.large, t="check", verify="true"))
650 d.addCallback(self.json_check_is_healthy, self.large, "large+v",
652 d.addCallback(lambda ign:
653 self.web_json(self.small, t="check", verify="true"))
654 d.addCallback(self.json_check_lit, self.small, "small+v")
655 d.addCallback(lambda ign:
656 self.web_json(self.small2, t="check", verify="true"))
657 d.addCallback(self.json_check_lit, self.small2, "small2+v")
658 d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", verify="true"))
659 d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+v")
660 d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", verify="true"))
661 d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+v")
663 # check and repair, no verify
664 d.addCallback(lambda ign:
665 self.web_json(self.root, t="check", repair="true"))
666 d.addCallback(self.json_check_and_repair_is_healthy, self.root, "root+r")
667 d.addCallback(lambda ign:
668 self.web_json(self.mutable, t="check", repair="true"))
669 d.addCallback(self.json_check_and_repair_is_healthy, self.mutable, "mutable+r")
670 d.addCallback(lambda ign:
671 self.web_json(self.large, t="check", repair="true"))
672 d.addCallback(self.json_check_and_repair_is_healthy, self.large, "large+r")
673 d.addCallback(lambda ign:
674 self.web_json(self.small, t="check", repair="true"))
675 d.addCallback(self.json_check_lit, self.small, "small+r")
676 d.addCallback(lambda ign:
677 self.web_json(self.small2, t="check", repair="true"))
678 d.addCallback(self.json_check_lit, self.small2, "small2+r")
679 d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", repair="true"))
680 d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+r")
681 d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", repair="true"))
682 d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+r")
684 # check+verify+repair
685 d.addCallback(lambda ign:
686 self.web_json(self.root, t="check", repair="true", verify="true"))
687 d.addCallback(self.json_check_and_repair_is_healthy, self.root, "root+vr")
688 d.addCallback(lambda ign:
689 self.web_json(self.mutable, t="check", repair="true", verify="true"))
690 d.addCallback(self.json_check_and_repair_is_healthy, self.mutable, "mutable+vr")
691 d.addCallback(lambda ign:
692 self.web_json(self.large, t="check", repair="true", verify="true"))
693 d.addCallback(self.json_check_and_repair_is_healthy, self.large, "large+vr", incomplete=True)
694 d.addCallback(lambda ign:
695 self.web_json(self.small, t="check", repair="true", verify="true"))
696 d.addCallback(self.json_check_lit, self.small, "small+vr")
697 d.addCallback(lambda ign:
698 self.web_json(self.small2, t="check", repair="true", verify="true"))
699 d.addCallback(self.json_check_lit, self.small2, "small2+vr")
700 d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", repair="true", verify=True))
701 d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+vr")
702 d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", repair="true", verify=True))
703 d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+vr")
705 # now run a deep-check, with various verify= and repair= flags
706 d.addCallback(lambda ign:
707 self.slow_web(self.root, t="start-deep-check", output="json"))
708 d.addCallback(self.json_full_deepcheck_is_healthy, self.root, "root+d")
709 d.addCallback(lambda ign:
710 self.slow_web(self.root, t="start-deep-check", verify="true",
712 d.addCallback(self.json_full_deepcheck_is_healthy, self.root, "root+dv")
713 d.addCallback(lambda ign:
714 self.slow_web(self.root, t="start-deep-check", repair="true",
716 d.addCallback(self.json_full_deepcheck_and_repair_is_healthy, self.root, "root+dr")
717 d.addCallback(lambda ign:
718 self.slow_web(self.root, t="start-deep-check", verify="true", repair="true", output="json"))
719 d.addCallback(self.json_full_deepcheck_and_repair_is_healthy, self.root, "root+dvr")
722 d.addCallback(lambda ign: self.web(self.root, t="info"))
723 # TODO: examine the output
724 d.addCallback(lambda ign: self.web(self.mutable, t="info"))
725 d.addCallback(lambda ign: self.web(self.large, t="info"))
726 d.addCallback(lambda ign: self.web(self.small, t="info"))
727 d.addCallback(lambda ign: self.web(self.small2, t="info"))
728 d.addCallback(lambda ign: self.web(self.empty_lit_dir, t="info"))
729 d.addCallback(lambda ign: self.web(self.tiny_lit_dir, t="info"))
733 def _run_cli(self, argv, stdin=""):
735 stdout, stderr = StringIO(), StringIO()
736 d = threads.deferToThread(runner.runner, argv, run_by_human=False,
737 stdin=StringIO(stdin),
738 stdout=stdout, stderr=stderr)
740 return stdout.getvalue(), stderr.getvalue()
744 def do_test_cli_good(self, ignored):
745 d = defer.succeed(None)
746 d.addCallback(lambda ign: self.do_cli_manifest_stream1())
747 d.addCallback(lambda ign: self.do_cli_manifest_stream2())
748 d.addCallback(lambda ign: self.do_cli_manifest_stream3())
749 d.addCallback(lambda ign: self.do_cli_manifest_stream4())
750 d.addCallback(lambda ign: self.do_cli_manifest_stream5())
751 d.addCallback(lambda ign: self.do_cli_stats1())
752 d.addCallback(lambda ign: self.do_cli_stats2())
755 def _check_manifest_storage_index(self, out):
756 lines = [l for l in out.split("\n") if l]
757 self.failUnlessEqual(len(lines), 3)
758 self.failUnless(base32.b2a(self.root.get_storage_index()) in lines)
759 self.failUnless(base32.b2a(self.mutable.get_storage_index()) in lines)
760 self.failUnless(base32.b2a(self.large.get_storage_index()) in lines)
762 def do_cli_manifest_stream1(self):
763 basedir = self.get_clientdir(0)
764 d = self._run_cli(["manifest",
765 "--node-directory", basedir,
767 def _check((out,err)):
768 self.failUnlessEqual(err, "")
769 lines = [l for l in out.split("\n") if l]
770 self.failUnlessEqual(len(lines), 8)
774 cap, path = l.split(None, 1)
779 self.failUnless(self.root.get_uri() in caps)
780 self.failUnlessEqual(caps[self.root.get_uri()], "")
781 self.failUnlessEqual(caps[self.mutable.get_uri()], "mutable")
782 self.failUnlessEqual(caps[self.large.get_uri()], "large")
783 self.failUnlessEqual(caps[self.small.get_uri()], "small")
784 self.failUnlessEqual(caps[self.small2.get_uri()], "small2")
785 self.failUnlessEqual(caps[self.empty_lit_dir.get_uri()], "empty_lit_dir")
786 self.failUnlessEqual(caps[self.tiny_lit_dir.get_uri()], "tiny_lit_dir")
787 d.addCallback(_check)
790 def do_cli_manifest_stream2(self):
791 basedir = self.get_clientdir(0)
792 d = self._run_cli(["manifest",
793 "--node-directory", basedir,
796 def _check((out,err)):
797 self.failUnlessEqual(err, "")
798 # this should be the same as the POST t=stream-manifest output
799 self._check_streamed_manifest(out)
800 d.addCallback(_check)
803 def do_cli_manifest_stream3(self):
804 basedir = self.get_clientdir(0)
805 d = self._run_cli(["manifest",
806 "--node-directory", basedir,
809 def _check((out,err)):
810 self.failUnlessEqual(err, "")
811 self._check_manifest_storage_index(out)
812 d.addCallback(_check)
815 def do_cli_manifest_stream4(self):
816 basedir = self.get_clientdir(0)
817 d = self._run_cli(["manifest",
818 "--node-directory", basedir,
821 def _check((out,err)):
822 self.failUnlessEqual(err, "")
823 lines = [l for l in out.split("\n") if l]
824 self.failUnlessEqual(len(lines), 3)
825 self.failUnless(self.root.get_verify_cap().to_string() in lines)
826 self.failUnless(self.mutable.get_verify_cap().to_string() in lines)
827 self.failUnless(self.large.get_verify_cap().to_string() in lines)
828 d.addCallback(_check)
831 def do_cli_manifest_stream5(self):
832 basedir = self.get_clientdir(0)
833 d = self._run_cli(["manifest",
834 "--node-directory", basedir,
837 def _check((out,err)):
838 self.failUnlessEqual(err, "")
839 lines = [l for l in out.split("\n") if l]
840 self.failUnlessEqual(len(lines), 3)
841 self.failUnless(self.root.get_repair_cap().to_string() in lines)
842 self.failUnless(self.mutable.get_repair_cap().to_string() in lines)
843 self.failUnless(self.large.get_repair_cap().to_string() in lines)
844 d.addCallback(_check)
847 def do_cli_stats1(self):
848 basedir = self.get_clientdir(0)
849 d = self._run_cli(["stats",
850 "--node-directory", basedir,
852 def _check3((out,err)):
853 lines = [l.strip() for l in out.split("\n") if l]
854 self.failUnless("count-immutable-files: 1" in lines)
855 self.failUnless("count-mutable-files: 1" in lines)
856 self.failUnless("count-literal-files: 3" in lines)
857 self.failUnless("count-files: 5" in lines)
858 self.failUnless("count-directories: 3" in lines)
859 self.failUnless("size-immutable-files: 13000 (13.00 kB, 12.70 kiB)" in lines, lines)
860 self.failUnless("size-literal-files: 56" in lines, lines)
861 self.failUnless(" 4-10 : 1 (10 B, 10 B)".strip() in lines, lines)
862 self.failUnless(" 11-31 : 2 (31 B, 31 B)".strip() in lines, lines)
863 self.failUnless("10001-31622 : 1 (31.62 kB, 30.88 kiB)".strip() in lines, lines)
864 d.addCallback(_check3)
867 def do_cli_stats2(self):
868 basedir = self.get_clientdir(0)
869 d = self._run_cli(["stats",
870 "--node-directory", basedir,
873 def _check4((out,err)):
874 data = simplejson.loads(out)
875 self.failUnlessEqual(data["count-immutable-files"], 1)
876 self.failUnlessEqual(data["count-immutable-files"], 1)
877 self.failUnlessEqual(data["count-mutable-files"], 1)
878 self.failUnlessEqual(data["count-literal-files"], 3)
879 self.failUnlessEqual(data["count-files"], 5)
880 self.failUnlessEqual(data["count-directories"], 3)
881 self.failUnlessEqual(data["size-immutable-files"], 13000)
882 self.failUnlessEqual(data["size-literal-files"], 56)
883 self.failUnless([4,10,1] in data["size-files-histogram"])
884 self.failUnless([11,31,2] in data["size-files-histogram"])
885 self.failUnless([10001,31622,1] in data["size-files-histogram"])
886 d.addCallback(_check4)
890 class DeepCheckWebBad(DeepCheckBase, unittest.TestCase):
892 self.basedir = "deepcheck/DeepCheckWebBad/bad"
894 d = self.set_up_damaged_tree()
895 d.addCallback(self.do_check)
896 d.addCallback(self.do_deepcheck)
897 d.addCallback(self.do_deepcheck_broken)
898 d.addCallback(self.do_test_web_bad)
899 d.addErrback(self.explain_web_error)
900 d.addErrback(self.explain_error)
905 def set_up_damaged_tree(self):
910 # mutable-missing-shares
911 # mutable-corrupt-shares
912 # mutable-unrecoverable
914 # large-missing-shares
915 # large-corrupt-shares
916 # large-unrecoverable
921 # subdir-unrecoverable
926 c0 = self.g.clients[0]
927 d = c0.create_dirnode()
928 def _created_root(n):
930 self.root_uri = n.get_uri()
931 d.addCallback(_created_root)
932 d.addCallback(self.create_mangled, "mutable-good")
933 d.addCallback(self.create_mangled, "mutable-missing-shares")
934 d.addCallback(self.create_mangled, "mutable-corrupt-shares")
935 d.addCallback(self.create_mangled, "mutable-unrecoverable")
936 d.addCallback(self.create_mangled, "large-good")
937 d.addCallback(self.create_mangled, "large-missing-shares")
938 d.addCallback(self.create_mangled, "large-corrupt-shares")
939 d.addCallback(self.create_mangled, "large-unrecoverable")
940 d.addCallback(lambda ignored: c0.create_dirnode())
941 d.addCallback(self._stash_node, "broken")
942 large1 = upload.Data("Lots of data\n" * 1000 + "large1" + "\n", None)
943 d.addCallback(lambda ignored:
944 self.nodes["broken"].add_file(u"large1", large1))
945 d.addCallback(lambda ignored:
946 self.nodes["broken"].create_subdirectory(u"subdir-good"))
947 large2 = upload.Data("Lots of data\n" * 1000 + "large2" + "\n", None)
948 d.addCallback(lambda subdir: subdir.add_file(u"large2-good", large2))
949 d.addCallback(lambda ignored:
950 self.nodes["broken"].create_subdirectory(u"subdir-unrecoverable"))
951 d.addCallback(self._stash_node, "subdir-unrecoverable")
952 large3 = upload.Data("Lots of data\n" * 1000 + "large3" + "\n", None)
953 d.addCallback(lambda subdir: subdir.add_file(u"large3-good", large3))
954 d.addCallback(lambda ignored:
955 self._delete_most_shares(self.nodes["broken"]))
958 def _stash_node(self, node, name):
959 self.nodes[name] = node
962 def create_mangled(self, ignored, name):
963 nodetype, mangletype = name.split("-", 1)
964 if nodetype == "mutable":
965 mutable_uploadable = MutableData("mutable file contents")
966 d = self.g.clients[0].create_mutable_file(mutable_uploadable)
967 d.addCallback(lambda n: self.root.set_node(unicode(name), n))
968 elif nodetype == "large":
969 large = upload.Data("Lots of data\n" * 1000 + name + "\n", None)
970 d = self.root.add_file(unicode(name), large)
971 elif nodetype == "small":
972 small = upload.Data("Small enough for a LIT", None)
973 d = self.root.add_file(unicode(name), small)
975 d.addCallback(self._stash_node, name)
977 if mangletype == "good":
979 elif mangletype == "missing-shares":
980 d.addCallback(self._delete_some_shares)
981 elif mangletype == "corrupt-shares":
982 d.addCallback(self._corrupt_some_shares)
984 assert mangletype == "unrecoverable"
985 d.addCallback(self._delete_most_shares)
989 def _run_cli(self, argv):
990 stdout, stderr = StringIO(), StringIO()
991 # this can only do synchronous operations
992 assert argv[0] == "debug"
993 runner.runner(argv, run_by_human=False, stdout=stdout, stderr=stderr)
994 return stdout.getvalue()
996 def _delete_some_shares(self, node):
997 self.delete_shares_numbered(node.get_uri(), [0,1])
999 def _corrupt_some_shares(self, node):
1000 for (shnum, serverid, sharefile) in self.find_uri_shares(node.get_uri()):
1002 self._run_cli(["debug", "corrupt-share", sharefile])
1004 def _delete_most_shares(self, node):
1005 self.delete_shares_numbered(node.get_uri(), range(1,10))
1008 def check_is_healthy(self, cr, where):
1010 self.failUnless(ICheckResults.providedBy(cr), (cr, type(cr), where))
1011 self.failUnless(cr.is_healthy(), (cr.get_report(), cr.is_healthy(), cr.get_summary(), where))
1012 self.failUnless(cr.is_recoverable(), where)
1014 self.failUnlessEqual(d["count-recoverable-versions"], 1, where)
1015 self.failUnlessEqual(d["count-unrecoverable-versions"], 0, where)
1017 except Exception, le:
1018 le.args = tuple(le.args + (where,))
1021 def check_is_missing_shares(self, cr, where):
1022 self.failUnless(ICheckResults.providedBy(cr), where)
1023 self.failIf(cr.is_healthy(), where)
1024 self.failUnless(cr.is_recoverable(), where)
1026 self.failUnlessEqual(d["count-recoverable-versions"], 1, where)
1027 self.failUnlessEqual(d["count-unrecoverable-versions"], 0, where)
1030 def check_has_corrupt_shares(self, cr, where):
1031 # by "corrupt-shares" we mean the file is still recoverable
1032 self.failUnless(ICheckResults.providedBy(cr), where)
1034 self.failIf(cr.is_healthy(), (where, cr))
1035 self.failUnless(cr.is_recoverable(), where)
1037 self.failUnless(d["count-shares-good"] < 10, where)
1038 self.failUnless(d["count-corrupt-shares"], where)
1039 self.failUnless(d["list-corrupt-shares"], where)
1042 def check_is_unrecoverable(self, cr, where):
1043 self.failUnless(ICheckResults.providedBy(cr), where)
1045 self.failIf(cr.is_healthy(), where)
1046 self.failIf(cr.is_recoverable(), where)
1047 self.failUnless(d["count-shares-good"] < d["count-shares-needed"], (d["count-shares-good"], d["count-shares-needed"], where))
1048 self.failUnlessEqual(d["count-recoverable-versions"], 0, where)
1049 self.failUnlessEqual(d["count-unrecoverable-versions"], 1, where)
1052 def do_check(self, ignored):
1053 d = defer.succeed(None)
1055 # check the individual items, without verification. This will not
1056 # detect corrupt shares.
1057 def _check(which, checker):
1058 d = self.nodes[which].check(Monitor())
1059 d.addCallback(checker, which + "--check")
1062 d.addCallback(lambda ign: _check("mutable-good", self.check_is_healthy))
1063 d.addCallback(lambda ign: _check("mutable-missing-shares",
1064 self.check_is_missing_shares))
1065 d.addCallback(lambda ign: _check("mutable-corrupt-shares",
1066 self.check_is_healthy))
1067 d.addCallback(lambda ign: _check("mutable-unrecoverable",
1068 self.check_is_unrecoverable))
1069 d.addCallback(lambda ign: _check("large-good", self.check_is_healthy))
1070 d.addCallback(lambda ign: _check("large-missing-shares",
1071 self.check_is_missing_shares))
1072 d.addCallback(lambda ign: _check("large-corrupt-shares",
1073 self.check_is_healthy))
1074 d.addCallback(lambda ign: _check("large-unrecoverable",
1075 self.check_is_unrecoverable))
1077 # and again with verify=True, which *does* detect corrupt shares.
1078 def _checkv(which, checker):
1079 d = self.nodes[which].check(Monitor(), verify=True)
1080 d.addCallback(checker, which + "--check-and-verify")
1083 d.addCallback(lambda ign: _checkv("mutable-good", self.check_is_healthy))
1084 d.addCallback(lambda ign: _checkv("mutable-missing-shares",
1085 self.check_is_missing_shares))
1086 d.addCallback(lambda ign: _checkv("mutable-corrupt-shares",
1087 self.check_has_corrupt_shares))
1088 d.addCallback(lambda ign: _checkv("mutable-unrecoverable",
1089 self.check_is_unrecoverable))
1090 d.addCallback(lambda ign: _checkv("large-good", self.check_is_healthy))
1091 d.addCallback(lambda ign: _checkv("large-missing-shares", self.check_is_missing_shares))
1092 d.addCallback(lambda ign: _checkv("large-corrupt-shares", self.check_has_corrupt_shares))
1093 d.addCallback(lambda ign: _checkv("large-unrecoverable",
1094 self.check_is_unrecoverable))
1098 def do_deepcheck(self, ignored):
1099 d = defer.succeed(None)
1101 # now deep-check the root, with various verify= and repair= options
1102 d.addCallback(lambda ign:
1103 self.root.start_deep_check().when_done())
1105 self.failUnless(IDeepCheckResults.providedBy(cr))
1106 c = cr.get_counters()
1107 self.failUnlessEqual(c["count-objects-checked"], 9)
1108 self.failUnlessEqual(c["count-objects-healthy"], 5)
1109 self.failUnlessEqual(c["count-objects-unhealthy"], 4)
1110 self.failUnlessEqual(c["count-objects-unrecoverable"], 2)
1111 d.addCallback(_check1)
1113 d.addCallback(lambda ign:
1114 self.root.start_deep_check(verify=True).when_done())
1116 self.failUnless(IDeepCheckResults.providedBy(cr))
1117 c = cr.get_counters()
1118 self.failUnlessEqual(c["count-objects-checked"], 9)
1119 self.failUnlessEqual(c["count-objects-healthy"], 3)
1120 self.failUnlessEqual(c["count-objects-unhealthy"], 6)
1121 self.failUnlessEqual(c["count-objects-healthy"], 3) # root, mutable good, large good
1122 self.failUnlessEqual(c["count-objects-unrecoverable"], 2) # mutable unrecoverable, large unrecoverable
1123 d.addCallback(_check2)
1127 def do_deepcheck_broken(self, ignored):
1128 # deep-check on the broken directory should fail, because of the
1129 # untraversable subdir
1130 def _do_deep_check():
1131 return self.nodes["broken"].start_deep_check().when_done()
1132 d = self.shouldFail(UnrecoverableFileError, "do_deep_check",
1133 "no recoverable versions",
1137 def json_is_healthy(self, data, where):
1139 self.failUnless(r["healthy"], where)
1140 self.failUnless(r["recoverable"], where)
1141 self.failUnlessEqual(r["count-recoverable-versions"], 1, where)
1142 self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where)
1144 def json_is_missing_shares(self, data, where):
1146 self.failIf(r["healthy"], where)
1147 self.failUnless(r["recoverable"], where)
1148 self.failUnlessEqual(r["count-recoverable-versions"], 1, where)
1149 self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where)
1151 def json_has_corrupt_shares(self, data, where):
1152 # by "corrupt-shares" we mean the file is still recoverable
1154 self.failIf(r["healthy"], where)
1155 self.failUnless(r["recoverable"], where)
1156 self.failUnless(r["count-shares-good"] < 10, where)
1157 self.failUnless(r["count-corrupt-shares"], where)
1158 self.failUnless(r["list-corrupt-shares"], where)
1160 def json_is_unrecoverable(self, data, where):
1162 self.failIf(r["healthy"], where)
1163 self.failIf(r["recoverable"], where)
1164 self.failUnless(r["count-shares-good"] < r["count-shares-needed"],
1166 self.failUnlessEqual(r["count-recoverable-versions"], 0, where)
1167 self.failUnlessEqual(r["count-unrecoverable-versions"], 1, where)
1169 def do_test_web_bad(self, ignored):
1170 d = defer.succeed(None)
1173 def _check(which, checker):
1174 d = self.web_json(self.nodes[which], t="check")
1175 d.addCallback(checker, which + "--webcheck")
1178 d.addCallback(lambda ign: _check("mutable-good",
1179 self.json_is_healthy))
1180 d.addCallback(lambda ign: _check("mutable-missing-shares",
1181 self.json_is_missing_shares))
1182 d.addCallback(lambda ign: _check("mutable-corrupt-shares",
1183 self.json_is_healthy))
1184 d.addCallback(lambda ign: _check("mutable-unrecoverable",
1185 self.json_is_unrecoverable))
1186 d.addCallback(lambda ign: _check("large-good",
1187 self.json_is_healthy))
1188 d.addCallback(lambda ign: _check("large-missing-shares",
1189 self.json_is_missing_shares))
1190 d.addCallback(lambda ign: _check("large-corrupt-shares",
1191 self.json_is_healthy))
1192 d.addCallback(lambda ign: _check("large-unrecoverable",
1193 self.json_is_unrecoverable))
1196 def _checkv(which, checker):
1197 d = self.web_json(self.nodes[which], t="check", verify="true")
1198 d.addCallback(checker, which + "--webcheck-and-verify")
1201 d.addCallback(lambda ign: _checkv("mutable-good",
1202 self.json_is_healthy))
1203 d.addCallback(lambda ign: _checkv("mutable-missing-shares",
1204 self.json_is_missing_shares))
1205 d.addCallback(lambda ign: _checkv("mutable-corrupt-shares",
1206 self.json_has_corrupt_shares))
1207 d.addCallback(lambda ign: _checkv("mutable-unrecoverable",
1208 self.json_is_unrecoverable))
1209 d.addCallback(lambda ign: _checkv("large-good",
1210 self.json_is_healthy))
1211 d.addCallback(lambda ign: _checkv("large-missing-shares", self.json_is_missing_shares))
1212 d.addCallback(lambda ign: _checkv("large-corrupt-shares", self.json_has_corrupt_shares))
1213 d.addCallback(lambda ign: _checkv("large-unrecoverable",
1214 self.json_is_unrecoverable))
1218 class Large(DeepCheckBase, unittest.TestCase):
1219 def test_lots_of_lits(self):
1220 self.basedir = "deepcheck/Large/lots_of_lits"
1222 # create the following directory structure:
1230 # then do a deepcheck and make sure it doesn't cause a
1231 # Deferred-tail-recursion stack overflow
1234 c0 = self.g.clients[0]
1235 d = c0.create_dirnode()
1237 def _created_root(n):
1240 d.addCallback(_created_root)
1241 d.addCallback(lambda root: root.create_subdirectory(u"subdir"))
1242 def _add_children(subdir_node):
1243 self.subdir_node = subdir_node
1245 for i in range(1, COUNT):
1246 litcap = LiteralFileURI("%03d-data" % i).to_string()
1247 kids[u"%03d-small" % i] = (litcap, litcap)
1248 return subdir_node.set_children(kids)
1249 d.addCallback(_add_children)
1250 up = upload.Data("large enough for CHK" * 100, "")
1251 d.addCallback(lambda ign: self.subdir_node.add_file(u"0000-large", up))
1253 def _start_deepcheck(ignored):
1254 return self.web(self.root, method="POST", t="stream-deep-check")
1255 d.addCallback(_start_deepcheck)
1256 def _check( (output, url) ):
1257 units = list(self.parse_streamed_json(output))
1258 self.failUnlessEqual(len(units), 2+COUNT+1)
1259 d.addCallback(_check)