source: trunk/src/allmydata/test/test_deepcheck.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 57.4 KB
Line 
1"""
2Ported to Python 3.
3"""
4
5from six import ensure_text
6
7import os, json
8from urllib.parse import quote as url_quote
9
10from twisted.trial import unittest
11from twisted.internet import defer
12from twisted.internet.defer import inlineCallbacks, returnValue
13
14from allmydata.immutable import upload
15from allmydata.mutable.common import UnrecoverableFileError
16from allmydata.mutable.publish import MutableData
17from allmydata.util import idlib
18from allmydata.util import base32
19from allmydata.interfaces import ICheckResults, ICheckAndRepairResults, \
20     IDeepCheckResults, IDeepCheckAndRepairResults
21from allmydata.monitor import Monitor, OperationCancelledError
22from allmydata.uri import LiteralFileURI
23
24from allmydata.test.common import ErrorMixin, _corrupt_mutable_share_data, \
25     ShouldFailMixin
26from .common_util import StallMixin, run_cli_unicode
27from .common_web import do_http
28from allmydata.test.no_network import GridTestMixin
29from .cli.common import CLITestMixin
30
31
32def run_cli(verb, *argv):
33    """Match usage in existing tests by accept *args."""
34    return run_cli_unicode(verb, list(argv))
35
36
37class MutableChecker(GridTestMixin, unittest.TestCase, ErrorMixin):
38    def test_good(self):
39        self.basedir = "deepcheck/MutableChecker/good"
40        self.set_up_grid()
41        CONTENTS = b"a little bit of data"
42        CONTENTS_uploadable = MutableData(CONTENTS)
43        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
44        def _created(node):
45            self.node = node
46            self.fileurl = "uri/" + url_quote(node.get_uri())
47        d.addCallback(_created)
48        # now make sure the webapi verifier sees no problems
49        d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true",
50                                           method="POST"))
51        def _got_results(out):
52            self.failUnless(b"<span>Healthy : Healthy</span>" in out, out)
53            self.failUnless(b"Recoverable Versions: 10*seq1-" in out, out)
54            self.failIf(b"Not Healthy!" in out, out)
55            self.failIf(b"Unhealthy" in out, out)
56            self.failIf(b"Corrupt Shares" in out, out)
57        d.addCallback(_got_results)
58        d.addErrback(self.explain_web_error)
59        return d
60
61    def test_corrupt(self):
62        self.basedir = "deepcheck/MutableChecker/corrupt"
63        self.set_up_grid()
64        CONTENTS = b"a little bit of data"
65        CONTENTS_uploadable = MutableData(CONTENTS)
66        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
67        def _stash_and_corrupt(node):
68            self.node = node
69            self.fileurl = "uri/" + url_quote(node.get_uri())
70            self.corrupt_shares_numbered(node.get_uri(), [0],
71                                         _corrupt_mutable_share_data)
72        d.addCallback(_stash_and_corrupt)
73        # now make sure the webapi verifier notices it
74        d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true",
75                                           method="POST"))
76        def _got_results(out):
77            self.failUnless(b"Not Healthy!" in out, out)
78            self.failUnless(b"Unhealthy: best version has only 9 shares (encoding is 3-of-10)" in out, out)
79            self.failUnless(b"Corrupt Shares:" in out, out)
80        d.addCallback(_got_results)
81
82        # now make sure the webapi repairer can fix it
83        d.addCallback(lambda ign:
84                      self.GET(self.fileurl+"?t=check&verify=true&repair=true",
85                               method="POST"))
86        def _got_repair_results(out):
87            self.failUnless(b"<div>Repair successful</div>" in out, out)
88        d.addCallback(_got_repair_results)
89        d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true",
90                                           method="POST"))
91        def _got_postrepair_results(out):
92            self.failIf(b"Not Healthy!" in out, out)
93            self.failUnless(b"Recoverable Versions: 10*seq" in out, out)
94        d.addCallback(_got_postrepair_results)
95        d.addErrback(self.explain_web_error)
96
97        return d
98
99    def test_delete_share(self):
100        self.basedir = "deepcheck/MutableChecker/delete_share"
101        self.set_up_grid()
102        CONTENTS = b"a little bit of data"
103        CONTENTS_uploadable = MutableData(CONTENTS)
104        d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable)
105        def _stash_and_delete(node):
106            self.node = node
107            self.fileurl = "uri/" + url_quote(node.get_uri())
108            self.delete_shares_numbered(node.get_uri(), [0])
109        d.addCallback(_stash_and_delete)
110        # now make sure the webapi checker notices it
111        d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=false",
112                                           method="POST"))
113        def _got_results(out):
114            self.failUnless(b"Not Healthy!" in out, out)
115            self.failUnless(b"Unhealthy: best version has only 9 shares (encoding is 3-of-10)" in out, out)
116            self.failIf(b"Corrupt Shares" in out, out)
117        d.addCallback(_got_results)
118
119        # now make sure the webapi repairer can fix it
120        d.addCallback(lambda ign:
121                      self.GET(self.fileurl+"?t=check&verify=false&repair=true",
122                               method="POST"))
123        def _got_repair_results(out):
124            self.failUnless(b"Repair successful" in out)
125        d.addCallback(_got_repair_results)
126        d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=false",
127                                           method="POST"))
128        def _got_postrepair_results(out):
129            self.failIf(b"Not Healthy!" in out, out)
130            self.failUnless(b"Recoverable Versions: 10*seq" in out)
131        d.addCallback(_got_postrepair_results)
132        d.addErrback(self.explain_web_error)
133
134        return d
135
136
137class DeepCheckBase(GridTestMixin, ErrorMixin, StallMixin, ShouldFailMixin,
138                    CLITestMixin):
139
140    def web_json(self, n, **kwargs):
141        kwargs["output"] = "json"
142        d = self.web(n, "POST", **kwargs)
143        d.addCallback(self.decode_json)
144        return d
145
146    def decode_json(self, args):
147        (s, url) = args
148        try:
149            data = json.loads(s)
150        except ValueError:
151            self.fail("%s: not JSON: '%s'" % (url, s))
152        return data
153
154    def parse_streamed_json(self, s):
155        s = ensure_text(s)
156        for unit in s.split("\n"):
157            if not unit:
158                # stream should end with a newline, so split returns ""
159                continue
160            try:
161                yield json.loads(unit)
162            except ValueError as le:
163                le.args = tuple(le.args + (unit,))
164                raise
165
166    @inlineCallbacks
167    def web(self, n, method="GET", **kwargs):
168        # returns (data, url)
169        url = (self.client_baseurls[0] + "uri/%s" % url_quote(n.get_uri())
170               + "?" + "&".join(["%s=%s" % (k,str(v, "ascii") if isinstance(v, bytes) else v) for (k,v) in kwargs.items()]))
171        data = yield do_http(method, url, browser_like_redirects=True)
172        returnValue((data,url))
173
174    @inlineCallbacks
175    def wait_for_operation(self, ophandle):
176        url = self.client_baseurls[0] + "operations/" + str(ophandle, "ascii")
177        url += "?t=status&output=JSON"
178        while True:
179            body = yield do_http("get", url)
180            data = json.loads(body)
181            if data["finished"]:
182                break
183            yield self.stall(delay=0.1)
184        returnValue(data)
185
186    @inlineCallbacks
187    def get_operation_results(self, ophandle, output=None):
188        url = self.client_baseurls[0] + "operations/" + str(ophandle, "ascii")
189        url += "?t=status"
190        if output:
191            url += "&output=" + output
192        body = yield do_http("get", url)
193        if output and output.lower() == "json":
194            data = json.loads(body)
195        else:
196            data = body
197        returnValue(data)
198
199    @inlineCallbacks
200    def slow_web(self, n, output=None, **kwargs):
201        # use ophandle=
202        handle = base32.b2a(os.urandom(4))
203        yield self.web(n, "POST", ophandle=handle, **kwargs)
204        yield self.wait_for_operation(handle)
205        data = yield self.get_operation_results(handle, output=output)
206        returnValue(data)
207
208
209class 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.
213
214    def set_up_tree(self):
215        # 2.9s
216
217        c0 = self.g.clients[0]
218        d = c0.create_dirnode()
219        def _created_root(n):
220            self.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(b"mutable file contents")))
225        d.addCallback(lambda n: self.root.set_node(u"mutable", n))
226        def _created_mutable(n):
227            self.mutable = n
228            self.mutable_uri = n.get_uri()
229        d.addCallback(_created_mutable)
230
231        large = upload.Data(b"Lots of data\n" * 1000, None)
232        d.addCallback(lambda ign: self.root.add_file(u"large", large))
233        def _created_large(n):
234            self.large = n
235            self.large_uri = n.get_uri()
236        d.addCallback(_created_large)
237
238        small = upload.Data(b"Small enough for a LIT", None)
239        d.addCallback(lambda ign: self.root.add_file(u"small", small))
240        def _created_small(n):
241            self.small = n
242            self.small_uri = n.get_uri()
243        d.addCallback(_created_small)
244
245        small2 = upload.Data(b"Small enough for a LIT too", None)
246        d.addCallback(lambda ign: self.root.add_file(u"small2", small2))
247        def _created_small2(n):
248            self.small2 = n
249            self.small2_uri = n.get_uri()
250        d.addCallback(_created_small2)
251
252        empty_litdir_uri = b"URI:DIR2-LIT:"
253        tiny_litdir_uri = b"URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
254
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)
261
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)
268
269        d.addCallback(lambda ign: self.root.set_node(u"loop", self.root))
270        return d
271
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(),
276                             where)
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        self.failUnlessEqual(num_servers, 10, where)
281
282        self.failUnlessEqual(cr.get_happiness(), num_servers, where)
283        self.failUnlessEqual(cr.get_share_counter_good(), num_servers, where)
284        self.failUnlessEqual(cr.get_encoding_needed(), 3, where)
285        self.failUnlessEqual(cr.get_encoding_expected(), num_servers, where)
286        if not incomplete:
287            self.failUnlessEqual(cr.get_host_counter_good_shares(),
288                                 num_servers, where)
289        self.failUnlessEqual(cr.get_corrupt_shares(), [], where)
290        if not incomplete:
291            self.failUnlessEqual(sorted([s.get_serverid()
292                                         for s in cr.get_servers_responding()]),
293                                 sorted(self.g.get_all_serverids()),
294                                 where)
295            all_serverids = set()
296            for (shareid, servers) in list(cr.get_sharemap().items()):
297                all_serverids.update([s.get_serverid() for s in servers])
298            self.failUnlessEqual(sorted(all_serverids),
299                                 sorted(self.g.get_all_serverids()),
300                                 where)
301
302        self.failUnlessEqual(cr.get_share_counter_wrong(), 0, where)
303        self.failUnlessEqual(cr.get_version_counter_recoverable(), 1, where)
304        self.failUnlessEqual(cr.get_version_counter_unrecoverable(), 0, where)
305
306
307    def check_and_repair_is_healthy(self, cr, n, where, incomplete=False):
308        self.failUnless(ICheckAndRepairResults.providedBy(cr), (where, cr))
309        self.failUnless(cr.get_pre_repair_results().is_healthy(), where)
310        self.check_is_healthy(cr.get_pre_repair_results(), n, where, incomplete)
311        self.failUnless(cr.get_post_repair_results().is_healthy(), where)
312        self.check_is_healthy(cr.get_post_repair_results(), n, where, incomplete)
313        self.failIf(cr.get_repair_attempted(), where)
314
315    def deep_check_is_healthy(self, cr, num_healthy, where):
316        self.failUnless(IDeepCheckResults.providedBy(cr))
317        self.failUnlessEqual(cr.get_counters()["count-objects-healthy"],
318                             num_healthy, where)
319
320    def deep_check_and_repair_is_healthy(self, cr, num_healthy, where):
321        self.failUnless(IDeepCheckAndRepairResults.providedBy(cr), where)
322        c = cr.get_counters()
323        self.failUnlessEqual(c["count-objects-healthy-pre-repair"],
324                             num_healthy, where)
325        self.failUnlessEqual(c["count-objects-healthy-post-repair"],
326                             num_healthy, where)
327        self.failUnlessEqual(c["count-repairs-attempted"], 0, where)
328
329    def test_good(self):
330        self.basedir = "deepcheck/DeepCheckWebGood/good"
331        self.set_up_grid()
332        d = self.set_up_tree()
333        d.addCallback(self.do_stats)
334        d.addCallback(self.do_web_stream_manifest)
335        d.addCallback(self.do_web_stream_check)
336        d.addCallback(self.do_test_check_good)
337        d.addCallback(self.do_test_web_good)
338        d.addCallback(self.do_test_cli_good)
339        d.addErrback(self.explain_web_error)
340        d.addErrback(self.explain_error)
341        return d
342
343    def do_stats(self, ignored):
344        d = defer.succeed(None)
345        d.addCallback(lambda ign: self.root.start_deep_stats().when_done())
346        d.addCallback(self.check_stats_good)
347        return d
348
349    def check_stats_good(self, s):
350        self.failUnlessEqual(s["count-directories"], 3)
351        self.failUnlessEqual(s["count-files"], 5)
352        self.failUnlessEqual(s["count-immutable-files"], 1)
353        self.failUnlessEqual(s["count-literal-files"], 3)
354        self.failUnlessEqual(s["count-mutable-files"], 1)
355        # don't check directories: their size will vary
356        # s["largest-directory"]
357        # s["size-directories"]
358        self.failUnlessEqual(s["largest-directory-children"], 7)
359        self.failUnlessEqual(s["largest-immutable-file"], 13000)
360        # to re-use this function for both the local
361        # dirnode.start_deep_stats() and the webapi t=start-deep-stats, we
362        # coerce the result into a list of tuples. dirnode.start_deep_stats()
363        # returns a list of tuples, but JSON only knows about lists., so
364        # t=start-deep-stats returns a list of lists.
365        histogram = [tuple(stuff) for stuff in s["size-files-histogram"]]
366        self.failUnlessEqual(histogram, [(4, 10, 1), (11, 31, 2),
367                                         (10001, 31622, 1),
368                                         ])
369        self.failUnlessEqual(s["size-immutable-files"], 13000)
370        self.failUnlessEqual(s["size-literal-files"], 56)
371
372    def do_web_stream_manifest(self, ignored):
373        d = self.web(self.root, method="POST", t="stream-manifest")
374        d.addCallback(lambda output_and_url:
375                      self._check_streamed_manifest(output_and_url[0]))
376        return d
377
378    def _check_streamed_manifest(self, output):
379        units = list(self.parse_streamed_json(output))
380        files = [u for u in units if u["type"] in ("file", "directory")]
381        assert units[-1]["type"] == "stats"
382        stats = units[-1]["stats"]
383        self.failUnlessEqual(len(files), 8)
384        # [root,mutable,large] are distributed, [small,small2,empty_litdir,tiny_litdir] are not
385        self.failUnlessEqual(len([f for f in files
386                                  if f["verifycap"] != ""]), 3)
387        self.failUnlessEqual(len([f for f in files
388                                  if f["verifycap"] == ""]), 5)
389        self.failUnlessEqual(len([f for f in files
390                                  if f["repaircap"] != ""]), 3)
391        self.failUnlessEqual(len([f for f in files
392                                  if f["repaircap"] == ""]), 5)
393        self.failUnlessEqual(len([f for f in files
394                                  if f["storage-index"] != ""]), 3)
395        self.failUnlessEqual(len([f for f in files
396                                  if f["storage-index"] == ""]), 5)
397        # make sure that a mutable file has filecap==repaircap!=verifycap
398        mutable = [f for f in files
399                   if f["cap"] is not None
400                   and f["cap"].startswith("URI:SSK:")][0]
401        self.failUnlessEqual(mutable["cap"].encode("ascii"), self.mutable_uri)
402        self.failIfEqual(mutable["cap"], mutable["verifycap"])
403        self.failUnlessEqual(mutable["cap"], mutable["repaircap"])
404        # for immutable file, verifycap==repaircap!=filecap
405        large = [f for f in files
406                   if f["cap"] is not None
407                   and f["cap"].startswith("URI:CHK:")][0]
408        self.failUnlessEqual(large["cap"].encode("ascii"), self.large_uri)
409        self.failIfEqual(large["cap"], large["verifycap"])
410        self.failUnlessEqual(large["verifycap"], large["repaircap"])
411        self.check_stats_good(stats)
412
413    def do_web_stream_check(self, ignored):
414        # TODO
415        return
416        d = self.web(self.root, t="stream-deep-check")
417        def _check(res):
418            units = list(self.parse_streamed_json(res))
419            #files = [u for u in units if u["type"] in ("file", "directory")]
420            assert units[-1]["type"] == "stats"
421            #stats = units[-1]["stats"]
422            # ...
423        d.addCallback(_check)
424        return d
425
426    def do_test_check_good(self, ignored):
427        d = defer.succeed(None)
428        # check the individual items
429        d.addCallback(lambda ign: self.root.check(Monitor()))
430        d.addCallback(self.check_is_healthy, self.root, "root")
431        d.addCallback(lambda ign: self.mutable.check(Monitor()))
432        d.addCallback(self.check_is_healthy, self.mutable, "mutable")
433        d.addCallback(lambda ign: self.large.check(Monitor()))
434        d.addCallback(self.check_is_healthy, self.large, "large")
435        d.addCallback(lambda ign: self.small.check(Monitor()))
436        d.addCallback(self.failUnlessEqual, None, "small")
437        d.addCallback(lambda ign: self.small2.check(Monitor()))
438        d.addCallback(self.failUnlessEqual, None, "small2")
439        d.addCallback(lambda ign: self.empty_lit_dir.check(Monitor()))
440        d.addCallback(self.failUnlessEqual, None, "empty_lit_dir")
441        d.addCallback(lambda ign: self.tiny_lit_dir.check(Monitor()))
442        d.addCallback(self.failUnlessEqual, None, "tiny_lit_dir")
443
444        # and again with verify=True
445        d.addCallback(lambda ign: self.root.check(Monitor(), verify=True))
446        d.addCallback(self.check_is_healthy, self.root, "root")
447        d.addCallback(lambda ign: self.mutable.check(Monitor(), verify=True))
448        d.addCallback(self.check_is_healthy, self.mutable, "mutable")
449        d.addCallback(lambda ign: self.large.check(Monitor(), verify=True))
450        d.addCallback(self.check_is_healthy, self.large, "large", incomplete=True)
451        d.addCallback(lambda ign: self.small.check(Monitor(), verify=True))
452        d.addCallback(self.failUnlessEqual, None, "small")
453        d.addCallback(lambda ign: self.small2.check(Monitor(), verify=True))
454        d.addCallback(self.failUnlessEqual, None, "small2")
455        d.addCallback(lambda ign: self.empty_lit_dir.check(Monitor(), verify=True))
456        d.addCallback(self.failUnlessEqual, None, "empty_lit_dir")
457        d.addCallback(lambda ign: self.tiny_lit_dir.check(Monitor(), verify=True))
458        d.addCallback(self.failUnlessEqual, None, "tiny_lit_dir")
459
460        # and check_and_repair(), which should be a nop
461        d.addCallback(lambda ign: self.root.check_and_repair(Monitor()))
462        d.addCallback(self.check_and_repair_is_healthy, self.root, "root")
463        d.addCallback(lambda ign: self.mutable.check_and_repair(Monitor()))
464        d.addCallback(self.check_and_repair_is_healthy, self.mutable, "mutable")
465        d.addCallback(lambda ign: self.large.check_and_repair(Monitor()))
466        d.addCallback(self.check_and_repair_is_healthy, self.large, "large")
467        d.addCallback(lambda ign: self.small.check_and_repair(Monitor()))
468        d.addCallback(self.failUnlessEqual, None, "small")
469        d.addCallback(lambda ign: self.small2.check_and_repair(Monitor()))
470        d.addCallback(self.failUnlessEqual, None, "small2")
471        d.addCallback(lambda ign: self.empty_lit_dir.check_and_repair(Monitor()))
472        d.addCallback(self.failUnlessEqual, None, "empty_lit_dir")
473        d.addCallback(lambda ign: self.tiny_lit_dir.check_and_repair(Monitor()))
474
475        # check_and_repair(verify=True)
476        d.addCallback(lambda ign: self.root.check_and_repair(Monitor(), verify=True))
477        d.addCallback(self.check_and_repair_is_healthy, self.root, "root")
478        d.addCallback(lambda ign: self.mutable.check_and_repair(Monitor(), verify=True))
479        d.addCallback(self.check_and_repair_is_healthy, self.mutable, "mutable")
480        d.addCallback(lambda ign: self.large.check_and_repair(Monitor(), verify=True))
481        d.addCallback(self.check_and_repair_is_healthy, self.large, "large", incomplete=True)
482        d.addCallback(lambda ign: self.small.check_and_repair(Monitor(), verify=True))
483        d.addCallback(self.failUnlessEqual, None, "small")
484        d.addCallback(lambda ign: self.small2.check_and_repair(Monitor(), verify=True))
485        d.addCallback(self.failUnlessEqual, None, "small2")
486        d.addCallback(self.failUnlessEqual, None, "small2")
487        d.addCallback(lambda ign: self.empty_lit_dir.check_and_repair(Monitor(), verify=True))
488        d.addCallback(self.failUnlessEqual, None, "empty_lit_dir")
489        d.addCallback(lambda ign: self.tiny_lit_dir.check_and_repair(Monitor(), verify=True))
490
491
492        # now deep-check the root, with various verify= and repair= options
493        d.addCallback(lambda ign:
494                      self.root.start_deep_check().when_done())
495        d.addCallback(self.deep_check_is_healthy, 3, "root")
496        d.addCallback(lambda ign:
497                      self.root.start_deep_check(verify=True).when_done())
498        d.addCallback(self.deep_check_is_healthy, 3, "root")
499        d.addCallback(lambda ign:
500                      self.root.start_deep_check_and_repair().when_done())
501        d.addCallback(self.deep_check_and_repair_is_healthy, 3, "root")
502        d.addCallback(lambda ign:
503                      self.root.start_deep_check_and_repair(verify=True).when_done())
504        d.addCallback(self.deep_check_and_repair_is_healthy, 3, "root")
505
506        # and finally, start a deep-check, but then cancel it.
507        d.addCallback(lambda ign: self.root.start_deep_check())
508        def _checking(monitor):
509            monitor.cancel()
510            d = monitor.when_done()
511            # this should fire as soon as the next dirnode.list finishes.
512            # TODO: add a counter to measure how many list() calls are made,
513            # assert that no more than one gets to run before the cancel()
514            # takes effect.
515            def _finished_normally(res):
516                self.fail("this was supposed to fail, not finish normally")
517            def _cancelled(f):
518                f.trap(OperationCancelledError)
519            d.addCallbacks(_finished_normally, _cancelled)
520            return d
521        d.addCallback(_checking)
522
523        return d
524
525    def json_check_is_healthy(self, data, n, where, incomplete=False):
526
527        self.failUnlessEqual(data["storage-index"],
528                             str(base32.b2a(n.get_storage_index()), "ascii"), where)
529        self.failUnless("summary" in data, (where, data))
530        self.failUnlessEqual(data["summary"].lower(), "healthy",
531                             "%s: '%s'" % (where, data["summary"]))
532        r = data["results"]
533        self.failUnlessEqual(r["healthy"], True, where)
534        num_servers = len(self.g.all_servers)
535        self.failUnlessEqual(num_servers, 10)
536
537        self.failIfIn("needs-rebalancing", r)
538        self.failUnlessEqual(r["count-happiness"], num_servers, where)
539        self.failUnlessEqual(r["count-shares-good"], num_servers, where)
540        self.failUnlessEqual(r["count-shares-needed"], 3, where)
541        self.failUnlessEqual(r["count-shares-expected"], num_servers, where)
542        if not incomplete:
543            self.failUnlessEqual(r["count-good-share-hosts"], num_servers,
544                                 where)
545        self.failUnlessEqual(r["count-corrupt-shares"], 0, where)
546        self.failUnlessEqual(r["list-corrupt-shares"], [], where)
547        if not incomplete:
548            self.failUnlessEqual(sorted(r["servers-responding"]),
549                                 sorted([idlib.nodeid_b2a(sid)
550                                         for sid in self.g.get_all_serverids()]),
551                                 where)
552            self.failUnless("sharemap" in r, where)
553            all_serverids = set()
554            for (shareid, serverids_s) in list(r["sharemap"].items()):
555                all_serverids.update(serverids_s)
556            self.failUnlessEqual(sorted(all_serverids),
557                                 sorted([idlib.nodeid_b2a(sid)
558                                         for sid in self.g.get_all_serverids()]),
559                                 where)
560        self.failUnlessEqual(r["count-wrong-shares"], 0, where)
561        self.failUnlessEqual(r["count-recoverable-versions"], 1, where)
562        self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where)
563
564    def json_check_and_repair_is_healthy(self, data, n, where, incomplete=False):
565        self.failUnlessEqual(data["storage-index"],
566                             str(base32.b2a(n.get_storage_index()), "ascii"), where)
567        self.failUnlessEqual(data["repair-attempted"], False, where)
568        self.json_check_is_healthy(data["pre-repair-results"],
569                                   n, where, incomplete)
570        self.json_check_is_healthy(data["post-repair-results"],
571                                   n, where, incomplete)
572
573    def json_full_deepcheck_is_healthy(self, data, n, where):
574        self.failUnlessEqual(data["root-storage-index"],
575                             str(base32.b2a(n.get_storage_index()), "ascii"), where)
576        self.failUnlessEqual(data["count-objects-checked"], 3, where)
577        self.failUnlessEqual(data["count-objects-healthy"], 3, where)
578        self.failUnlessEqual(data["count-objects-unhealthy"], 0, where)
579        self.failUnlessEqual(data["count-corrupt-shares"], 0, where)
580        self.failUnlessEqual(data["list-corrupt-shares"], [], where)
581        self.failUnlessEqual(data["list-unhealthy-files"], [], where)
582        self.json_check_stats_good(data["stats"], where)
583
584    def json_full_deepcheck_and_repair_is_healthy(self, data, n, where):
585        self.failUnlessEqual(data["root-storage-index"],
586                             str(base32.b2a(n.get_storage_index()), "ascii"), where)
587        self.failUnlessEqual(data["count-objects-checked"], 3, where)
588
589        self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 3, where)
590        self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0, where)
591        self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0, where)
592
593        self.failUnlessEqual(data["count-objects-healthy-post-repair"], 3, where)
594        self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0, where)
595        self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0, where)
596
597        self.failUnlessEqual(data["list-corrupt-shares"], [], where)
598        self.failUnlessEqual(data["list-remaining-corrupt-shares"], [], where)
599        self.failUnlessEqual(data["list-unhealthy-files"], [], where)
600
601        self.failUnlessEqual(data["count-repairs-attempted"], 0, where)
602        self.failUnlessEqual(data["count-repairs-successful"], 0, where)
603        self.failUnlessEqual(data["count-repairs-unsuccessful"], 0, where)
604
605
606    def json_check_lit(self, data, n, where):
607        self.failUnlessEqual(data["storage-index"], "", where)
608        self.failUnlessEqual(data["results"]["healthy"], True, where)
609
610    def json_check_stats_good(self, data, where):
611        self.check_stats_good(data)
612
613    def do_test_web_good(self, ignored):
614        d = defer.succeed(None)
615
616        # stats
617        d.addCallback(lambda ign:
618                      self.slow_web(self.root,
619                                    t="start-deep-stats", output="json"))
620        d.addCallback(self.json_check_stats_good, "deep-stats")
621
622        # check, no verify
623        d.addCallback(lambda ign: self.web_json(self.root, t="check"))
624        d.addCallback(self.json_check_is_healthy, self.root, "root")
625        d.addCallback(lambda ign: self.web_json(self.mutable, t="check"))
626        d.addCallback(self.json_check_is_healthy, self.mutable, "mutable")
627        d.addCallback(lambda ign: self.web_json(self.large, t="check"))
628        d.addCallback(self.json_check_is_healthy, self.large, "large")
629        d.addCallback(lambda ign: self.web_json(self.small, t="check"))
630        d.addCallback(self.json_check_lit, self.small, "small")
631        d.addCallback(lambda ign: self.web_json(self.small2, t="check"))
632        d.addCallback(self.json_check_lit, self.small2, "small2")
633        d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check"))
634        d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir")
635        d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check"))
636        d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir")
637
638        # check and verify
639        d.addCallback(lambda ign:
640                      self.web_json(self.root, t="check", verify="true"))
641        d.addCallback(self.json_check_is_healthy, self.root, "root+v")
642        d.addCallback(lambda ign:
643                      self.web_json(self.mutable, t="check", verify="true"))
644        d.addCallback(self.json_check_is_healthy, self.mutable, "mutable+v")
645        d.addCallback(lambda ign:
646                      self.web_json(self.large, t="check", verify="true"))
647        d.addCallback(self.json_check_is_healthy, self.large, "large+v",
648                      incomplete=True)
649        d.addCallback(lambda ign:
650                      self.web_json(self.small, t="check", verify="true"))
651        d.addCallback(self.json_check_lit, self.small, "small+v")
652        d.addCallback(lambda ign:
653                      self.web_json(self.small2, t="check", verify="true"))
654        d.addCallback(self.json_check_lit, self.small2, "small2+v")
655        d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", verify="true"))
656        d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+v")
657        d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", verify="true"))
658        d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+v")
659
660        # check and repair, no verify
661        d.addCallback(lambda ign:
662                      self.web_json(self.root, t="check", repair="true"))
663        d.addCallback(self.json_check_and_repair_is_healthy, self.root, "root+r")
664        d.addCallback(lambda ign:
665                      self.web_json(self.mutable, t="check", repair="true"))
666        d.addCallback(self.json_check_and_repair_is_healthy, self.mutable, "mutable+r")
667        d.addCallback(lambda ign:
668                      self.web_json(self.large, t="check", repair="true"))
669        d.addCallback(self.json_check_and_repair_is_healthy, self.large, "large+r")
670        d.addCallback(lambda ign:
671                      self.web_json(self.small, t="check", repair="true"))
672        d.addCallback(self.json_check_lit, self.small, "small+r")
673        d.addCallback(lambda ign:
674                      self.web_json(self.small2, t="check", repair="true"))
675        d.addCallback(self.json_check_lit, self.small2, "small2+r")
676        d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", repair="true"))
677        d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+r")
678        d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", repair="true"))
679        d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+r")
680
681        # check+verify+repair
682        d.addCallback(lambda ign:
683                      self.web_json(self.root, t="check", repair="true", verify="true"))
684        d.addCallback(self.json_check_and_repair_is_healthy, self.root, "root+vr")
685        d.addCallback(lambda ign:
686                      self.web_json(self.mutable, t="check", repair="true", verify="true"))
687        d.addCallback(self.json_check_and_repair_is_healthy, self.mutable, "mutable+vr")
688        d.addCallback(lambda ign:
689                      self.web_json(self.large, t="check", repair="true", verify="true"))
690        d.addCallback(self.json_check_and_repair_is_healthy, self.large, "large+vr", incomplete=True)
691        d.addCallback(lambda ign:
692                      self.web_json(self.small, t="check", repair="true", verify="true"))
693        d.addCallback(self.json_check_lit, self.small, "small+vr")
694        d.addCallback(lambda ign:
695                      self.web_json(self.small2, t="check", repair="true", verify="true"))
696        d.addCallback(self.json_check_lit, self.small2, "small2+vr")
697        d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", repair="true", verify=True))
698        d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+vr")
699        d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", repair="true", verify=True))
700        d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+vr")
701
702        # now run a deep-check, with various verify= and repair= flags
703        d.addCallback(lambda ign:
704                      self.slow_web(self.root, t="start-deep-check", output="json"))
705        d.addCallback(self.json_full_deepcheck_is_healthy, self.root, "root+d")
706        d.addCallback(lambda ign:
707                      self.slow_web(self.root, t="start-deep-check", verify="true",
708                                    output="json"))
709        d.addCallback(self.json_full_deepcheck_is_healthy, self.root, "root+dv")
710        d.addCallback(lambda ign:
711                      self.slow_web(self.root, t="start-deep-check", repair="true",
712                                    output="json"))
713        d.addCallback(self.json_full_deepcheck_and_repair_is_healthy, self.root, "root+dr")
714        d.addCallback(lambda ign:
715                      self.slow_web(self.root, t="start-deep-check", verify="true", repair="true", output="json"))
716        d.addCallback(self.json_full_deepcheck_and_repair_is_healthy, self.root, "root+dvr")
717
718        # now look at t=info
719        d.addCallback(lambda ign: self.web(self.root, t="info"))
720        # TODO: examine the output
721        d.addCallback(lambda ign: self.web(self.mutable, t="info"))
722        d.addCallback(lambda ign: self.web(self.large, t="info"))
723        d.addCallback(lambda ign: self.web(self.small, t="info"))
724        d.addCallback(lambda ign: self.web(self.small2, t="info"))
725        d.addCallback(lambda ign: self.web(self.empty_lit_dir, t="info"))
726        d.addCallback(lambda ign: self.web(self.tiny_lit_dir, t="info"))
727
728        return d
729
730    def do_test_cli_good(self, ignored):
731        d = defer.succeed(None)
732        d.addCallback(lambda ign: self.do_cli_manifest_stream1())
733        d.addCallback(lambda ign: self.do_cli_manifest_stream2())
734        d.addCallback(lambda ign: self.do_cli_manifest_stream3())
735        d.addCallback(lambda ign: self.do_cli_manifest_stream4())
736        d.addCallback(lambda ign: self.do_cli_manifest_stream5())
737        d.addCallback(lambda ign: self.do_cli_stats1())
738        d.addCallback(lambda ign: self.do_cli_stats2())
739        return d
740
741    def _check_manifest_storage_index(self, out):
742        lines = [l.encode("utf-8") for l in out.split("\n") if l]
743        self.failUnlessEqual(len(lines), 3)
744        self.failUnless(base32.b2a(self.root.get_storage_index()) in lines)
745        self.failUnless(base32.b2a(self.mutable.get_storage_index()) in lines)
746        self.failUnless(base32.b2a(self.large.get_storage_index()) in lines)
747
748    def do_cli_manifest_stream1(self):
749        d = self.do_cli("manifest", self.root_uri)
750        def _check(args):
751            (rc, out, err) = args
752            self.failUnlessEqual(err, "")
753            lines = [l for l in out.split("\n") if l]
754            self.failUnlessEqual(len(lines), 8)
755            caps = {}
756            for l in lines:
757                try:
758                    cap, path = l.split(None, 1)
759                except ValueError:
760                    cap = l.strip()
761                    path = ""
762                caps[cap.encode("ascii")] = path
763            self.failUnless(self.root.get_uri() in caps)
764            self.failUnlessEqual(caps[self.root.get_uri()], "")
765            self.failUnlessEqual(caps[self.mutable.get_uri()], "mutable")
766            self.failUnlessEqual(caps[self.large.get_uri()], "large")
767            self.failUnlessEqual(caps[self.small.get_uri()], "small")
768            self.failUnlessEqual(caps[self.small2.get_uri()], "small2")
769            self.failUnlessEqual(caps[self.empty_lit_dir.get_uri()], "empty_lit_dir")
770            self.failUnlessEqual(caps[self.tiny_lit_dir.get_uri()], "tiny_lit_dir")
771        d.addCallback(_check)
772        return d
773
774    def do_cli_manifest_stream2(self):
775        d = self.do_cli("manifest", "--raw", self.root_uri)
776        def _check(args):
777            (rc, out, err) = args
778            self.failUnlessEqual(err, "")
779            # this should be the same as the POST t=stream-manifest output
780            self._check_streamed_manifest(out)
781        d.addCallback(_check)
782        return d
783
784    def do_cli_manifest_stream3(self):
785        d = self.do_cli("manifest", "--storage-index", self.root_uri)
786        def _check(args):
787            (rc, out, err) = args
788            self.failUnlessEqual(err, "")
789            self._check_manifest_storage_index(out)
790        d.addCallback(_check)
791        return d
792
793    def do_cli_manifest_stream4(self):
794        d = self.do_cli("manifest", "--verify-cap", self.root_uri)
795        def _check(args):
796            (rc, out, err) = args
797            self.failUnlessEqual(err, "")
798            lines = [l.encode("utf-8") for l in out.split("\n") if l]
799            self.failUnlessEqual(len(lines), 3)
800            self.failUnless(self.root.get_verify_cap().to_string() in lines)
801            self.failUnless(self.mutable.get_verify_cap().to_string() in lines)
802            self.failUnless(self.large.get_verify_cap().to_string() in lines)
803        d.addCallback(_check)
804        return d
805
806    def do_cli_manifest_stream5(self):
807        d = self.do_cli("manifest", "--repair-cap", self.root_uri)
808        def _check(args):
809            (rc, out, err) = args
810            self.failUnlessEqual(err, "")
811            lines = [l.encode("utf-8") for l in out.split("\n") if l]
812            self.failUnlessEqual(len(lines), 3)
813            self.failUnless(self.root.get_repair_cap().to_string() in lines)
814            self.failUnless(self.mutable.get_repair_cap().to_string() in lines)
815            self.failUnless(self.large.get_repair_cap().to_string() in lines)
816        d.addCallback(_check)
817        return d
818
819    def do_cli_stats1(self):
820        d = self.do_cli("stats", self.root_uri)
821        def _check3(args):
822            (rc, out, err) = args
823            lines = [l.strip() for l in out.split("\n") if l]
824            self.failUnless("count-immutable-files: 1" in lines)
825            self.failUnless("count-mutable-files: 1" in lines)
826            self.failUnless("count-literal-files: 3" in lines)
827            self.failUnless("count-files: 5" in lines)
828            self.failUnless("count-directories: 3" in lines)
829            self.failUnless("size-immutable-files: 13000    (13.00 kB, 12.70 kiB)" in lines, lines)
830            self.failUnless("size-literal-files: 56" in lines, lines)
831            self.failUnless("    4-10    : 1    (10 B, 10 B)".strip() in lines, lines)
832            self.failUnless("   11-31    : 2    (31 B, 31 B)".strip() in lines, lines)
833            self.failUnless("10001-31622 : 1    (31.62 kB, 30.88 kiB)".strip() in lines, lines)
834        d.addCallback(_check3)
835        return d
836
837    def do_cli_stats2(self):
838        d = self.do_cli("stats", "--raw", self.root_uri)
839        def _check4(args):
840            (rc, out, err) = args
841            data = json.loads(out)
842            self.failUnlessEqual(data["count-immutable-files"], 1)
843            self.failUnlessEqual(data["count-immutable-files"], 1)
844            self.failUnlessEqual(data["count-mutable-files"], 1)
845            self.failUnlessEqual(data["count-literal-files"], 3)
846            self.failUnlessEqual(data["count-files"], 5)
847            self.failUnlessEqual(data["count-directories"], 3)
848            self.failUnlessEqual(data["size-immutable-files"], 13000)
849            self.failUnlessEqual(data["size-literal-files"], 56)
850            self.failUnless([4,10,1] in data["size-files-histogram"])
851            self.failUnless([11,31,2] in data["size-files-histogram"])
852            self.failUnless([10001,31622,1] in data["size-files-histogram"])
853        d.addCallback(_check4)
854        return d
855
856
857class DeepCheckWebBad(DeepCheckBase, unittest.TestCase):
858    def test_bad(self):
859        self.basedir = "deepcheck/DeepCheckWebBad/bad"
860        self.set_up_grid()
861        d = self.set_up_damaged_tree()
862        d.addCallback(self.do_check)
863        d.addCallback(self.do_deepcheck)
864        d.addCallback(self.do_deepcheck_broken)
865        d.addCallback(self.do_test_web_bad)
866        d.addErrback(self.explain_web_error)
867        d.addErrback(self.explain_error)
868        return d
869
870
871
872    def set_up_damaged_tree(self):
873        # 6.4s
874
875        # root
876        #   mutable-good
877        #   mutable-missing-shares
878        #   mutable-corrupt-shares
879        #   mutable-unrecoverable
880        #   large-good
881        #   large-missing-shares
882        #   large-corrupt-shares
883        #   large-unrecoverable
884        # broken
885        #   large1-good
886        #   subdir-good
887        #     large2-good
888        #   subdir-unrecoverable
889        #     large3-good
890
891        self.nodes = {}
892
893        c0 = self.g.clients[0]
894        d = c0.create_dirnode()
895        def _created_root(n):
896            self.root = n
897            self.root_uri = n.get_uri()
898        d.addCallback(_created_root)
899        d.addCallback(self.create_mangled, "mutable-good")
900        d.addCallback(self.create_mangled, "mutable-missing-shares")
901        d.addCallback(self.create_mangled, "mutable-corrupt-shares")
902        d.addCallback(self.create_mangled, "mutable-unrecoverable")
903        d.addCallback(self.create_mangled, "large-good")
904        d.addCallback(self.create_mangled, "large-missing-shares")
905        d.addCallback(self.create_mangled, "large-corrupt-shares")
906        d.addCallback(self.create_mangled, "large-unrecoverable")
907        d.addCallback(lambda ignored: c0.create_dirnode())
908        d.addCallback(self._stash_node, "broken")
909        large1 = upload.Data(b"Lots of data\n" * 1000 + b"large1" + b"\n", None)
910        d.addCallback(lambda ignored:
911                      self.nodes["broken"].add_file(u"large1", large1))
912        d.addCallback(lambda ignored:
913                      self.nodes["broken"].create_subdirectory(u"subdir-good"))
914        large2 = upload.Data(b"Lots of data\n" * 1000 + b"large2" + b"\n", None)
915        d.addCallback(lambda subdir: subdir.add_file(u"large2-good", large2))
916        d.addCallback(lambda ignored:
917                      self.nodes["broken"].create_subdirectory(u"subdir-unrecoverable"))
918        d.addCallback(self._stash_node, "subdir-unrecoverable")
919        large3 = upload.Data(b"Lots of data\n" * 1000 + b"large3" + b"\n", None)
920        d.addCallback(lambda subdir: subdir.add_file(u"large3-good", large3))
921        d.addCallback(lambda ignored:
922                      self._delete_most_shares(self.nodes["broken"]))
923        return d
924
925    def _stash_node(self, node, name):
926        self.nodes[name] = node
927        return node
928
929    def create_mangled(self, ignored, name):
930        nodetype, mangletype = name.split("-", 1)
931        if nodetype == "mutable":
932            mutable_uploadable = MutableData(b"mutable file contents")
933            d = self.g.clients[0].create_mutable_file(mutable_uploadable)
934            d.addCallback(lambda n: self.root.set_node(str(name), n))  # TODO drop str() once strings are unicode
935        elif nodetype == "large":
936            large = upload.Data(b"Lots of data\n" * 1000 + name.encode("ascii") + b"\n", None)
937            d = self.root.add_file(str(name), large)
938        elif nodetype == "small":
939            small = upload.Data(b"Small enough for a LIT", None)
940            d = self.root.add_file(str(name), small)
941
942        d.addCallback(self._stash_node, name)
943
944        if mangletype == "good":
945            pass
946        elif mangletype == "missing-shares":
947            d.addCallback(self._delete_some_shares)
948        elif mangletype == "corrupt-shares":
949            d.addCallback(self._corrupt_some_shares)
950        else:
951            assert mangletype == "unrecoverable"
952            d.addCallback(self._delete_most_shares)
953
954        return d
955
956    def _delete_some_shares(self, node):
957        self.delete_shares_numbered(node.get_uri(), [0,1])
958
959    @defer.inlineCallbacks
960    def _corrupt_some_shares(self, node):
961        for (shnum, serverid, sharefile) in self.find_uri_shares(node.get_uri()):
962            if shnum in (0,1):
963                yield run_cli("debug", "corrupt-share", sharefile)
964
965    def _delete_most_shares(self, node):
966        self.delete_shares_numbered(node.get_uri(), list(range(1,10)))
967
968
969    def check_is_healthy(self, cr, where):
970        try:
971            self.failUnless(ICheckResults.providedBy(cr), (cr, type(cr), where))
972            self.failUnless(cr.is_healthy(), (cr.get_report(), cr.is_healthy(), cr.get_summary(), where))
973            self.failUnless(cr.is_recoverable(), where)
974            self.failUnlessEqual(cr.get_version_counter_recoverable(), 1, where)
975            self.failUnlessEqual(cr.get_version_counter_unrecoverable(), 0, where)
976            return cr
977        except Exception as le:
978            le.args = tuple(le.args + (where,))
979            raise
980
981    def check_is_missing_shares(self, cr, where):
982        self.failUnless(ICheckResults.providedBy(cr), where)
983        self.failIf(cr.is_healthy(), where)
984        self.failUnless(cr.is_recoverable(), where)
985        self.failUnlessEqual(cr.get_version_counter_recoverable(), 1, where)
986        self.failUnlessEqual(cr.get_version_counter_unrecoverable(), 0, where)
987        return cr
988
989    def check_has_corrupt_shares(self, cr, where):
990        # by "corrupt-shares" we mean the file is still recoverable
991        self.failUnless(ICheckResults.providedBy(cr), where)
992        self.failIf(cr.is_healthy(), (where, cr))
993        self.failUnless(cr.is_recoverable(), where)
994        self.failUnless(cr.get_share_counter_good() < 10, where)
995        self.failUnless(cr.get_corrupt_shares(), where)
996        return cr
997
998    def check_is_unrecoverable(self, cr, where):
999        self.failUnless(ICheckResults.providedBy(cr), where)
1000        self.failIf(cr.is_healthy(), where)
1001        self.failIf(cr.is_recoverable(), where)
1002        self.failUnless(cr.get_share_counter_good() < cr.get_encoding_needed(),
1003                        (cr.get_share_counter_good(), cr.get_encoding_needed(),
1004                         where))
1005        self.failUnlessEqual(cr.get_version_counter_recoverable(), 0, where)
1006        self.failUnlessEqual(cr.get_version_counter_unrecoverable(), 1, where)
1007        return cr
1008
1009    def do_check(self, ignored):
1010        d = defer.succeed(None)
1011
1012        # check the individual items, without verification. This will not
1013        # detect corrupt shares.
1014        def _check(which, checker):
1015            d = self.nodes[which].check(Monitor())
1016            d.addCallback(checker, which + "--check")
1017            return d
1018
1019        d.addCallback(lambda ign: _check("mutable-good", self.check_is_healthy))
1020        d.addCallback(lambda ign: _check("mutable-missing-shares",
1021                                         self.check_is_missing_shares))
1022        d.addCallback(lambda ign: _check("mutable-corrupt-shares",
1023                                         self.check_is_healthy))
1024        d.addCallback(lambda ign: _check("mutable-unrecoverable",
1025                                         self.check_is_unrecoverable))
1026        d.addCallback(lambda ign: _check("large-good", self.check_is_healthy))
1027        d.addCallback(lambda ign: _check("large-missing-shares",
1028                                         self.check_is_missing_shares))
1029        d.addCallback(lambda ign: _check("large-corrupt-shares",
1030                                         self.check_is_healthy))
1031        d.addCallback(lambda ign: _check("large-unrecoverable",
1032                                         self.check_is_unrecoverable))
1033
1034        # and again with verify=True, which *does* detect corrupt shares.
1035        def _checkv(which, checker):
1036            d = self.nodes[which].check(Monitor(), verify=True)
1037            d.addCallback(checker, which + "--check-and-verify")
1038            return d
1039
1040        d.addCallback(lambda ign: _checkv("mutable-good", self.check_is_healthy))
1041        d.addCallback(lambda ign: _checkv("mutable-missing-shares",
1042                                         self.check_is_missing_shares))
1043        d.addCallback(lambda ign: _checkv("mutable-corrupt-shares",
1044                                         self.check_has_corrupt_shares))
1045        d.addCallback(lambda ign: _checkv("mutable-unrecoverable",
1046                                         self.check_is_unrecoverable))
1047        d.addCallback(lambda ign: _checkv("large-good", self.check_is_healthy))
1048        d.addCallback(lambda ign: _checkv("large-missing-shares", self.check_is_missing_shares))
1049        d.addCallback(lambda ign: _checkv("large-corrupt-shares", self.check_has_corrupt_shares))
1050        d.addCallback(lambda ign: _checkv("large-unrecoverable",
1051                                         self.check_is_unrecoverable))
1052
1053        return d
1054
1055    def do_deepcheck(self, ignored):
1056        d = defer.succeed(None)
1057
1058        # now deep-check the root, with various verify= and repair= options
1059        d.addCallback(lambda ign:
1060                      self.root.start_deep_check().when_done())
1061        def _check1(cr):
1062            self.failUnless(IDeepCheckResults.providedBy(cr))
1063            c = cr.get_counters()
1064            self.failUnlessEqual(c["count-objects-checked"], 9)
1065            self.failUnlessEqual(c["count-objects-healthy"], 5)
1066            self.failUnlessEqual(c["count-objects-unhealthy"], 4)
1067            self.failUnlessEqual(c["count-objects-unrecoverable"], 2)
1068        d.addCallback(_check1)
1069
1070        d.addCallback(lambda ign:
1071                      self.root.start_deep_check(verify=True).when_done())
1072        def _check2(cr):
1073            self.failUnless(IDeepCheckResults.providedBy(cr))
1074            c = cr.get_counters()
1075            self.failUnlessEqual(c["count-objects-checked"], 9)
1076            self.failUnlessEqual(c["count-objects-healthy"], 3)
1077            self.failUnlessEqual(c["count-objects-unhealthy"], 6)
1078            self.failUnlessEqual(c["count-objects-healthy"], 3) # root, mutable good, large good
1079            self.failUnlessEqual(c["count-objects-unrecoverable"], 2) # mutable unrecoverable, large unrecoverable
1080        d.addCallback(_check2)
1081
1082        return d
1083
1084    def do_deepcheck_broken(self, ignored):
1085        # deep-check on the broken directory should fail, because of the
1086        # untraversable subdir
1087        def _do_deep_check():
1088            return self.nodes["broken"].start_deep_check().when_done()
1089        d = self.shouldFail(UnrecoverableFileError, "do_deep_check",
1090                            "no recoverable versions",
1091                            _do_deep_check)
1092        return d
1093
1094    def json_is_healthy(self, data, where):
1095        r = data["results"]
1096        self.failUnless(r["healthy"], where)
1097        self.failUnless(r["recoverable"], where)
1098        self.failUnlessEqual(r["count-recoverable-versions"], 1, where)
1099        self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where)
1100
1101    def json_is_missing_shares(self, data, where):
1102        r = data["results"]
1103        self.failIf(r["healthy"], where)
1104        self.failUnless(r["recoverable"], where)
1105        self.failUnlessEqual(r["count-recoverable-versions"], 1, where)
1106        self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where)
1107
1108    def json_has_corrupt_shares(self, data, where):
1109        # by "corrupt-shares" we mean the file is still recoverable
1110        r = data["results"]
1111        self.failIf(r["healthy"], where)
1112        self.failUnless(r["recoverable"], where)
1113        self.failUnless(r["count-shares-good"] < 10, where)
1114        self.failUnless(r["count-corrupt-shares"], where)
1115        self.failUnless(r["list-corrupt-shares"], where)
1116
1117    def json_is_unrecoverable(self, data, where):
1118        r = data["results"]
1119        self.failIf(r["healthy"], where)
1120        self.failIf(r["recoverable"], where)
1121        self.failUnless(r["count-shares-good"] < r["count-shares-needed"],
1122                        where)
1123        self.failUnlessEqual(r["count-recoverable-versions"], 0, where)
1124        self.failUnlessEqual(r["count-unrecoverable-versions"], 1, where)
1125
1126    def do_test_web_bad(self, ignored):
1127        d = defer.succeed(None)
1128
1129        # check, no verify
1130        def _check(which, checker):
1131            d = self.web_json(self.nodes[which], t="check")
1132            d.addCallback(checker, which + "--webcheck")
1133            return d
1134
1135        d.addCallback(lambda ign: _check("mutable-good",
1136                                         self.json_is_healthy))
1137        d.addCallback(lambda ign: _check("mutable-missing-shares",
1138                                         self.json_is_missing_shares))
1139        d.addCallback(lambda ign: _check("mutable-corrupt-shares",
1140                                         self.json_is_healthy))
1141        d.addCallback(lambda ign: _check("mutable-unrecoverable",
1142                                         self.json_is_unrecoverable))
1143        d.addCallback(lambda ign: _check("large-good",
1144                                         self.json_is_healthy))
1145        d.addCallback(lambda ign: _check("large-missing-shares",
1146                                         self.json_is_missing_shares))
1147        d.addCallback(lambda ign: _check("large-corrupt-shares",
1148                                         self.json_is_healthy))
1149        d.addCallback(lambda ign: _check("large-unrecoverable",
1150                                         self.json_is_unrecoverable))
1151
1152        # check and verify
1153        def _checkv(which, checker):
1154            d = self.web_json(self.nodes[which], t="check", verify="true")
1155            d.addCallback(checker, which + "--webcheck-and-verify")
1156            return d
1157
1158        d.addCallback(lambda ign: _checkv("mutable-good",
1159                                          self.json_is_healthy))
1160        d.addCallback(lambda ign: _checkv("mutable-missing-shares",
1161                                         self.json_is_missing_shares))
1162        d.addCallback(lambda ign: _checkv("mutable-corrupt-shares",
1163                                         self.json_has_corrupt_shares))
1164        d.addCallback(lambda ign: _checkv("mutable-unrecoverable",
1165                                         self.json_is_unrecoverable))
1166        d.addCallback(lambda ign: _checkv("large-good",
1167                                          self.json_is_healthy))
1168        d.addCallback(lambda ign: _checkv("large-missing-shares", self.json_is_missing_shares))
1169        d.addCallback(lambda ign: _checkv("large-corrupt-shares", self.json_has_corrupt_shares))
1170        d.addCallback(lambda ign: _checkv("large-unrecoverable",
1171                                         self.json_is_unrecoverable))
1172
1173        return d
1174
1175class Large(DeepCheckBase, unittest.TestCase):
1176    def test_lots_of_lits(self):
1177        self.basedir = "deepcheck/Large/lots_of_lits"
1178        self.set_up_grid()
1179        # create the following directory structure:
1180        #  root/
1181        #   subdir/
1182        #    000-large (CHK)
1183        #    001-small (LIT)
1184        #    002-small
1185        #    ...
1186        #    399-small
1187        # then do a deepcheck and make sure it doesn't cause a
1188        # Deferred-tail-recursion stack overflow
1189
1190        COUNT = 400
1191        c0 = self.g.clients[0]
1192        d = c0.create_dirnode()
1193        self.stash = {}
1194        def _created_root(n):
1195            self.root = n
1196            return n
1197        d.addCallback(_created_root)
1198        d.addCallback(lambda root: root.create_subdirectory(u"subdir"))
1199        def _add_children(subdir_node):
1200            self.subdir_node = subdir_node
1201            kids = {}
1202            for i in range(1, COUNT):
1203                litcap = LiteralFileURI(b"%03d-data" % i).to_string()
1204                kids[u"%03d-small" % i] = (litcap, litcap)
1205            return subdir_node.set_children(kids)
1206        d.addCallback(_add_children)
1207        up = upload.Data(b"large enough for CHK" * 100, b"")
1208        d.addCallback(lambda ign: self.subdir_node.add_file(u"0000-large", up))
1209
1210        def _start_deepcheck(ignored):
1211            return self.web(self.root, method="POST", t="stream-deep-check")
1212        d.addCallback(_start_deepcheck)
1213        def _check(output_and_url):
1214            (output, url) = output_and_url
1215            units = list(self.parse_streamed_json(output))
1216            self.failUnlessEqual(len(units), 2+COUNT+1)
1217        d.addCallback(_check)
1218
1219        return d
Note: See TracBrowser for help on using the repository browser.