source: trunk/src/allmydata/test/cli/test_check.py

Last change on this file was fd28160, checked in by JW Jacobson <116485484+jwjacobson@…>, at 2024-11-04T16:31:45Z

WIP 1101 Add verbose flag to check command

This commit adds the verbose option to the command 'check' so that if
you choose verbose, you do not get an error message. The full verbose
output is not yet implemented. When verbose is fully supported, the
option should return more detailed output.

modified: src/allmydata/scripts/cli.py
modified: src/allmydata/test/cli/test_check.py

  • Property mode set to 100644
File size: 19.1 KB
Line 
1from six import ensure_text
2
3import os.path
4import json
5from twisted.trial import unittest
6from io import StringIO
7
8from allmydata import uri
9from allmydata.util import base32
10from allmydata.util.encodingutil import to_bytes, quote_output_u
11from allmydata.mutable.publish import MutableData
12from allmydata.immutable import upload
13from allmydata.scripts import debug
14from ..no_network import GridTestMixin
15from .common import CLITestMixin
16
17
18class Check(GridTestMixin, CLITestMixin, unittest.TestCase):
19
20    def test_check(self):
21        self.basedir = "cli/Check/check"
22        self.set_up_grid()
23        c0 = self.g.clients[0]
24        DATA = b"data" * 100
25        DATA_uploadable = MutableData(DATA)
26        d = c0.create_mutable_file(DATA_uploadable)
27        def _stash_uri(n):
28            self.uri = n.get_uri()
29        d.addCallback(_stash_uri)
30
31        d.addCallback(lambda ign: self.do_cli("check", self.uri))
32        def _check1(args):
33            (rc, out, err) = args
34            self.assertEqual(len(err), 0, err)
35            self.failUnlessReallyEqual(rc, 0)
36            lines = out.splitlines()
37            self.failUnless("Summary: Healthy" in lines, out)
38            self.failUnless(" good-shares: 10 (encoding is 3-of-10)" in lines, out)
39        d.addCallback(_check1)
40
41        d.addCallback(lambda ign: self.do_cli("check", "--raw", self.uri))
42        def _check2(args):
43            (rc, out, err) = args
44            self.assertEqual(len(err), 0, err)
45            self.failUnlessReallyEqual(rc, 0)
46            data = json.loads(out)
47            self.failUnlessReallyEqual(to_bytes(data["summary"]), b"Healthy")
48            self.failUnlessReallyEqual(data["results"]["healthy"], True)
49        d.addCallback(_check2)
50
51        d.addCallback(lambda ign: c0.upload(upload.Data(b"literal", convergence=b"")))
52        def _stash_lit_uri(n):
53            self.lit_uri = n.get_uri()
54        d.addCallback(_stash_lit_uri)
55
56        d.addCallback(lambda ign: self.do_cli("check", self.lit_uri))
57        def _check_lit(args):
58            (rc, out, err) = args
59            self.assertEqual(len(err), 0, err)
60            self.failUnlessReallyEqual(rc, 0)
61            lines = out.splitlines()
62            self.failUnless("Summary: Healthy (LIT)" in lines, out)
63        d.addCallback(_check_lit)
64
65        d.addCallback(lambda ign: self.do_cli("check", "--raw", self.lit_uri))
66        def _check_lit_raw(args):
67            (rc, out, err) = args
68            self.assertEqual(len(err), 0, err)
69            self.failUnlessReallyEqual(rc, 0)
70            data = json.loads(out)
71            self.failUnlessReallyEqual(data["results"]["healthy"], True)
72        d.addCallback(_check_lit_raw)
73
74        d.addCallback(lambda ign: c0.create_immutable_dirnode({}, convergence=b""))
75        def _stash_lit_dir_uri(n):
76            self.lit_dir_uri = n.get_uri()
77        d.addCallback(_stash_lit_dir_uri)
78
79        d.addCallback(lambda ign: self.do_cli("check", self.lit_dir_uri))
80        d.addCallback(_check_lit)
81
82        d.addCallback(lambda ign: self.do_cli("check", "--raw", self.lit_uri))
83        d.addCallback(_check_lit_raw)
84
85        def _clobber_shares(ignored):
86            # delete one, corrupt a second
87            shares = self.find_uri_shares(self.uri)
88            self.failUnlessReallyEqual(len(shares), 10)
89            os.unlink(shares[0][2])
90            cso = debug.CorruptShareOptions()
91            cso.stdout = StringIO()
92            cso.parseOptions([shares[1][2]])
93            storage_index = uri.from_string(self.uri).get_storage_index()
94            self._corrupt_share_line = "  server %s, SI %s, shnum %d" % \
95                (str(base32.b2a(shares[1][1]), "ascii"),
96                 str(base32.b2a(storage_index), "ascii"),
97                 shares[1][0])
98            debug.corrupt_share(cso)
99        d.addCallback(_clobber_shares)
100
101        d.addCallback(lambda ign: self.do_cli("check", "--verify", self.uri))
102        def _check3(args):
103            (rc, out, err) = args
104            self.assertEqual(len(err), 0, err)
105            self.failUnlessReallyEqual(rc, 0)
106            lines = out.splitlines()
107            summary = [l for l in lines if l.startswith("Summary")][0]
108            self.failUnless("Summary: Unhealthy: 8 shares (enc 3-of-10)"
109                            in summary, summary)
110            self.failUnless(" good-shares: 8 (encoding is 3-of-10)" in lines, out)
111            self.failUnless(" corrupt shares:" in lines, out)
112            self.failUnless(self._corrupt_share_line in lines, out)
113        d.addCallback(_check3)
114
115        d.addCallback(lambda ign: self.do_cli("check", "--verify", "--raw", self.uri))
116        def _check3_raw(args):
117            (rc, out, err) = args
118            self.assertEqual(len(err), 0, err)
119            self.failUnlessReallyEqual(rc, 0)
120            data = json.loads(out)
121            self.failUnlessReallyEqual(data["results"]["healthy"], False)
122            self.failUnlessIn("Unhealthy: 8 shares (enc 3-of-10)", data["summary"])
123            self.failUnlessReallyEqual(data["results"]["count-shares-good"], 8)
124            self.failUnlessReallyEqual(data["results"]["count-corrupt-shares"], 1)
125            self.failUnlessIn("list-corrupt-shares", data["results"])
126        d.addCallback(_check3_raw)
127
128        d.addCallback(lambda ign:
129                      self.do_cli("check", "--verify", "--repair", self.uri))
130        def _check4(args):
131            (rc, out, err) = args
132            self.assertEqual(len(err), 0, err)
133            self.failUnlessReallyEqual(rc, 0)
134            lines = out.splitlines()
135            self.failUnless("Summary: not healthy" in lines, out)
136            self.failUnless(" good-shares: 8 (encoding is 3-of-10)" in lines, out)
137            self.failUnless(" corrupt shares:" in lines, out)
138            self.failUnless(self._corrupt_share_line in lines, out)
139            self.failUnless(" repair successful" in lines, out)
140        d.addCallback(_check4)
141
142        d.addCallback(lambda ign:
143                      self.do_cli("check", "--verify", "--repair", self.uri))
144        def _check5(args):
145            (rc, out, err) = args
146            self.assertEqual(len(err), 0, err)
147            self.failUnlessReallyEqual(rc, 0)
148            lines = out.splitlines()
149            self.failUnless("Summary: healthy" in lines, out)
150            self.failUnless(" good-shares: 10 (encoding is 3-of-10)" in lines, out)
151            self.failIf(" corrupt shares:" in lines, out)
152        d.addCallback(_check5)
153
154        # Testing verbose option
155        d.addCallback(lambda ign: self.do_cli("check", "--verbose", self.uri))
156        def _check6(args):
157            (rc, out, err) = args
158            self.assertEqual(len(err), 0, err)
159            self.failUnlessReallyEqual(rc, 0)
160            lines = out.splitlines()
161            self.failUnless("Summary: Healthy" in lines, out)
162            self.failUnless(" good-shares: 10 (encoding is 3-of-10)" in lines, out)
163        d.addCallback(_check1)
164
165
166        return d
167
168    def test_deep_check(self):
169        self.basedir = "cli/Check/deep_check"
170        self.set_up_grid()
171        c0 = self.g.clients[0]
172        self.uris = {}
173        self.fileurls = {}
174        DATA = b"data" * 100
175        quoted_good = quote_output_u("g\u00F6\u00F6d")
176
177        d = c0.create_dirnode()
178        def _stash_root_and_create_file(n):
179            self.rootnode = n
180            self.rooturi = n.get_uri()
181            return n.add_file(u"g\u00F6\u00F6d", upload.Data(DATA, convergence=b""))
182        d.addCallback(_stash_root_and_create_file)
183        def _stash_uri(fn, which):
184            self.uris[which] = fn.get_uri()
185            return fn
186        d.addCallback(_stash_uri, u"g\u00F6\u00F6d")
187        d.addCallback(lambda ign:
188                      self.rootnode.add_file(u"small",
189                                           upload.Data(b"literal",
190                                                        convergence=b"")))
191        d.addCallback(_stash_uri, "small")
192        d.addCallback(lambda ign:
193            c0.create_mutable_file(MutableData(DATA+b"1")))
194        d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
195        d.addCallback(_stash_uri, "mutable")
196
197        d.addCallback(lambda ign: self.do_cli("deep-check", self.rooturi))
198        def _check1(args):
199            (rc, out, err) = args
200            self.assertEqual(len(err), 0, err)
201            self.failUnlessReallyEqual(rc, 0)
202            lines = out.splitlines()
203            self.failUnless("done: 4 objects checked, 4 healthy, 0 unhealthy"
204                            in lines, out)
205        d.addCallback(_check1)
206
207        # root
208        # root/g\u00F6\u00F6d
209        # root/small
210        # root/mutable
211
212        d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
213                                              self.rooturi))
214        def _check2(args):
215            (rc, out, err) = args
216            self.assertEqual(len(err), 0, err)
217            self.failUnlessReallyEqual(rc, 0)
218            out = ensure_text(out)
219            lines = out.splitlines()
220            self.failUnless("'<root>': Healthy" in lines, out)
221            self.failUnless("'small': Healthy (LIT)" in lines, out)
222            self.failUnless((quoted_good + ": Healthy") in lines, out)
223            self.failUnless("'mutable': Healthy" in lines, out)
224            self.failUnless("done: 4 objects checked, 4 healthy, 0 unhealthy"
225                            in lines, out)
226        d.addCallback(_check2)
227
228        d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
229        def _check_stats(args):
230            (rc, out, err) = args
231            self.assertEqual(len(err), 0, err)
232            self.failUnlessReallyEqual(rc, 0)
233            lines = out.splitlines()
234            self.failUnlessIn(" count-immutable-files: 1", lines)
235            self.failUnlessIn("   count-mutable-files: 1", lines)
236            self.failUnlessIn("   count-literal-files: 1", lines)
237            self.failUnlessIn("     count-directories: 1", lines)
238            self.failUnlessIn("  size-immutable-files: 400", lines)
239            self.failUnlessIn("Size Histogram:", lines)
240            self.failUnlessIn("   4-10   : 1    (10 B, 10 B)", lines)
241            self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
242        d.addCallback(_check_stats)
243
244        def _clobber_shares(ignored):
245            shares = self.find_uri_shares(self.uris[u"g\u00F6\u00F6d"])
246            self.failUnlessReallyEqual(len(shares), 10)
247            os.unlink(shares[0][2])
248
249            shares = self.find_uri_shares(self.uris["mutable"])
250            cso = debug.CorruptShareOptions()
251            cso.stdout = StringIO()
252            cso.parseOptions([shares[1][2]])
253            storage_index = uri.from_string(self.uris["mutable"]).get_storage_index()
254            self._corrupt_share_line = " corrupt: server %s, SI %s, shnum %d" % \
255                                       (str(base32.b2a(shares[1][1]), "ascii"),
256                                        str(base32.b2a(storage_index), "ascii"),
257                                        shares[1][0])
258            debug.corrupt_share(cso)
259        d.addCallback(_clobber_shares)
260
261        # root
262        # root/g\u00F6\u00F6d  [9 shares]
263        # root/small
264        # root/mutable [1 corrupt share]
265
266        d.addCallback(lambda ign:
267                      self.do_cli("deep-check", "--verbose", self.rooturi))
268        def _check3(args):
269            (rc, out, err) = args
270            self.assertEqual(len(err), 0, err)
271            self.failUnlessReallyEqual(rc, 0)
272            out = ensure_text(out)
273            lines = out.splitlines()
274            self.failUnless("'<root>': Healthy" in lines, out)
275            self.failUnless("'small': Healthy (LIT)" in lines, out)
276            self.failUnless("'mutable': Healthy" in lines, out) # needs verifier
277            self.failUnless((quoted_good + ": Not Healthy: 9 shares (enc 3-of-10)") in lines, out)
278            self.failIf(self._corrupt_share_line in lines, out)
279            self.failUnless("done: 4 objects checked, 3 healthy, 1 unhealthy"
280                            in lines, out)
281        d.addCallback(_check3)
282
283        d.addCallback(lambda ign:
284                      self.do_cli("deep-check", "--verbose", "--verify",
285                                  self.rooturi))
286        def _check4(args):
287            (rc, out, err) = args
288            self.assertEqual(len(err), 0, err)
289            self.failUnlessReallyEqual(rc, 0)
290            out = ensure_text(out)
291            lines = out.splitlines()
292            self.failUnless("'<root>': Healthy" in lines, out)
293            self.failUnless("'small': Healthy (LIT)" in lines, out)
294            mutable = [l for l in lines if l.startswith("'mutable'")][0]
295            self.failUnless(mutable.startswith("'mutable': Unhealthy: 9 shares (enc 3-of-10)"),
296                            mutable)
297            self.failUnless(self._corrupt_share_line in lines, out)
298            self.failUnless((quoted_good + ": Not Healthy: 9 shares (enc 3-of-10)") in lines, out)
299            self.failUnless("done: 4 objects checked, 2 healthy, 2 unhealthy"
300                            in lines, out)
301        d.addCallback(_check4)
302
303        d.addCallback(lambda ign:
304                      self.do_cli("deep-check", "--raw",
305                                  self.rooturi))
306        def _check5(args):
307            (rc, out, err) = args
308            self.assertEqual(len(err), 0, err)
309            self.failUnlessReallyEqual(rc, 0)
310            lines = out.splitlines()
311            units = [json.loads(line) for line in lines]
312            # root, small, g\u00F6\u00F6d, mutable,  stats
313            self.failUnlessReallyEqual(len(units), 4+1)
314        d.addCallback(_check5)
315
316        d.addCallback(lambda ign:
317                      self.do_cli("deep-check",
318                                  "--verbose", "--verify", "--repair",
319                                  self.rooturi))
320        def _check6(args):
321            (rc, out, err) = args
322            self.assertEqual(len(err), 0, err)
323            self.failUnlessReallyEqual(rc, 0)
324            out = ensure_text(out)
325            lines = out.splitlines()
326            self.failUnless("'<root>': healthy" in lines, out)
327            self.failUnless("'small': healthy" in lines, out)
328            self.failUnless("'mutable': not healthy" in lines, out)
329            self.failUnless(self._corrupt_share_line in lines, out)
330            self.failUnless((quoted_good + ": not healthy") in lines, out)
331            self.failUnless("done: 4 objects checked" in lines, out)
332            self.failUnless(" pre-repair: 2 healthy, 2 unhealthy" in lines, out)
333            self.failUnless(" 2 repairs attempted, 2 successful, 0 failed"
334                            in lines, out)
335            self.failUnless(" post-repair: 4 healthy, 0 unhealthy" in lines,out)
336        d.addCallback(_check6)
337
338        # now add a subdir, and a file below that, then make the subdir
339        # unrecoverable
340
341        d.addCallback(lambda ign: self.rootnode.create_subdirectory(u"subdir"))
342        d.addCallback(_stash_uri, "subdir")
343        d.addCallback(lambda fn:
344                      fn.add_file(u"subfile", upload.Data(DATA+b"2", b"")))
345        d.addCallback(lambda ign:
346                      self.delete_shares_numbered(self.uris["subdir"],
347                                                  list(range(10))))
348
349        # root
350        # rootg\u00F6\u00F6d/
351        # root/small
352        # root/mutable
353        # root/subdir [unrecoverable: 0 shares]
354        # root/subfile
355
356        d.addCallback(lambda ign: self.do_cli("manifest", self.rooturi))
357        def _manifest_failed(args):
358            (rc, out, err) = args
359            self.failIfEqual(rc, 0)
360            self.failUnlessIn("ERROR: UnrecoverableFileError", err)
361            # the fatal directory should still show up, as the last line
362            self.failUnlessIn(" subdir\n", ensure_text(out))
363        d.addCallback(_manifest_failed)
364
365        d.addCallback(lambda ign: self.do_cli("deep-check", self.rooturi))
366        def _deep_check_failed(args):
367            (rc, out, err) = args
368            self.failIfEqual(rc, 0)
369            self.failUnlessIn("ERROR: UnrecoverableFileError", err)
370            # we want to make sure that the error indication is the last
371            # thing that gets emitted
372            self.failIf("done:" in out, out)
373        d.addCallback(_deep_check_failed)
374
375        # this test is disabled until the deep-repair response to an
376        # unrepairable directory is fixed. The failure-to-repair should not
377        # throw an exception, but the failure-to-traverse that follows
378        # should throw UnrecoverableFileError.
379
380        #d.addCallback(lambda ign:
381        #              self.do_cli("deep-check", "--repair", self.rooturi))
382        #def _deep_check_repair_failed((rc, out, err)):
383        #    self.failIfEqual(rc, 0)
384        #    print(err)
385        #    self.failUnlessIn("ERROR: UnrecoverableFileError", err)
386        #    self.failIf("done:" in out, out)
387        #d.addCallback(_deep_check_repair_failed)
388
389        return d
390
391    def test_check_without_alias(self):
392        # 'tahoe check' should output a sensible error message if it needs to
393        # find the default alias and can't
394        self.basedir = "cli/Check/check_without_alias"
395        self.set_up_grid(oneshare=True)
396        d = self.do_cli("check")
397        def _check(args):
398            (rc, out, err) = args
399            self.failUnlessReallyEqual(rc, 1)
400            self.failUnlessIn("error:", err)
401            self.assertEqual(len(out), 0, out)
402        d.addCallback(_check)
403        d.addCallback(lambda ign: self.do_cli("deep-check"))
404        d.addCallback(_check)
405        return d
406
407    def test_check_with_nonexistent_alias(self):
408        # 'tahoe check' should output a sensible error message if it needs to
409        # find an alias and can't.
410        self.basedir = "cli/Check/check_with_nonexistent_alias"
411        self.set_up_grid(oneshare=True)
412        d = self.do_cli("check", "nonexistent:")
413        def _check(args):
414            (rc, out, err) = args
415            self.failUnlessReallyEqual(rc, 1)
416            self.failUnlessIn("error:", err)
417            self.failUnlessIn("nonexistent", err)
418            self.assertEqual(len(out), 0, out)
419        d.addCallback(_check)
420        return d
421
422    def test_check_with_multiple_aliases(self):
423        self.basedir = "cli/Check/check_with_multiple_aliases"
424        self.set_up_grid(oneshare=True)
425        self.uriList = []
426        c0 = self.g.clients[0]
427        d = c0.create_dirnode()
428        def _stash_uri(n):
429            self.uriList.append(n.get_uri())
430        d.addCallback(_stash_uri)
431        d.addCallback(lambda _: c0.create_dirnode())
432        d.addCallback(_stash_uri)
433
434        d.addCallback(lambda ign: self.do_cli("check", self.uriList[0], self.uriList[1]))
435        def _check(args):
436            (rc, out, err) = args
437            self.failUnlessReallyEqual(rc, 0)
438            self.assertEqual(len(err), 0, err)
439            #Ensure healthy appears for each uri
440            self.failUnlessIn("Healthy", out[:len(out)//2])
441            self.failUnlessIn("Healthy", out[len(out)//2:])
442        d.addCallback(_check)
443
444        d.addCallback(lambda ign: self.do_cli("check", self.uriList[0], "nonexistent:"))
445        def _check2(args):
446            (rc, out, err) = args
447            self.failUnlessReallyEqual(rc, 1)
448            self.failUnlessIn("Healthy", out)
449            self.failUnlessIn("error:", err)
450            self.failUnlessIn("nonexistent", err)
451        d.addCallback(_check2)
452
453        return d
Note: See TracBrowser for help on using the repository browser.