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