]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/tahoe_check.py
remove stray trailing whitespace
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / tahoe_check.py
1
2 import urllib
3 import simplejson
4 from twisted.protocols.basic import LineOnlyReceiver
5 from allmydata.scripts.common import get_alias, DEFAULT_ALIAS, escape_path, \
6                                      UnknownAliasError
7 from allmydata.scripts.common_http import do_http, format_http_error
8 from allmydata.util.encodingutil import quote_output, quote_path
9
10 class Checker:
11     pass
12
13 def _quote_serverid_index_share(serverid, storage_index, sharenum):
14     return "server %s, SI %s, shnum %r" % (quote_output(serverid, quotemarks=False),
15                                            quote_output(storage_index, quotemarks=False),
16                                            sharenum)
17
18 def check_location(options, where):
19     stdout = options.stdout
20     stderr = options.stderr
21     nodeurl = options['node-url']
22     if not nodeurl.endswith("/"):
23         nodeurl += "/"
24     try:
25         rootcap, path = get_alias(options.aliases, where, DEFAULT_ALIAS)
26     except UnknownAliasError, e:
27         e.display(stderr)
28         return 1
29     if path == '/':
30         path = ''
31     url = nodeurl + "uri/%s" % urllib.quote(rootcap)
32     if path:
33         url += "/" + escape_path(path)
34     # todo: should it end with a slash?
35     url += "?t=check&output=JSON"
36     if options["verify"]:
37         url += "&verify=true"
38     if options["repair"]:
39         url += "&repair=true"
40     if options["add-lease"]:
41         url += "&add-lease=true"
42
43     resp = do_http("POST", url)
44     if resp.status != 200:
45         print >>stderr, format_http_error("ERROR", resp)
46         return 1
47     jdata = resp.read()
48     if options.get("raw"):
49         stdout.write(jdata)
50         stdout.write("\n")
51         return 0
52     data = simplejson.loads(jdata)
53
54     if options["repair"]:
55         # show repair status
56         if data["pre-repair-results"]["results"]["healthy"]:
57             summary = "healthy"
58         else:
59             summary = "not healthy"
60         stdout.write("Summary: %s\n" % summary)
61         cr = data["pre-repair-results"]["results"]
62         stdout.write(" storage index: %s\n" % quote_output(data["storage-index"], quotemarks=False))
63         stdout.write(" good-shares: %r (encoding is %r-of-%r)\n"
64                      % (cr["count-shares-good"],
65                         cr["count-shares-needed"],
66                         cr["count-shares-expected"]))
67         stdout.write(" wrong-shares: %r\n" % cr["count-wrong-shares"])
68         corrupt = cr["list-corrupt-shares"]
69         if corrupt:
70             stdout.write(" corrupt shares:\n")
71             for (serverid, storage_index, sharenum) in corrupt:
72                 stdout.write("  %s\n" % _quote_serverid_index_share(serverid, storage_index, sharenum))
73         if data["repair-attempted"]:
74             if data["repair-successful"]:
75                 stdout.write(" repair successful\n")
76             else:
77                 stdout.write(" repair failed\n")
78     else:
79         # LIT files and directories do not have a "summary" field.
80         summary = data.get("summary", "Healthy (LIT)")
81         stdout.write("Summary: %s\n" % quote_output(summary, quotemarks=False))
82         cr = data["results"]
83         stdout.write(" storage index: %s\n" % quote_output(data["storage-index"], quotemarks=False))
84
85         if all([field in cr for field in ("count-shares-good", "count-shares-needed",
86                                           "count-shares-expected", "count-wrong-shares")]):
87             stdout.write(" good-shares: %r (encoding is %r-of-%r)\n"
88                          % (cr["count-shares-good"],
89                             cr["count-shares-needed"],
90                             cr["count-shares-expected"]))
91             stdout.write(" wrong-shares: %r\n" % cr["count-wrong-shares"])
92
93         corrupt = cr.get("list-corrupt-shares", [])
94         if corrupt:
95             stdout.write(" corrupt shares:\n")
96             for (serverid, storage_index, sharenum) in corrupt:
97                 stdout.write("  %s\n" % _quote_serverid_index_share(serverid, storage_index, sharenum))
98
99     return 0;
100
101 def check(options):
102     if len(options.locations) == 0:
103         errno = check_location(options, unicode())
104         if errno != 0:
105             return errno
106         return 0
107     for location in options.locations:
108         errno = check_location(options, location)
109         if errno != 0:
110             return errno
111     return 0
112
113 class FakeTransport:
114     disconnecting = False
115
116 class DeepCheckOutput(LineOnlyReceiver):
117     delimiter = "\n"
118     def __init__(self, streamer, options):
119         self.streamer = streamer
120         self.transport = FakeTransport()
121
122         self.verbose = bool(options["verbose"])
123         self.stdout = options.stdout
124         self.stderr = options.stderr
125         self.num_objects = 0
126         self.files_healthy = 0
127         self.files_unhealthy = 0
128         self.in_error = False
129
130     def lineReceived(self, line):
131         if self.in_error:
132             print >>self.stderr, quote_output(line, quotemarks=False)
133             return
134         if line.startswith("ERROR:"):
135             self.in_error = True
136             self.streamer.rc = 1
137             print >>self.stderr, quote_output(line, quotemarks=False)
138             return
139
140         d = simplejson.loads(line)
141         stdout = self.stdout
142         if d["type"] not in ("file", "directory"):
143             return
144         self.num_objects += 1
145         # non-verbose means print a progress marker every 100 files
146         if self.num_objects % 100 == 0:
147             print >>stdout, "%d objects checked.." % self.num_objects
148         cr = d["check-results"]
149         if cr["results"]["healthy"]:
150             self.files_healthy += 1
151         else:
152             self.files_unhealthy += 1
153         if self.verbose:
154             # verbose means also print one line per file
155             path = d["path"]
156             if not path:
157                 path = ["<root>"]
158
159             # LIT files and directories do not have a "summary" field.
160             summary = cr.get("summary", "Healthy (LIT)")
161             print >>stdout, "%s: %s" % (quote_path(path), quote_output(summary, quotemarks=False))
162
163         # always print out corrupt shares
164         for shareloc in cr["results"].get("list-corrupt-shares", []):
165             (serverid, storage_index, sharenum) = shareloc
166             print >>stdout, " corrupt: %s" % _quote_serverid_index_share(serverid, storage_index, sharenum)
167
168     def done(self):
169         if self.in_error:
170             return
171         stdout = self.stdout
172         print >>stdout, "done: %d objects checked, %d healthy, %d unhealthy" \
173               % (self.num_objects, self.files_healthy, self.files_unhealthy)
174
175 class DeepCheckAndRepairOutput(LineOnlyReceiver):
176     delimiter = "\n"
177     def __init__(self, streamer, options):
178         self.streamer = streamer
179         self.transport = FakeTransport()
180
181         self.verbose = bool(options["verbose"])
182         self.stdout = options.stdout
183         self.stderr = options.stderr
184         self.num_objects = 0
185         self.pre_repair_files_healthy = 0
186         self.pre_repair_files_unhealthy = 0
187         self.repairs_attempted = 0
188         self.repairs_successful = 0
189         self.post_repair_files_healthy = 0
190         self.post_repair_files_unhealthy = 0
191         self.in_error = False
192
193     def lineReceived(self, line):
194         if self.in_error:
195             print >>self.stderr, quote_output(line, quotemarks=False)
196             return
197         if line.startswith("ERROR:"):
198             self.in_error = True
199             self.streamer.rc = 1
200             print >>self.stderr, quote_output(line, quotemarks=False)
201             return
202
203         d = simplejson.loads(line)
204         stdout = self.stdout
205         if d["type"] not in ("file", "directory"):
206             return
207         self.num_objects += 1
208         # non-verbose means print a progress marker every 100 files
209         if self.num_objects % 100 == 0:
210             print >>stdout, "%d objects checked.." % self.num_objects
211         crr = d["check-and-repair-results"]
212         if d["storage-index"]:
213             if crr["pre-repair-results"]["results"]["healthy"]:
214                 was_healthy = True
215                 self.pre_repair_files_healthy += 1
216             else:
217                 was_healthy = False
218                 self.pre_repair_files_unhealthy += 1
219             if crr["post-repair-results"]["results"]["healthy"]:
220                 self.post_repair_files_healthy += 1
221             else:
222                 self.post_repair_files_unhealthy += 1
223         else:
224             # LIT file
225             was_healthy = True
226             self.pre_repair_files_healthy += 1
227             self.post_repair_files_healthy += 1
228         if crr["repair-attempted"]:
229             self.repairs_attempted += 1
230             if crr["repair-successful"]:
231                 self.repairs_successful += 1
232         if self.verbose:
233             # verbose means also print one line per file
234             path = d["path"]
235             if not path:
236                 path = ["<root>"]
237             # we don't seem to have a summary available, so build one
238             if was_healthy:
239                 summary = "healthy"
240             else:
241                 summary = "not healthy"
242             print >>stdout, "%s: %s" % (quote_path(path), summary)
243
244         # always print out corrupt shares
245         prr = crr.get("pre-repair-results", {})
246         for shareloc in prr.get("results", {}).get("list-corrupt-shares", []):
247             (serverid, storage_index, sharenum) = shareloc
248             print >>stdout, " corrupt: %s" % _quote_serverid_index_share(serverid, storage_index, sharenum)
249
250         # always print out repairs
251         if crr["repair-attempted"]:
252             if crr["repair-successful"]:
253                 print >>stdout, " repair successful"
254             else:
255                 print >>stdout, " repair failed"
256
257     def done(self):
258         if self.in_error:
259             return
260         stdout = self.stdout
261         print >>stdout, "done: %d objects checked" % self.num_objects
262         print >>stdout, " pre-repair: %d healthy, %d unhealthy" \
263               % (self.pre_repair_files_healthy,
264                  self.pre_repair_files_unhealthy)
265         print >>stdout, " %d repairs attempted, %d successful, %d failed" \
266               % (self.repairs_attempted,
267                  self.repairs_successful,
268                  (self.repairs_attempted - self.repairs_successful))
269         print >>stdout, " post-repair: %d healthy, %d unhealthy" \
270               % (self.post_repair_files_healthy,
271                  self.post_repair_files_unhealthy)
272
273 class DeepCheckStreamer(LineOnlyReceiver):
274
275     def deepcheck_location(self, options, where):
276         stdout = options.stdout
277         stderr = options.stderr
278         self.rc = 0
279         self.options = options
280         nodeurl = options['node-url']
281         if not nodeurl.endswith("/"):
282             nodeurl += "/"
283         self.nodeurl = nodeurl
284
285         try:
286             rootcap, path = get_alias(options.aliases, where, DEFAULT_ALIAS)
287         except UnknownAliasError, e:
288             e.display(stderr)
289             return 1
290         if path == '/':
291             path = ''
292         url = nodeurl + "uri/%s" % urllib.quote(rootcap)
293         if path:
294             url += "/" + escape_path(path)
295         # todo: should it end with a slash?
296         url += "?t=stream-deep-check"
297         if options["verify"]:
298             url += "&verify=true"
299         if options["repair"]:
300             url += "&repair=true"
301             output = DeepCheckAndRepairOutput(self, options)
302         else:
303             output = DeepCheckOutput(self, options)
304         if options["add-lease"]:
305             url += "&add-lease=true"
306         resp = do_http("POST", url)
307         if resp.status not in (200, 302):
308             print >>stderr, format_http_error("ERROR", resp)
309             return 1
310
311         # use Twisted to split this into lines
312         while True:
313             chunk = resp.read(100)
314             if not chunk:
315                 break
316             if self.options["raw"]:
317                 stdout.write(chunk)
318             else:
319                 output.dataReceived(chunk)
320         if not self.options["raw"]:
321             output.done()
322         return 0
323
324
325     def run(self, options):
326         if len(options.locations) == 0:
327             errno = self.deepcheck_location(options, unicode())
328             if errno != 0:
329                 return errno
330             return 0
331         for location in options.locations:
332             errno = self.deepcheck_location(options, location)
333             if errno != 0:
334                 return errno
335         return self.rc
336
337 def deepcheck(options):
338     return DeepCheckStreamer().run(options)