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

Last change on this file was 53084f7, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-27T23:49:07Z

remove more Python2 compatibility

  • Property mode set to 100644
File size: 18.6 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        return d
155
156    def test_deep_check(self):
157        self.basedir = "cli/Check/deep_check"
158        self.set_up_grid()
159        c0 = self.g.clients[0]
160        self.uris = {}
161        self.fileurls = {}
162        DATA = b"data" * 100
163        quoted_good = quote_output_u("g\u00F6\u00F6d")
164
165        d = c0.create_dirnode()
166        def _stash_root_and_create_file(n):
167            self.rootnode = n
168            self.rooturi = n.get_uri()
169            return n.add_file(u"g\u00F6\u00F6d", upload.Data(DATA, convergence=b""))
170        d.addCallback(_stash_root_and_create_file)
171        def _stash_uri(fn, which):
172            self.uris[which] = fn.get_uri()
173            return fn
174        d.addCallback(_stash_uri, u"g\u00F6\u00F6d")
175        d.addCallback(lambda ign:
176                      self.rootnode.add_file(u"small",
177                                           upload.Data(b"literal",
178                                                        convergence=b"")))
179        d.addCallback(_stash_uri, "small")
180        d.addCallback(lambda ign:
181            c0.create_mutable_file(MutableData(DATA+b"1")))
182        d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
183        d.addCallback(_stash_uri, "mutable")
184
185        d.addCallback(lambda ign: self.do_cli("deep-check", self.rooturi))
186        def _check1(args):
187            (rc, out, err) = args
188            self.assertEqual(len(err), 0, err)
189            self.failUnlessReallyEqual(rc, 0)
190            lines = out.splitlines()
191            self.failUnless("done: 4 objects checked, 4 healthy, 0 unhealthy"
192                            in lines, out)
193        d.addCallback(_check1)
194
195        # root
196        # root/g\u00F6\u00F6d
197        # root/small
198        # root/mutable
199
200        d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
201                                              self.rooturi))
202        def _check2(args):
203            (rc, out, err) = args
204            self.assertEqual(len(err), 0, err)
205            self.failUnlessReallyEqual(rc, 0)
206            out = ensure_text(out)
207            lines = out.splitlines()
208            self.failUnless("'<root>': Healthy" in lines, out)
209            self.failUnless("'small': Healthy (LIT)" in lines, out)
210            self.failUnless((quoted_good + ": Healthy") in lines, out)
211            self.failUnless("'mutable': Healthy" in lines, out)
212            self.failUnless("done: 4 objects checked, 4 healthy, 0 unhealthy"
213                            in lines, out)
214        d.addCallback(_check2)
215
216        d.addCallback(lambda ign: self.do_cli("stats", self.rooturi))
217        def _check_stats(args):
218            (rc, out, err) = args
219            self.assertEqual(len(err), 0, err)
220            self.failUnlessReallyEqual(rc, 0)
221            lines = out.splitlines()
222            self.failUnlessIn(" count-immutable-files: 1", lines)
223            self.failUnlessIn("   count-mutable-files: 1", lines)
224            self.failUnlessIn("   count-literal-files: 1", lines)
225            self.failUnlessIn("     count-directories: 1", lines)
226            self.failUnlessIn("  size-immutable-files: 400", lines)
227            self.failUnlessIn("Size Histogram:", lines)
228            self.failUnlessIn("   4-10   : 1    (10 B, 10 B)", lines)
229            self.failUnlessIn(" 317-1000 : 1    (1000 B, 1000 B)", lines)
230        d.addCallback(_check_stats)
231
232        def _clobber_shares(ignored):
233            shares = self.find_uri_shares(self.uris[u"g\u00F6\u00F6d"])
234            self.failUnlessReallyEqual(len(shares), 10)
235            os.unlink(shares[0][2])
236
237            shares = self.find_uri_shares(self.uris["mutable"])
238            cso = debug.CorruptShareOptions()
239            cso.stdout = StringIO()
240            cso.parseOptions([shares[1][2]])
241            storage_index = uri.from_string(self.uris["mutable"]).get_storage_index()
242            self._corrupt_share_line = " corrupt: server %s, SI %s, shnum %d" % \
243                                       (str(base32.b2a(shares[1][1]), "ascii"),
244                                        str(base32.b2a(storage_index), "ascii"),
245                                        shares[1][0])
246            debug.corrupt_share(cso)
247        d.addCallback(_clobber_shares)
248
249        # root
250        # root/g\u00F6\u00F6d  [9 shares]
251        # root/small
252        # root/mutable [1 corrupt share]
253
254        d.addCallback(lambda ign:
255                      self.do_cli("deep-check", "--verbose", self.rooturi))
256        def _check3(args):
257            (rc, out, err) = args
258            self.assertEqual(len(err), 0, err)
259            self.failUnlessReallyEqual(rc, 0)
260            out = ensure_text(out)
261            lines = out.splitlines()
262            self.failUnless("'<root>': Healthy" in lines, out)
263            self.failUnless("'small': Healthy (LIT)" in lines, out)
264            self.failUnless("'mutable': Healthy" in lines, out) # needs verifier
265            self.failUnless((quoted_good + ": Not Healthy: 9 shares (enc 3-of-10)") in lines, out)
266            self.failIf(self._corrupt_share_line in lines, out)
267            self.failUnless("done: 4 objects checked, 3 healthy, 1 unhealthy"
268                            in lines, out)
269        d.addCallback(_check3)
270
271        d.addCallback(lambda ign:
272                      self.do_cli("deep-check", "--verbose", "--verify",
273                                  self.rooturi))
274        def _check4(args):
275            (rc, out, err) = args
276            self.assertEqual(len(err), 0, err)
277            self.failUnlessReallyEqual(rc, 0)
278            out = ensure_text(out)
279            lines = out.splitlines()
280            self.failUnless("'<root>': Healthy" in lines, out)
281            self.failUnless("'small': Healthy (LIT)" in lines, out)
282            mutable = [l for l in lines if l.startswith("'mutable'")][0]
283            self.failUnless(mutable.startswith("'mutable': Unhealthy: 9 shares (enc 3-of-10)"),
284                            mutable)
285            self.failUnless(self._corrupt_share_line in lines, out)
286            self.failUnless((quoted_good + ": Not Healthy: 9 shares (enc 3-of-10)") in lines, out)
287            self.failUnless("done: 4 objects checked, 2 healthy, 2 unhealthy"
288                            in lines, out)
289        d.addCallback(_check4)
290
291        d.addCallback(lambda ign:
292                      self.do_cli("deep-check", "--raw",
293                                  self.rooturi))
294        def _check5(args):
295            (rc, out, err) = args
296            self.assertEqual(len(err), 0, err)
297            self.failUnlessReallyEqual(rc, 0)
298            lines = out.splitlines()
299            units = [json.loads(line) for line in lines]
300            # root, small, g\u00F6\u00F6d, mutable,  stats
301            self.failUnlessReallyEqual(len(units), 4+1)
302        d.addCallback(_check5)
303
304        d.addCallback(lambda ign:
305                      self.do_cli("deep-check",
306                                  "--verbose", "--verify", "--repair",
307                                  self.rooturi))
308        def _check6(args):
309            (rc, out, err) = args
310            self.assertEqual(len(err), 0, err)
311            self.failUnlessReallyEqual(rc, 0)
312            out = ensure_text(out)
313            lines = out.splitlines()
314            self.failUnless("'<root>': healthy" in lines, out)
315            self.failUnless("'small': healthy" in lines, out)
316            self.failUnless("'mutable': not healthy" in lines, out)
317            self.failUnless(self._corrupt_share_line in lines, out)
318            self.failUnless((quoted_good + ": not healthy") in lines, out)
319            self.failUnless("done: 4 objects checked" in lines, out)
320            self.failUnless(" pre-repair: 2 healthy, 2 unhealthy" in lines, out)
321            self.failUnless(" 2 repairs attempted, 2 successful, 0 failed"
322                            in lines, out)
323            self.failUnless(" post-repair: 4 healthy, 0 unhealthy" in lines,out)
324        d.addCallback(_check6)
325
326        # now add a subdir, and a file below that, then make the subdir
327        # unrecoverable
328
329        d.addCallback(lambda ign: self.rootnode.create_subdirectory(u"subdir"))
330        d.addCallback(_stash_uri, "subdir")
331        d.addCallback(lambda fn:
332                      fn.add_file(u"subfile", upload.Data(DATA+b"2", b"")))
333        d.addCallback(lambda ign:
334                      self.delete_shares_numbered(self.uris["subdir"],
335                                                  list(range(10))))
336
337        # root
338        # rootg\u00F6\u00F6d/
339        # root/small
340        # root/mutable
341        # root/subdir [unrecoverable: 0 shares]
342        # root/subfile
343
344        d.addCallback(lambda ign: self.do_cli("manifest", self.rooturi))
345        def _manifest_failed(args):
346            (rc, out, err) = args
347            self.failIfEqual(rc, 0)
348            self.failUnlessIn("ERROR: UnrecoverableFileError", err)
349            # the fatal directory should still show up, as the last line
350            self.failUnlessIn(" subdir\n", ensure_text(out))
351        d.addCallback(_manifest_failed)
352
353        d.addCallback(lambda ign: self.do_cli("deep-check", self.rooturi))
354        def _deep_check_failed(args):
355            (rc, out, err) = args
356            self.failIfEqual(rc, 0)
357            self.failUnlessIn("ERROR: UnrecoverableFileError", err)
358            # we want to make sure that the error indication is the last
359            # thing that gets emitted
360            self.failIf("done:" in out, out)
361        d.addCallback(_deep_check_failed)
362
363        # this test is disabled until the deep-repair response to an
364        # unrepairable directory is fixed. The failure-to-repair should not
365        # throw an exception, but the failure-to-traverse that follows
366        # should throw UnrecoverableFileError.
367
368        #d.addCallback(lambda ign:
369        #              self.do_cli("deep-check", "--repair", self.rooturi))
370        #def _deep_check_repair_failed((rc, out, err)):
371        #    self.failIfEqual(rc, 0)
372        #    print(err)
373        #    self.failUnlessIn("ERROR: UnrecoverableFileError", err)
374        #    self.failIf("done:" in out, out)
375        #d.addCallback(_deep_check_repair_failed)
376
377        return d
378
379    def test_check_without_alias(self):
380        # 'tahoe check' should output a sensible error message if it needs to
381        # find the default alias and can't
382        self.basedir = "cli/Check/check_without_alias"
383        self.set_up_grid(oneshare=True)
384        d = self.do_cli("check")
385        def _check(args):
386            (rc, out, err) = args
387            self.failUnlessReallyEqual(rc, 1)
388            self.failUnlessIn("error:", err)
389            self.assertEqual(len(out), 0, out)
390        d.addCallback(_check)
391        d.addCallback(lambda ign: self.do_cli("deep-check"))
392        d.addCallback(_check)
393        return d
394
395    def test_check_with_nonexistent_alias(self):
396        # 'tahoe check' should output a sensible error message if it needs to
397        # find an alias and can't.
398        self.basedir = "cli/Check/check_with_nonexistent_alias"
399        self.set_up_grid(oneshare=True)
400        d = self.do_cli("check", "nonexistent:")
401        def _check(args):
402            (rc, out, err) = args
403            self.failUnlessReallyEqual(rc, 1)
404            self.failUnlessIn("error:", err)
405            self.failUnlessIn("nonexistent", err)
406            self.assertEqual(len(out), 0, out)
407        d.addCallback(_check)
408        return d
409
410    def test_check_with_multiple_aliases(self):
411        self.basedir = "cli/Check/check_with_multiple_aliases"
412        self.set_up_grid(oneshare=True)
413        self.uriList = []
414        c0 = self.g.clients[0]
415        d = c0.create_dirnode()
416        def _stash_uri(n):
417            self.uriList.append(n.get_uri())
418        d.addCallback(_stash_uri)
419        d.addCallback(lambda _: c0.create_dirnode())
420        d.addCallback(_stash_uri)
421
422        d.addCallback(lambda ign: self.do_cli("check", self.uriList[0], self.uriList[1]))
423        def _check(args):
424            (rc, out, err) = args
425            self.failUnlessReallyEqual(rc, 0)
426            self.assertEqual(len(err), 0, err)
427            #Ensure healthy appears for each uri
428            self.failUnlessIn("Healthy", out[:len(out)//2])
429            self.failUnlessIn("Healthy", out[len(out)//2:])
430        d.addCallback(_check)
431
432        d.addCallback(lambda ign: self.do_cli("check", self.uriList[0], "nonexistent:"))
433        def _check2(args):
434            (rc, out, err) = args
435            self.failUnlessReallyEqual(rc, 1)
436            self.failUnlessIn("Healthy", out)
437            self.failUnlessIn("error:", err)
438            self.failUnlessIn("nonexistent", err)
439        d.addCallback(_check2)
440
441        return d
Note: See TracBrowser for help on using the repository browser.