4 from nevow import rend, inevow, tags as T
5 from twisted.web import http, html
6 from allmydata.web.common import getxmlfile, get_arg, get_root, WebError
7 from allmydata.web.operations import ReloadMixin
8 from allmydata.interfaces import ICheckAndRepairResults, ICheckResults
9 from allmydata.util import base32, idlib
11 def json_check_counts(d):
13 r["count-shares-good"] = d["count-shares-good"]
14 r["count-shares-needed"] = d["count-shares-needed"]
15 r["count-shares-expected"] = d["count-shares-expected"]
16 r["count-good-share-hosts"] = d["count-good-share-hosts"]
17 r["count-corrupt-shares"] = d["count-corrupt-shares"]
18 r["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid),
19 base32.b2a(si), shnum)
20 for (serverid, si, shnum)
21 in d["list-corrupt-shares"] ]
22 r["servers-responding"] = [idlib.nodeid_b2a(serverid)
23 for serverid in d["servers-responding"]]
25 for (shareid, serverids) in d["sharemap"].items():
26 sharemap[shareid] = [idlib.nodeid_b2a(serverid)
27 for serverid in serverids]
28 r["sharemap"] = sharemap
30 r["count-wrong-shares"] = d["count-wrong-shares"]
31 r["count-recoverable-versions"] = d["count-recoverable-versions"]
32 r["count-unrecoverable-versions"] = d["count-unrecoverable-versions"]
36 def json_check_results(r):
39 data = {"storage-index": "",
40 "results": {"healthy": True},
44 data["storage-index"] = r.get_storage_index_string()
45 data["summary"] = r.get_summary()
46 data["results"] = json_check_counts(r.get_data())
47 data["results"]["needs-rebalancing"] = r.needs_rebalancing()
48 data["results"]["healthy"] = r.is_healthy()
49 data["results"]["recoverable"] = r.is_recoverable()
52 def json_check_and_repair_results(r):
55 data = {"storage-index": "",
56 "repair-attempted": False,
60 data["storage-index"] = r.get_storage_index_string()
61 data["repair-attempted"] = r.get_repair_attempted()
62 data["repair-successful"] = r.get_repair_successful()
63 pre = r.get_pre_repair_results()
64 data["pre-repair-results"] = json_check_results(pre)
65 post = r.get_post_repair_results()
66 data["post-repair-results"] = json_check_results(post)
70 # self.client must point to the Client, so we can get nicknames and
71 # determine the permuted peer order
73 def _join_pathstring(self, path):
75 pathstring = "/".join(self._html(path))
80 def _render_results(self, ctx, cr):
81 assert ICheckResults(cr)
83 sb = c.get_storage_broker()
87 r.append(T.li[name + ": ", value])
89 add("Report", T.pre["\n".join(self._html(cr.get_report()))])
91 "need %d-of-%d, have %d" % (data["count-shares-needed"],
92 data["count-shares-expected"],
93 data["count-shares-good"]))
94 add("Hosts with good shares", data["count-good-share-hosts"])
96 if data["list-corrupt-shares"]:
98 for (serverid, si, shnum) in data["list-corrupt-shares"]:
99 nickname = sb.get_nickname_for_serverid(serverid)
100 badsharemap.append(T.tr[T.td["sh#%d" % shnum],
101 T.td[T.div(class_="nickname")[nickname],
102 T.div(class_="nodeid")[T.tt[base32.b2a(serverid)]]],
104 add("Corrupt shares", T.table()[
105 T.tr[T.th["Share ID"],
106 T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]]],
109 add("Corrupt shares", "none")
111 add("Wrong Shares", data["count-wrong-shares"])
116 # FIXME: The two tables below contain nickname-and-nodeid table column markup which is duplicated with each other, introducer.xhtml, and deep-check-results.xhtml. All of these (and any other presentations of nickname-and-nodeid) should be combined.
118 for shareid in sorted(data["sharemap"].keys()):
119 serverids = data["sharemap"][shareid]
120 for i,serverid in enumerate(serverids):
121 if serverid not in servers:
122 servers[serverid] = []
123 servers[serverid].append(shareid)
127 nickname = sb.get_nickname_for_serverid(serverid)
128 sharemap.append(T.tr[T.td[shareid_s],
129 T.td[T.div(class_="nickname")[nickname],
130 T.div(class_="nodeid")[T.tt[base32.b2a(serverid)]]]
132 add("Good Shares (sorted in share order)",
133 T.table()[T.tr[T.th["Share ID"], T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]]],
137 add("Recoverable Versions", data["count-recoverable-versions"])
138 add("Unrecoverable Versions", data["count-unrecoverable-versions"])
140 # this table is sorted by permuted order
141 sb = c.get_storage_broker()
142 permuted_peer_ids = [peerid
144 in sb.get_servers(cr.get_storage_index())]
146 num_shares_left = sum([len(shares) for shares in servers.values()])
148 for serverid in permuted_peer_ids:
149 nickname = sb.get_nickname_for_serverid(serverid)
150 shareids = servers.get(serverid, [])
152 shareids_s = [ T.tt[shareid, " "] for shareid in sorted(shareids) ]
153 servermap.append(T.tr[T.td[T.div(class_="nickname")[nickname],
154 T.div(class_="nodeid")[T.tt[base32.b2a(serverid)]]],
157 num_shares_left -= len(shareids)
158 if not num_shares_left:
160 add("Share Balancing (servers in permuted order)",
161 T.table()[T.tr[T.th(class_="nickname-and-peerid")[T.div["Nickname"], T.div(class_="nodeid")["Node ID"]], T.th["Share IDs"]],
167 if isinstance(s, (str, unicode)):
168 return html.escape(s)
169 assert isinstance(s, (list, tuple))
170 return [html.escape(w) for w in s]
172 def want_json(self, ctx):
173 output = get_arg(inevow.IRequest(ctx), "output", "").lower()
174 if output.lower() == "json":
178 def _render_si_link(self, ctx, storage_index):
179 si_s = base32.b2a(storage_index)
181 req = inevow.IRequest(ctx)
182 ophandle = req.prepath[-1]
183 target = "%s/operations/%s/%s" % (get_root(ctx), ophandle, si_s)
184 output = get_arg(ctx, "output")
186 target = target + "?output=%s" % output
187 return T.a(href=target)[si_s]
189 class LiteralCheckResults(rend.Page, ResultsBase):
190 docFactory = getxmlfile("literal-check-results.xhtml")
192 def __init__(self, client):
194 rend.Page.__init__(self, client)
196 def renderHTTP(self, ctx):
197 if self.want_json(ctx):
198 return self.json(ctx)
199 return rend.Page.renderHTTP(self, ctx)
202 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
203 data = json_check_results(None)
204 return simplejson.dumps(data, indent=1) + "\n"
206 def render_return(self, ctx, data):
207 req = inevow.IRequest(ctx)
208 return_to = get_arg(req, "return_to", None)
210 return T.div[T.a(href=return_to)["Return to file."]]
215 def renderHTTP(self, ctx):
216 if self.want_json(ctx):
217 return self.json(ctx)
218 return rend.Page.renderHTTP(self, ctx)
220 def render_storage_index(self, ctx, data):
221 return self.r.get_storage_index_string()
223 def render_return(self, ctx, data):
224 req = inevow.IRequest(ctx)
225 return_to = get_arg(req, "return_to", None)
227 return T.div[T.a(href=return_to)["Return to file/directory."]]
230 class CheckResults(CheckerBase, rend.Page, ResultsBase):
231 docFactory = getxmlfile("check-results.xhtml")
233 def __init__(self, client, results):
235 self.r = ICheckResults(results)
236 rend.Page.__init__(self, results)
239 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
240 data = json_check_results(self.r)
241 return simplejson.dumps(data, indent=1) + "\n"
243 def render_summary(self, ctx, data):
245 if data.is_healthy():
246 results.append("Healthy")
247 elif data.is_recoverable():
248 results.append("Not Healthy!")
250 results.append("Not Recoverable!")
251 results.append(" : ")
252 results.append(self._html(data.get_summary()))
253 return ctx.tag[results]
255 def render_repair(self, ctx, data):
256 if data.is_healthy():
258 repair = T.form(action=".", method="post",
259 enctype="multipart/form-data")[
261 T.input(type="hidden", name="t", value="check"),
262 T.input(type="hidden", name="repair", value="true"),
263 T.input(type="submit", value="Repair"),
265 return "" # repair button disabled until we make it work correctly,
266 # see #622 for details
267 return ctx.tag[repair]
269 def render_results(self, ctx, data):
270 cr = self._render_results(ctx, data)
273 class CheckAndRepairResults(CheckerBase, rend.Page, ResultsBase):
274 docFactory = getxmlfile("check-and-repair-results.xhtml")
276 def __init__(self, client, results):
280 self.r = ICheckAndRepairResults(results)
281 rend.Page.__init__(self, results)
284 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
285 data = json_check_and_repair_results(self.r)
286 return simplejson.dumps(data, indent=1) + "\n"
288 def render_summary(self, ctx, data):
289 cr = data.get_post_repair_results()
292 results.append("Healthy")
293 elif cr.is_recoverable():
294 results.append("Not Healthy!")
296 results.append("Not Recoverable!")
297 results.append(" : ")
298 results.append(self._html(cr.get_summary()))
299 return ctx.tag[results]
301 def render_repair_results(self, ctx, data):
302 if data.get_repair_attempted():
303 if data.get_repair_successful():
304 return ctx.tag["Repair successful"]
306 return ctx.tag["Repair unsuccessful"]
307 return ctx.tag["No repair necessary"]
309 def render_post_repair_results(self, ctx, data):
310 cr = self._render_results(ctx, data.get_post_repair_results())
311 return ctx.tag[T.div["Post-Repair Checker Results:"], cr]
313 def render_maybe_pre_repair_results(self, ctx, data):
314 if data.get_repair_attempted():
315 cr = self._render_results(ctx, data.get_pre_repair_results())
316 return ctx.tag[T.div["Pre-Repair Checker Results:"], cr]
320 class DeepCheckResults(rend.Page, ResultsBase, ReloadMixin):
321 docFactory = getxmlfile("deep-check-results.xhtml")
323 def __init__(self, client, monitor):
325 self.monitor = monitor
327 def childFactory(self, ctx, name):
330 # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information
331 # about a specific file or directory that was checked
332 si = base32.a2b(name)
333 r = self.monitor.get_status()
335 return CheckResults(self.client,
336 r.get_results_for_storage_index(si))
338 raise WebError("No detailed results for SI %s" % html.escape(name),
341 def renderHTTP(self, ctx):
342 if self.want_json(ctx):
343 return self.json(ctx)
344 return rend.Page.renderHTTP(self, ctx)
347 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
349 data["finished"] = self.monitor.is_finished()
350 res = self.monitor.get_status()
351 data["root-storage-index"] = res.get_root_storage_index_string()
352 c = res.get_counters()
353 data["count-objects-checked"] = c["count-objects-checked"]
354 data["count-objects-healthy"] = c["count-objects-healthy"]
355 data["count-objects-unhealthy"] = c["count-objects-unhealthy"]
356 data["count-corrupt-shares"] = c["count-corrupt-shares"]
357 data["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid),
358 base32.b2a(storage_index),
360 for (serverid, storage_index, shnum)
361 in res.get_corrupt_shares() ]
362 data["list-unhealthy-files"] = [ (path_t, json_check_results(r))
364 in res.get_all_results().items()
365 if not r.is_healthy() ]
366 data["stats"] = res.get_stats()
367 return simplejson.dumps(data, indent=1) + "\n"
369 def render_root_storage_index(self, ctx, data):
370 return self.monitor.get_status().get_root_storage_index_string()
372 def data_objects_checked(self, ctx, data):
373 return self.monitor.get_status().get_counters()["count-objects-checked"]
374 def data_objects_healthy(self, ctx, data):
375 return self.monitor.get_status().get_counters()["count-objects-healthy"]
376 def data_objects_unhealthy(self, ctx, data):
377 return self.monitor.get_status().get_counters()["count-objects-unhealthy"]
378 def data_objects_unrecoverable(self, ctx, data):
379 return self.monitor.get_status().get_counters()["count-objects-unrecoverable"]
381 def data_count_corrupt_shares(self, ctx, data):
382 return self.monitor.get_status().get_counters()["count-corrupt-shares"]
384 def render_problems_p(self, ctx, data):
385 c = self.monitor.get_status().get_counters()
386 if c["count-objects-unhealthy"]:
390 def data_problems(self, ctx, data):
391 all_objects = self.monitor.get_status().get_all_results()
392 for path in sorted(all_objects.keys()):
393 cr = all_objects[path]
394 assert ICheckResults.providedBy(cr)
395 if not cr.is_healthy():
398 def render_problem(self, ctx, data):
401 summary = cr.get_summary()
403 summary_text = ": " + summary
404 summary_text += " [SI: %s]" % cr.get_storage_index_string()
405 return ctx.tag[self._join_pathstring(path), self._html(summary_text)]
408 def render_servers_with_corrupt_shares_p(self, ctx, data):
409 if self.monitor.get_status().get_counters()["count-corrupt-shares"]:
413 def data_servers_with_corrupt_shares(self, ctx, data):
415 for (serverid, storage_index, sharenum)
416 in self.monitor.get_status().get_corrupt_shares()]
420 def render_server_problem(self, ctx, data):
422 data = [idlib.shortnodeid_b2a(serverid)]
423 sb = self.client.get_storage_broker()
424 nickname = sb.get_nickname_for_serverid(serverid)
426 data.append(" (%s)" % self._html(nickname))
430 def render_corrupt_shares_p(self, ctx, data):
431 if self.monitor.get_status().get_counters()["count-corrupt-shares"]:
434 def data_corrupt_shares(self, ctx, data):
435 return self.monitor.get_status().get_corrupt_shares()
436 def render_share_problem(self, ctx, data):
437 serverid, storage_index, sharenum = data
438 sb = self.client.get_storage_broker()
439 nickname = sb.get_nickname_for_serverid(serverid)
440 ctx.fillSlots("serverid", idlib.shortnodeid_b2a(serverid))
442 ctx.fillSlots("nickname", self._html(nickname))
443 ctx.fillSlots("si", self._render_si_link(ctx, storage_index))
444 ctx.fillSlots("shnum", str(sharenum))
447 def render_return(self, ctx, data):
448 req = inevow.IRequest(ctx)
449 return_to = get_arg(req, "return_to", None)
451 return T.div[T.a(href=return_to)["Return to file/directory."]]
454 def data_all_objects(self, ctx, data):
455 r = self.monitor.get_status().get_all_results()
456 for path in sorted(r.keys()):
457 yield (path, r[path])
459 def render_object(self, ctx, data):
461 ctx.fillSlots("path", self._join_pathstring(path))
462 ctx.fillSlots("healthy", str(r.is_healthy()))
463 ctx.fillSlots("recoverable", str(r.is_recoverable()))
464 storage_index = r.get_storage_index()
465 ctx.fillSlots("storage_index", self._render_si_link(ctx, storage_index))
466 ctx.fillSlots("summary", self._html(r.get_summary()))
469 def render_runtime(self, ctx, data):
470 req = inevow.IRequest(ctx)
471 runtime = time.time() - req.processing_started_timestamp
472 return ctx.tag["runtime: %s seconds" % runtime]
474 class DeepCheckAndRepairResults(rend.Page, ResultsBase, ReloadMixin):
475 docFactory = getxmlfile("deep-check-and-repair-results.xhtml")
477 def __init__(self, client, monitor):
479 self.monitor = monitor
481 def childFactory(self, ctx, name):
484 # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information
485 # about a specific file or directory that was checked
486 si = base32.a2b(name)
487 r = self.monitor.get_status()
489 return CheckAndRepairResults(self.client,
490 r.get_results_for_storage_index(si))
492 raise WebError("No detailed results for SI %s" % html.escape(name),
495 def renderHTTP(self, ctx):
496 if self.want_json(ctx):
497 return self.json(ctx)
498 return rend.Page.renderHTTP(self, ctx)
501 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
502 res = self.monitor.get_status()
504 data["finished"] = self.monitor.is_finished()
505 data["root-storage-index"] = res.get_root_storage_index_string()
506 c = res.get_counters()
507 data["count-objects-checked"] = c["count-objects-checked"]
509 data["count-objects-healthy-pre-repair"] = c["count-objects-healthy-pre-repair"]
510 data["count-objects-unhealthy-pre-repair"] = c["count-objects-unhealthy-pre-repair"]
511 data["count-objects-healthy-post-repair"] = c["count-objects-healthy-post-repair"]
512 data["count-objects-unhealthy-post-repair"] = c["count-objects-unhealthy-post-repair"]
514 data["count-repairs-attempted"] = c["count-repairs-attempted"]
515 data["count-repairs-successful"] = c["count-repairs-successful"]
516 data["count-repairs-unsuccessful"] = c["count-repairs-unsuccessful"]
518 data["count-corrupt-shares-pre-repair"] = c["count-corrupt-shares-pre-repair"]
519 data["count-corrupt-shares-post-repair"] = c["count-corrupt-shares-pre-repair"]
521 data["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid),
522 base32.b2a(storage_index),
524 for (serverid, storage_index, shnum)
525 in res.get_corrupt_shares() ]
527 remaining_corrupt = [ (idlib.nodeid_b2a(serverid),
528 base32.b2a(storage_index),
530 for (serverid, storage_index, shnum)
531 in res.get_remaining_corrupt_shares() ]
532 data["list-remaining-corrupt-shares"] = remaining_corrupt
534 unhealthy = [ (path_t,
535 json_check_results(crr.get_pre_repair_results()))
537 in res.get_all_results().items()
538 if not crr.get_pre_repair_results().is_healthy() ]
539 data["list-unhealthy-files"] = unhealthy
540 data["stats"] = res.get_stats()
541 return simplejson.dumps(data, indent=1) + "\n"
543 def render_root_storage_index(self, ctx, data):
544 return self.monitor.get_status().get_root_storage_index_string()
546 def data_objects_checked(self, ctx, data):
547 return self.monitor.get_status().get_counters()["count-objects-checked"]
549 def data_objects_healthy(self, ctx, data):
550 return self.monitor.get_status().get_counters()["count-objects-healthy-pre-repair"]
551 def data_objects_unhealthy(self, ctx, data):
552 return self.monitor.get_status().get_counters()["count-objects-unhealthy-pre-repair"]
553 def data_corrupt_shares(self, ctx, data):
554 return self.monitor.get_status().get_counters()["count-corrupt-shares-pre-repair"]
556 def data_repairs_attempted(self, ctx, data):
557 return self.monitor.get_status().get_counters()["count-repairs-attempted"]
558 def data_repairs_successful(self, ctx, data):
559 return self.monitor.get_status().get_counters()["count-repairs-successful"]
560 def data_repairs_unsuccessful(self, ctx, data):
561 return self.monitor.get_status().get_counters()["count-repairs-unsuccessful"]
563 def data_objects_healthy_post(self, ctx, data):
564 return self.monitor.get_status().get_counters()["count-objects-healthy-post-repair"]
565 def data_objects_unhealthy_post(self, ctx, data):
566 return self.monitor.get_status().get_counters()["count-objects-unhealthy-post-repair"]
567 def data_corrupt_shares_post(self, ctx, data):
568 return self.monitor.get_status().get_counters()["count-corrupt-shares-post-repair"]
570 def render_pre_repair_problems_p(self, ctx, data):
571 c = self.monitor.get_status().get_counters()
572 if c["count-objects-unhealthy-pre-repair"]:
576 def data_pre_repair_problems(self, ctx, data):
577 all_objects = self.monitor.get_status().get_all_results()
578 for path in sorted(all_objects.keys()):
579 r = all_objects[path]
580 assert ICheckAndRepairResults.providedBy(r)
581 cr = r.get_pre_repair_results()
582 if not cr.is_healthy():
585 def render_problem(self, ctx, data):
587 return ctx.tag[self._join_pathstring(path), ": ",
588 self._html(cr.get_summary())]
590 def render_post_repair_problems_p(self, ctx, data):
591 c = self.monitor.get_status().get_counters()
592 if (c["count-objects-unhealthy-post-repair"]
593 or c["count-corrupt-shares-post-repair"]):
597 def data_post_repair_problems(self, ctx, data):
598 all_objects = self.monitor.get_status().get_all_results()
599 for path in sorted(all_objects.keys()):
600 r = all_objects[path]
601 assert ICheckAndRepairResults.providedBy(r)
602 cr = r.get_post_repair_results()
603 if not cr.is_healthy():
606 def render_servers_with_corrupt_shares_p(self, ctx, data):
607 if self.monitor.get_status().get_counters()["count-corrupt-shares-pre-repair"]:
610 def data_servers_with_corrupt_shares(self, ctx, data):
612 def render_server_problem(self, ctx, data):
616 def render_remaining_corrupt_shares_p(self, ctx, data):
617 if self.monitor.get_status().get_counters()["count-corrupt-shares-post-repair"]:
620 def data_post_repair_corrupt_shares(self, ctx, data):
623 def render_share_problem(self, ctx, data):
627 def render_return(self, ctx, data):
628 req = inevow.IRequest(ctx)
629 return_to = get_arg(req, "return_to", None)
631 return T.div[T.a(href=return_to)["Return to file/directory."]]
634 def data_all_objects(self, ctx, data):
635 r = self.monitor.get_status().get_all_results()
636 for path in sorted(r.keys()):
637 yield (path, r[path])
639 def render_object(self, ctx, data):
641 ctx.fillSlots("path", self._join_pathstring(path))
642 ctx.fillSlots("healthy_pre_repair",
643 str(r.get_pre_repair_results().is_healthy()))
644 ctx.fillSlots("recoverable_pre_repair",
645 str(r.get_pre_repair_results().is_recoverable()))
646 ctx.fillSlots("healthy_post_repair",
647 str(r.get_post_repair_results().is_healthy()))
648 storage_index = r.get_storage_index()
649 ctx.fillSlots("storage_index",
650 self._render_si_link(ctx, storage_index))
651 ctx.fillSlots("summary",
652 self._html(r.get_pre_repair_results().get_summary()))
655 def render_runtime(self, ctx, data):
656 req = inevow.IRequest(ctx)
657 runtime = time.time() - req.processing_started_timestamp
658 return ctx.tag["runtime: %s seconds" % runtime]