source: trunk/src/allmydata/web/check_results.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 29.3 KB
Line 
1"""
2Ported to Python 3.
3"""
4
5import time
6
7from twisted.web import (
8    http,
9    html,
10)
11from twisted.python.filepath import FilePath
12from twisted.web.template import (
13    Element,
14    XMLFile,
15    renderer,
16    renderElement,
17    tags,
18)
19from allmydata.web.common import (
20    exception_to_child,
21    get_arg,
22    get_root,
23    render_exception,
24    WebError,
25    MultiFormatResource,
26    SlotsSequenceElement,
27)
28from allmydata.web.operations import ReloadMixin
29from allmydata.interfaces import (
30    ICheckAndRepairResults,
31    ICheckResults,
32)
33from allmydata.util import (
34    base32,
35    dictutil,
36    jsonbytes as json,  # Supporting dumping bytes
37)
38
39
40def json_check_counts(r):
41    d = {"count-happiness": r.get_happiness(),
42         "count-shares-good": r.get_share_counter_good(),
43         "count-shares-needed": r.get_encoding_needed(),
44         "count-shares-expected": r.get_encoding_expected(),
45         "count-good-share-hosts": r.get_host_counter_good_shares(),
46         "count-corrupt-shares": len(r.get_corrupt_shares()),
47         "list-corrupt-shares": [ (s.get_longname(), base32.b2a(si), shnum)
48                                  for (s, si, shnum)
49                                  in r.get_corrupt_shares() ],
50         "servers-responding": [s.get_longname()
51                                for s in r.get_servers_responding()],
52         "sharemap": dict([(shareid,
53                            sorted([s.get_longname() for s in servers]))
54                           for (shareid, servers)
55                           in r.get_sharemap().items()]),
56         "count-wrong-shares": r.get_share_counter_wrong(),
57         "count-recoverable-versions": r.get_version_counter_recoverable(),
58         "count-unrecoverable-versions": r.get_version_counter_unrecoverable(),
59         }
60    return d
61
62def json_check_results(r):
63    if r is None:
64        # LIT file
65        data = {"storage-index": "",
66                "results": {"healthy": True},
67                }
68        return data
69    data = {}
70    data["storage-index"] = r.get_storage_index_string()
71    data["summary"] = r.get_summary()
72    data["results"] = json_check_counts(r)
73    data["results"]["healthy"] = r.is_healthy()
74    data["results"]["recoverable"] = r.is_recoverable()
75    return data
76
77def json_check_and_repair_results(r):
78    if r is None:
79        # LIT file
80        data = {"storage-index": "",
81                "repair-attempted": False,
82                }
83        return data
84    data = {}
85    data["storage-index"] = r.get_storage_index_string()
86    data["repair-attempted"] = r.get_repair_attempted()
87    data["repair-successful"] = r.get_repair_successful()
88    pre = r.get_pre_repair_results()
89    data["pre-repair-results"] = json_check_results(pre)
90    post = r.get_post_repair_results()
91    data["post-repair-results"] = json_check_results(post)
92    return data
93
94class ResultsBase(object):
95    # self._client must point to the Client, so we can get nicknames and
96    # determine the permuted peer order
97
98    def _join_pathstring(self, path):
99        """
100        :param tuple path: a path represented by a tuple, such as
101            ``(u'some', u'dir', u'file')``.
102
103        :return: a string joined by path separaters, such as
104            ``u'some/dir/file'``.
105        """
106        if path:
107            pathstring = "/".join(self._html(path))
108        else:
109            pathstring = "<root>"
110        return pathstring
111
112    def _render_results(self, req, cr):
113        assert ICheckResults(cr)
114        c = self._client
115        sb = c.get_storage_broker()
116        r = []
117        def add(name, value):
118            r.append(tags.li(name + ": ", value))
119
120        add("Report", tags.pre("\n".join(self._html(cr.get_report()))))
121
122        add("Share Counts",
123            "need %d-of-%d, have %d" % (cr.get_encoding_needed(),
124                                        cr.get_encoding_expected(),
125                                        cr.get_share_counter_good()))
126        add("Happiness Level", str(cr.get_happiness()))
127        add("Hosts with good shares", str(cr.get_host_counter_good_shares()))
128
129        if cr.get_corrupt_shares():
130            badsharemap = []
131            for (s, si, shnum) in cr.get_corrupt_shares():
132                d = tags.tr(tags.td("sh#%d" % shnum),
133                            tags.td(tags.div(s.get_nickname(), class_="nickname"),
134                                    tags.div(tags.tt(s.get_name()), class_="nodeid")),)
135                badsharemap.append(d)
136            add("Corrupt shares",
137                tags.table(
138                    tags.tr(tags.th("Share ID"),
139                            tags.th((tags.div("Nickname"), tags.div("Node ID", class_="nodeid")), class_="nickname-and-peerid")),
140                    badsharemap))
141        else:
142            add("Corrupt shares", "none")
143
144        add("Wrong Shares", str(cr.get_share_counter_wrong()))
145
146        sharemap_data = []
147        shares_on_server = dictutil.DictOfSets()
148
149        # FIXME: The two tables below contain nickname-and-nodeid
150        # table column markup which is duplicated with each other,
151        # introducer.xhtml, and deep-check-results.xhtml. All of these
152        # (and any other presentations of nickname-and-nodeid) should be combined.
153
154        for shareid in sorted(cr.get_sharemap().keys()):
155            servers = sorted(cr.get_sharemap()[shareid],
156                             key=lambda s: s.get_longname())
157            for i,s in enumerate(servers):
158                shares_on_server.add(s, shareid)
159                shareid_s = ""
160                if i == 0:
161                    if isinstance(shareid, bytes):
162                        shareid_s = str(shareid, "utf-8")
163                    else:
164                        shareid_s = str(shareid)
165                d = tags.tr(tags.td(shareid_s),
166                            tags.td(tags.div(s.get_nickname(), class_="nickname"),
167                                    tags.div(tags.tt(s.get_name()), class_="nodeid")))
168                sharemap_data.append(d)
169
170        add("Good Shares (sorted in share order)",
171            tags.table(tags.tr(tags.th("Share ID"),
172                               tags.th(tags.div("Nickname"),
173                                       tags.div("Node ID", class_="nodeid"), class_="nickname-and-peerid")),
174                       sharemap_data))
175
176        add("Recoverable Versions", str(cr.get_version_counter_recoverable()))
177        add("Unrecoverable Versions", str(cr.get_version_counter_unrecoverable()))
178
179        # this table is sorted by permuted order
180        permuted_servers = [s
181                            for s
182                            in sb.get_servers_for_psi(cr.get_storage_index())]
183
184        num_shares_left = sum([len(shareids)
185                               for shareids in shares_on_server.values()])
186        servermap = []
187        for s in permuted_servers:
188            shareids = list(shares_on_server.get(s, []))
189            shareids.reverse()
190            shareids_s = [tags.tt(str(shareid), " ") for shareid in sorted(shareids)]
191
192            d = tags.tr(tags.td(tags.div(s.get_nickname(), class_="nickname"),
193                             tags.div(tags.tt(s.get_name()), class_="nodeid")),
194                        tags.td(shareids_s), )
195            servermap.append(d)
196            num_shares_left -= len(shareids)
197            if not num_shares_left:
198                break
199
200        add("Share Balancing (servers in permuted order)",
201            tags.table(tags.tr(tags.th(tags.div("Nickname"),
202                                    tags.div("Node ID", class_="nodeid"), class_="nickname-and-peerid"),
203                            tags.th("Share IDs")),
204                       servermap))
205
206        return tags.ul(r)
207
208    def _html(self, s):
209        if isinstance(s, (bytes, str)):
210            return html.escape(s)
211        assert isinstance(s, (list, tuple))
212        return [html.escape(w) for w in s]
213
214    def _render_si_link(self, req, storage_index):
215        si_s = str(base32.b2a(storage_index), "utf-8")
216        ophandle = str(req.prepath[-1], "utf-8")
217        target = "%s/operations/%s/%s" % (get_root(req), ophandle, si_s)
218        output = get_arg(req, "output")
219        if output:
220            target = target + "?output=" + str(output, "utf-8")
221        return tags.a(si_s, href=target)
222
223
224class LiteralCheckResultsRenderer(MultiFormatResource, ResultsBase):
225
226    formatArgument = "output"
227
228    def __init__(self, client):
229        """
230        :param allmydata.interfaces.IStatsProducer client: stats provider.
231        """
232        super(LiteralCheckResultsRenderer, self).__init__()
233        self._client = client
234
235    @render_exception
236    def render_HTML(self, req):
237        return renderElement(req, LiteralCheckResultsRendererElement())
238
239    @render_exception
240    def render_JSON(self, req):
241        req.setHeader("content-type", "text/plain")
242        data = json_check_results(None)
243        return json.dumps(data, indent=1) + "\n"
244
245
246class LiteralCheckResultsRendererElement(Element):
247
248    loader = XMLFile(FilePath(__file__).sibling("literal-check-results.xhtml"))
249
250    def __init__(self):
251        super(LiteralCheckResultsRendererElement, self).__init__()
252
253    @renderer
254    def return_to(self, req, tag):
255        return_to = get_arg(req, "return_to", None)
256        if return_to:
257            return tags.div(tags.a("Return to file.", href=return_to))
258        return ""
259
260
261class CheckerBase(object):
262
263    @renderer
264    def storage_index(self, req, tag):
265        return self._results.get_storage_index_string()
266
267    @renderer
268    def return_to(self, req, tag):
269        return_to = get_arg(req, "return_to", None)
270        if return_to:
271            return tags.div(tags.a("Return to file/directory.", href=return_to))
272        return ""
273
274
275class CheckResultsRenderer(MultiFormatResource):
276
277    formatArgument = "output"
278
279    def __init__(self, client, results):
280        """
281        :param allmydata.interfaces.IStatsProducer client: stats provider.
282        :param allmydata.interfaces.ICheckResults results: results of check/vefify operation.
283        """
284        super(CheckResultsRenderer, self).__init__()
285        self._client = client
286        self._results = ICheckResults(results)
287
288    @render_exception
289    def render_HTML(self, req):
290        return renderElement(req, CheckResultsRendererElement(self._client, self._results))
291
292    @render_exception
293    def render_JSON(self, req):
294        req.setHeader("content-type", "text/plain")
295        data = json_check_results(self._results)
296        return json.dumps(data, indent=1) + "\n"
297
298
299class CheckResultsRendererElement(Element, CheckerBase, ResultsBase):
300
301    loader = XMLFile(FilePath(__file__).sibling("check-results.xhtml"))
302
303    def __init__(self, client, results):
304        super(CheckResultsRendererElement, self).__init__()
305        self._client = client
306        self._results = results
307
308    @renderer
309    def summary(self, req, tag):
310        results = []
311        if self._results.is_healthy():
312            results.append("Healthy")
313        elif self._results.is_recoverable():
314            results.append("Not Healthy!")
315        else:
316            results.append("Not Recoverable!")
317        results.append(" : ")
318        results.append(self._html(self._results.get_summary()))
319        return tag(results)
320
321    @renderer
322    def repair(self, req, tag):
323        if self._results.is_healthy():
324            return ""
325
326        #repair = T.form(action=".", method="post",
327        #                enctype="multipart/form-data")[
328        #    T.fieldset[
329        #    T.input(type="hidden", name="t", value="check"),
330        #    T.input(type="hidden", name="repair", value="true"),
331        #    T.input(type="submit", value="Repair"),
332        #    ]]
333        #return ctx.tag[repair]
334
335        return "" # repair button disabled until we make it work correctly,
336                  # see #622 for details
337
338    @renderer
339    def results(self, req, tag):
340        cr = self._render_results(req, self._results)
341        return tag(cr)
342
343class CheckAndRepairResultsRenderer(MultiFormatResource):
344
345    formatArgument = "output"
346
347    def __init__(self, client, results):
348        """
349        :param allmydata.interfaces.IStatsProducer client: stats provider.
350        :param allmydata.interfaces.ICheckResults results: check/verify results.
351        """
352        super(CheckAndRepairResultsRenderer, self).__init__()
353        self._client = client
354        self._results = None
355        if results:
356            self._results = ICheckAndRepairResults(results)
357
358    @render_exception
359    def render_HTML(self, req):
360        elem = CheckAndRepairResultsRendererElement(self._client, self._results)
361        return renderElement(req, elem)
362
363    @render_exception
364    def render_JSON(self, req):
365        req.setHeader("content-type", "text/plain")
366        data = json_check_and_repair_results(self._results)
367        return json.dumps(data, indent=1) + "\n"
368
369
370class CheckAndRepairResultsRendererElement(Element, CheckerBase, ResultsBase):
371
372    loader = XMLFile(FilePath(__file__).sibling("check-and-repair-results.xhtml"))
373
374    def __init__(self, client, results):
375        super(CheckAndRepairResultsRendererElement, self).__init__()
376        self._client = client
377        self._results = results
378
379    @renderer
380    def summary(self, req, tag):
381        cr = self._results.get_post_repair_results()
382        results = []
383        if cr.is_healthy():
384            results.append("Healthy")
385        elif cr.is_recoverable():
386            results.append("Not Healthy!")
387        else:
388            results.append("Not Recoverable!")
389        results.append(" : ")
390        results.append(self._html(cr.get_summary()))
391        return tag(results)
392
393    @renderer
394    def repair_results(self, req, tag):
395        if self._results.get_repair_attempted():
396            if self._results.get_repair_successful():
397                return tag("Repair successful")
398            else:
399                return tag("Repair unsuccessful")
400        return tag("No repair necessary")
401
402    @renderer
403    def post_repair_results(self, req, tag):
404        cr = self._render_results(req, self._results.get_post_repair_results())
405        return tag(tags.div("Post-Repair Checker Results:"), cr)
406
407    @renderer
408    def maybe_pre_repair_results(self, req, tag):
409        if self._results.get_repair_attempted():
410            cr = self._render_results(req, self._results.get_pre_repair_results())
411            return tag(tags.div("Pre-Repair Checker Results:"), cr)
412        return ""
413
414
415class DeepCheckResultsRenderer(MultiFormatResource):
416
417    formatArgument = "output"
418
419    def __init__(self, client, monitor):
420        """
421        :param allmydata.interfaces.IStatsProducer client: stats provider.
422        :param allmydata.monitor.IMonitor monitor: status, progress, and cancellation provider.
423        """
424        super(DeepCheckResultsRenderer, self).__init__()
425        self._client = client
426        self.monitor = monitor
427
428    @exception_to_child
429    def getChild(self, name, req):
430        if not name:
431            return self
432        # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information
433        # about a specific file or directory that was checked
434        si = base32.a2b(name)
435        r = self.monitor.get_status()
436        try:
437            return CheckResultsRenderer(self._client,
438                                        r.get_results_for_storage_index(si))
439        except KeyError:
440            raise WebError("No detailed results for SI %s" % html.escape(str(name, "utf-8")),
441                           http.NOT_FOUND)
442
443    @render_exception
444    def render_HTML(self, req):
445        elem = DeepCheckResultsRendererElement(self.monitor)
446        return renderElement(req, elem)
447
448    @render_exception
449    def render_JSON(self, req):
450        req.setHeader("content-type", "text/plain")
451        data = {}
452        data["finished"] = self.monitor.is_finished()
453        res = self.monitor.get_status()
454        data["root-storage-index"] = res.get_root_storage_index_string()
455        c = res.get_counters()
456        data["count-objects-checked"] = c["count-objects-checked"]
457        data["count-objects-healthy"] = c["count-objects-healthy"]
458        data["count-objects-unhealthy"] = c["count-objects-unhealthy"]
459        data["count-corrupt-shares"] = c["count-corrupt-shares"]
460        data["list-corrupt-shares"] = [ (s.get_longname(),
461                                         base32.b2a(storage_index),
462                                         shnum)
463                                        for (s, storage_index, shnum)
464                                        in res.get_corrupt_shares() ]
465        data["list-unhealthy-files"] = [ (path_t, json_check_results(r))
466                                         for (path_t, r)
467                                         in res.get_all_results().items()
468                                         if not r.is_healthy() ]
469        data["stats"] = res.get_stats()
470        return json.dumps(data, indent=1) + "\n"
471
472
473class DeepCheckResultsRendererElement(Element, ResultsBase, ReloadMixin):
474
475    loader = XMLFile(FilePath(__file__).sibling("deep-check-results.xhtml"))
476
477    def __init__(self, monitor):
478        super(DeepCheckResultsRendererElement, self).__init__()
479        self.monitor = monitor
480
481    @renderer
482    def root_storage_index(self, req, tag):
483        if not self.monitor.get_status():
484            return ""
485        return self.monitor.get_status().get_root_storage_index_string()
486
487    def _get_monitor_counter(self, name):
488        if not self.monitor.get_status():
489            return ""
490        return str(self.monitor.get_status().get_counters().get(name))
491
492    @renderer
493    def objects_checked(self, req, tag):
494        return self._get_monitor_counter("count-objects-checked")
495
496    @renderer
497    def objects_healthy(self, req, tag):
498        return self._get_monitor_counter("count-objects-healthy")
499
500    @renderer
501    def objects_unhealthy(self, req, tag):
502        return self._get_monitor_counter("count-objects-unhealthy")
503
504    @renderer
505    def objects_unrecoverable(self, req, tag):
506        return self._get_monitor_counter("count-objects-unrecoverable")
507
508    @renderer
509    def count_corrupt_shares(self, req, tag):
510        return self._get_monitor_counter("count-corrupt-shares")
511
512    @renderer
513    def problems_p(self, req, tag):
514        if self._get_monitor_counter("count-objects-unhealthy"):
515            return tag
516        return ""
517
518    @renderer
519    def problems(self, req, tag):
520        all_objects = self.monitor.get_status().get_all_results()
521        problems = []
522
523        for path in sorted(all_objects.keys()):
524            cr = all_objects[path]
525            assert ICheckResults.providedBy(cr)
526            if not cr.is_healthy():
527                summary_text = ""
528                summary = cr.get_summary()
529                if summary:
530                    summary_text = ": " + summary
531                summary_text += " [SI: %s]" % cr.get_storage_index_string().decode("ascii")
532                problems.append({
533                    # Not sure self._join_pathstring(path) is the
534                    # right thing to use here.
535                    "problem": self._join_pathstring(path) + self._html(summary_text),
536                })
537
538        return SlotsSequenceElement(tag, problems)
539
540    @renderer
541    def servers_with_corrupt_shares_p(self, req, tag):
542        if self._get_monitor_counter("count-corrupt-shares"):
543            return tag
544        return ""
545
546    @renderer
547    def servers_with_corrupt_shares(self, req, tag):
548        servers = [s
549                   for (s, storage_index, sharenum)
550                   in self.monitor.get_status().get_corrupt_shares()]
551        servers.sort(key=lambda s: s.get_longname())
552
553        problems = []
554
555        for server in servers:
556            name = [server.get_name()]
557            nickname = server.get_nickname()
558            if nickname:
559                name.append(" (%s)" % self._html(nickname))
560            problems.append({"problem": name})
561
562        return SlotsSequenceElement(tag, problems)
563
564    @renderer
565    def corrupt_shares_p(self, req, tag):
566        if self._get_monitor_counter("count-corrupt-shares"):
567            return tag
568        return ""
569
570    @renderer
571    def corrupt_shares(self, req, tag):
572        shares = self.monitor.get_status().get_corrupt_shares()
573        problems = []
574
575        for share in shares:
576            server, storage_index, sharenum = share
577            nickname = server.get_nickname()
578            problem = {
579                "serverid": server.get_name(),
580                "nickname": self._html(nickname),
581                "si": self._render_si_link(req, storage_index),
582                "shnum": str(sharenum),
583            }
584            problems.append(problem)
585
586        return SlotsSequenceElement(tag, problems)
587
588    @renderer
589    def return_to(self, req, tag):
590        return_to = get_arg(req, "return_to", None)
591        if return_to:
592            return tags.div(tags.a("Return to file/directory.", href=return_to))
593        return ""
594
595    @renderer
596    def all_objects(self, req, tag):
597        results = self.monitor.get_status().get_all_results()
598        objects = []
599
600        for path in sorted(results.keys()):
601            result = results.get(path)
602            storage_index = result.get_storage_index()
603            object = {
604                "path": self._join_pathstring(path),
605                "healthy": str(result.is_healthy()),
606                "recoverable": str(result.is_recoverable()),
607                "storage_index": self._render_si_link(req, storage_index),
608                "summary": self._html(result.get_summary()),
609            }
610            objects.append(object)
611
612        return SlotsSequenceElement(tag, objects)
613
614    @renderer
615    def runtime(self, req, tag):
616        runtime = 'unknown'
617        if hasattr(req, 'processing_started_timestamp'):
618            runtime = time.time() - req.processing_started_timestamp
619        return tag("runtime: %s seconds" % runtime)
620
621
622class DeepCheckAndRepairResultsRenderer(MultiFormatResource):
623
624    formatArgument = "output"
625
626    def __init__(self, client, monitor):
627        """
628        :param allmydata.interfaces.IStatsProducer client: stats provider.
629        :param allmydata.monitor.IMonitor monitor: status, progress, and cancellation provider.
630        """
631        super(DeepCheckAndRepairResultsRenderer, self).__init__()
632        self._client = client
633        self.monitor = monitor
634
635    @exception_to_child
636    def getChild(self, name, req):
637        if not name:
638            return self
639        # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information
640        # about a specific file or directory that was checked
641        si = base32.a2b(name)
642        s = self.monitor.get_status()
643        try:
644            results = s.get_results_for_storage_index(si)
645            return CheckAndRepairResultsRenderer(self._client, results)
646        except KeyError:
647            raise WebError("No detailed results for SI %s" % html.escape(name),
648                           http.NOT_FOUND)
649
650    @render_exception
651    def render_HTML(self, req):
652        elem = DeepCheckAndRepairResultsRendererElement(self.monitor)
653        return renderElement(req, elem)
654
655    @render_exception
656    def render_JSON(self, req):
657        req.setHeader("content-type", "text/plain")
658        res = self.monitor.get_status()
659        data = {}
660        data["finished"] = self.monitor.is_finished()
661        data["root-storage-index"] = res.get_root_storage_index_string()
662        c = res.get_counters()
663        data["count-objects-checked"] = c["count-objects-checked"]
664
665        data["count-objects-healthy-pre-repair"] = c["count-objects-healthy-pre-repair"]
666        data["count-objects-unhealthy-pre-repair"] = c["count-objects-unhealthy-pre-repair"]
667        data["count-objects-healthy-post-repair"] = c["count-objects-healthy-post-repair"]
668        data["count-objects-unhealthy-post-repair"] = c["count-objects-unhealthy-post-repair"]
669
670        data["count-repairs-attempted"] = c["count-repairs-attempted"]
671        data["count-repairs-successful"] = c["count-repairs-successful"]
672        data["count-repairs-unsuccessful"] = c["count-repairs-unsuccessful"]
673
674        data["count-corrupt-shares-pre-repair"] = c["count-corrupt-shares-pre-repair"]
675        data["count-corrupt-shares-post-repair"] = c["count-corrupt-shares-pre-repair"]
676
677        data["list-corrupt-shares"] = [ (s.get_longname(),
678                                         base32.b2a(storage_index),
679                                         shnum)
680                                        for (s, storage_index, shnum)
681                                        in res.get_corrupt_shares() ]
682
683        remaining_corrupt = [ (s.get_longname(), base32.b2a(storage_index),
684                               shnum)
685                              for (s, storage_index, shnum)
686                              in res.get_remaining_corrupt_shares() ]
687        data["list-remaining-corrupt-shares"] = remaining_corrupt
688
689        unhealthy = [ (path_t,
690                       json_check_results(crr.get_pre_repair_results()))
691                      for (path_t, crr)
692                      in res.get_all_results().items()
693                      if not crr.get_pre_repair_results().is_healthy() ]
694        data["list-unhealthy-files"] = unhealthy
695        data["stats"] = res.get_stats()
696        return json.dumps(data, indent=1) + "\n"
697
698
699class DeepCheckAndRepairResultsRendererElement(DeepCheckResultsRendererElement):
700    """
701    The page generated here has several elements common to "deep check
702    results" page; hence the code reuse.
703    """
704
705    loader = XMLFile(FilePath(__file__).sibling("deep-check-and-repair-results.xhtml"))
706
707    def __init__(self, monitor):
708        super(DeepCheckAndRepairResultsRendererElement, self).__init__(monitor)
709        self.monitor = monitor
710
711    @renderer
712    def objects_healthy(self, req, tag):
713        return self._get_monitor_counter("count-objects-healthy-pre-repair")
714
715    @renderer
716    def objects_unhealthy(self, req, tag):
717        return self._get_monitor_counter("count-objects-unhealthy-pre-repair")
718
719    @renderer
720    def corrupt_shares(self, req, tag):
721        return self._get_monitor_counter("count-corrupt-shares-pre-repair")
722
723    @renderer
724    def repairs_attempted(self, req, tag):
725        return self._get_monitor_counter("count-repairs-attempted")
726
727    @renderer
728    def repairs_successful(self, req, tag):
729        return self._get_monitor_counter("count-repairs-successful")
730
731    @renderer
732    def repairs_unsuccessful(self, req, tag):
733        return self._get_monitor_counter("count-repairs-unsuccessful")
734
735    @renderer
736    def objects_healthy_post(self, req, tag):
737        return self._get_monitor_counter("count-objects-healthy-post-repair")
738
739    @renderer
740    def objects_unhealthy_post(self, req, tag):
741        return self._get_monitor_counter("count-objects-unhealthy-post-repair")
742
743    @renderer
744    def corrupt_shares_post(self, req, tag):
745        return self._get_monitor_counter("count-corrupt-shares-post-repair")
746
747    @renderer
748    def pre_repair_problems_p(self, req, tag):
749        if self._get_monitor_counter("count-objects-unhealthy-pre-repair"):
750            return tag
751        return ""
752
753    @renderer
754    def pre_repair_problems(self, req, tag):
755        all_objects = self.monitor.get_status().get_all_results()
756        problems = []
757
758        for path in sorted(all_objects.keys()):
759            r = all_objects[path]
760            assert ICheckAndRepairResults.providedBy(r)
761            cr = r.get_pre_repair_results()
762            if not cr.is_healthy():
763                problem = self._join_pathstring(path), ": ", self._html(cr.get_summary())
764                problems.append({"problem": problem})
765
766        return SlotsSequenceElement(tag, problems)
767
768    @renderer
769    def post_repair_problems_p(self, req, tag):
770        if (self._get_monitor_counter("count-objects-unhealthy-post-repair")
771            or self._get_monitor_counter("count-corrupt-shares-post-repair")):
772            return tag
773        return ""
774
775    @renderer
776    def post_repair_problems(self, req, tag):
777        all_objects = self.monitor.get_status().get_all_results()
778        problems = []
779
780        for path in sorted(all_objects.keys()):
781            r = all_objects[path]
782            assert ICheckAndRepairResults.providedBy(r)
783            cr = r.get_post_repair_results()
784            if not cr.is_healthy():
785                problem = self._join_pathstring(path), ": ", self._html(cr.get_summary())
786                problems.append({"problem": problem})
787
788        return SlotsSequenceElement(tag, problems)
789
790    @renderer
791    def remaining_corrupt_shares_p(self, req, tag):
792        if self._get_monitor_counter("count-corrupt-shares-post-repair"):
793            return tag
794        return ""
795
796    @renderer
797    def post_repair_corrupt_shares(self, req, tag):
798        # TODO: this was not implemented before porting to
799        # twisted.web.template; leaving it as such.
800        #
801        # https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3371
802        corrupt = [{"share":"unimplemented"}]
803        return SlotsSequenceElement(tag, corrupt)
804
805    @renderer
806    def all_objects(self, req, tag):
807        results = {}
808        if self.monitor.get_status():
809            results = self.monitor.get_status().get_all_results()
810        objects = []
811
812        for path in sorted(results.keys()):
813            result = results[path]
814            storage_index = result.get_storage_index()
815            obj = {
816                "path": self._join_pathstring(path),
817                "healthy_pre_repair": str(result.get_pre_repair_results().is_healthy()),
818                "recoverable_pre_repair": str(result.get_pre_repair_results().is_recoverable()),
819                "healthy_post_repair": str(result.get_post_repair_results().is_healthy()),
820                "storage_index": self._render_si_link(req, storage_index),
821                "summary": self._html(result.get_pre_repair_results().get_summary()),
822            }
823            objects.append(obj)
824
825        return SlotsSequenceElement(tag, objects)
826
Note: See TracBrowser for help on using the repository browser.