From: Zooko O'Whielacronx Date: Tue, 6 Jan 2009 20:37:03 +0000 (-0700) Subject: rename "checker results" to "check results", because it is more parallel to "check... X-Git-Url: https://git.rkrishnan.org/simplejson/components/com_hotproperty/index.php?a=commitdiff_plain;h=5e6f90a015d4ffc48fd74ed8785c2d3ba474d0a9;p=tahoe-lafs%2Ftahoe-lafs.git rename "checker results" to "check results", because it is more parallel to "check-and-repair results" --- diff --git a/docs/frontends/webapi.txt b/docs/frontends/webapi.txt index 426ff7be..29fae18b 100644 --- a/docs/frontends/webapi.txt +++ b/docs/frontends/webapi.txt @@ -826,7 +826,7 @@ POST $URL?t=start-deep-check (must add &ophandle=XYZ) will continue to run in the background, and the /operations page should be used to find out when the operation is done. - Detailed checker results for non-healthy files and directories will be + Detailed check results for non-healthy files and directories will be available under /operations/$HANDLE/$STORAGEINDEX, and the HTML status will contain links to these detailed results. diff --git a/src/allmydata/check_results.py b/src/allmydata/check_results.py new file mode 100644 index 00000000..d4951151 --- /dev/null +++ b/src/allmydata/check_results.py @@ -0,0 +1,222 @@ + +from zope.interface import implements +from allmydata.interfaces import ICheckResults, ICheckAndRepairResults, \ + IDeepCheckResults, IDeepCheckAndRepairResults, IURI +from allmydata.util import base32 + +class CheckerResults: + implements(ICheckResults) + + def __init__(self, uri, storage_index): + assert IURI.providedBy(uri), uri + self.uri = uri + self.storage_index = storage_index + self.problems = [] + self.data = {"count-corrupt-shares": 0, + "list-corrupt-shares": [], + } + self.summary = "" + self.report = [] + + def set_healthy(self, healthy): + self.healthy = bool(healthy) + if self.healthy: + assert (not hasattr(self, 'recoverable')) or self.recoverable, hasattr(self, 'recoverable') and self.recoverable + self.recoverable = True + self.summary = "healthy" + else: + self.summary = "not healthy" + def set_recoverable(self, recoverable): + self.recoverable = recoverable + if not self.recoverable: + assert (not hasattr(self, 'healthy')) or not self.healthy + self.healthy = False + def set_needs_rebalancing(self, needs_rebalancing): + self.needs_rebalancing_p = bool(needs_rebalancing) + def set_data(self, data): + self.data.update(data) + def set_summary(self, summary): + assert isinstance(summary, str) # should be a single string + self.summary = summary + def set_report(self, report): + assert not isinstance(report, str) # should be list of strings + self.report = report + + def set_servermap(self, smap): + # mutable only + self.servermap = smap + + + def get_storage_index(self): + return self.storage_index + def get_storage_index_string(self): + return base32.b2a(self.storage_index) + def get_uri(self): + return self.uri + + def is_healthy(self): + return self.healthy + def is_recoverable(self): + return self.recoverable + + def needs_rebalancing(self): + return self.needs_rebalancing_p + def get_data(self): + return self.data + + def get_summary(self): + return self.summary + def get_report(self): + return self.report + def get_servermap(self): + return self.servermap + +class CheckAndRepairResults: + implements(ICheckAndRepairResults) + + def __init__(self, storage_index): + self.storage_index = storage_index + self.repair_attempted = False + + def get_storage_index(self): + return self.storage_index + def get_storage_index_string(self): + return base32.b2a(self.storage_index) + def get_repair_attempted(self): + return self.repair_attempted + def get_repair_successful(self): + if not self.repair_attempted: + return False + return self.repair_successful + def get_pre_repair_results(self): + return self.pre_repair_results + def get_post_repair_results(self): + return self.post_repair_results + + +class DeepResultsBase: + + def __init__(self, root_storage_index): + self.root_storage_index = root_storage_index + if root_storage_index is None: + self.root_storage_index_s = "" + else: + self.root_storage_index_s = base32.b2a(root_storage_index) + + self.objects_checked = 0 + self.objects_healthy = 0 + self.objects_unhealthy = 0 + self.objects_unrecoverable = 0 + self.corrupt_shares = [] + self.all_results = {} + self.all_results_by_storage_index = {} + self.stats = {} + + def update_stats(self, new_stats): + self.stats.update(new_stats) + + def get_root_storage_index_string(self): + return self.root_storage_index_s + + def get_corrupt_shares(self): + return self.corrupt_shares + + def get_all_results(self): + return self.all_results + + def get_results_for_storage_index(self, storage_index): + return self.all_results_by_storage_index[storage_index] + + def get_stats(self): + return self.stats + + +class DeepCheckResults(DeepResultsBase): + implements(IDeepCheckResults) + + def add_check(self, r, path): + if not r: + return # non-distributed object, i.e. LIT file + r = ICheckResults(r) + assert isinstance(path, (list, tuple)) + self.objects_checked += 1 + if r.is_healthy(): + self.objects_healthy += 1 + else: + self.objects_unhealthy += 1 + if not r.is_recoverable(): + self.objects_unrecoverable += 1 + self.all_results[tuple(path)] = r + self.all_results_by_storage_index[r.get_storage_index()] = r + self.corrupt_shares.extend(r.get_data()["list-corrupt-shares"]) + + def get_counters(self): + return {"count-objects-checked": self.objects_checked, + "count-objects-healthy": self.objects_healthy, + "count-objects-unhealthy": self.objects_unhealthy, + "count-objects-unrecoverable": self.objects_unrecoverable, + "count-corrupt-shares": len(self.corrupt_shares), + } + + +class DeepCheckAndRepairResults(DeepResultsBase): + implements(IDeepCheckAndRepairResults) + + def __init__(self, root_storage_index): + DeepResultsBase.__init__(self, root_storage_index) + self.objects_healthy_post_repair = 0 + self.objects_unhealthy_post_repair = 0 + self.objects_unrecoverable_post_repair = 0 + self.repairs_attempted = 0 + self.repairs_successful = 0 + self.repairs_unsuccessful = 0 + self.corrupt_shares_post_repair = [] + + def add_check_and_repair(self, r, path): + if not r: + return # non-distributed object, i.e. LIT file + r = ICheckAndRepairResults(r) + assert isinstance(path, (list, tuple)) + pre_repair = r.get_pre_repair_results() + post_repair = r.get_post_repair_results() + self.objects_checked += 1 + if pre_repair.is_healthy(): + self.objects_healthy += 1 + else: + self.objects_unhealthy += 1 + if not pre_repair.is_recoverable(): + self.objects_unrecoverable += 1 + self.corrupt_shares.extend(pre_repair.get_data()["list-corrupt-shares"]) + if r.get_repair_attempted(): + self.repairs_attempted += 1 + if r.get_repair_successful(): + self.repairs_successful += 1 + else: + self.repairs_unsuccessful += 1 + if post_repair.is_healthy(): + self.objects_healthy_post_repair += 1 + else: + self.objects_unhealthy_post_repair += 1 + if not post_repair.is_recoverable(): + self.objects_unrecoverable_post_repair += 1 + self.all_results[tuple(path)] = r + self.all_results_by_storage_index[r.get_storage_index()] = r + self.corrupt_shares_post_repair.extend(post_repair.get_data()["list-corrupt-shares"]) + + def get_counters(self): + return {"count-objects-checked": self.objects_checked, + "count-objects-healthy-pre-repair": self.objects_healthy, + "count-objects-unhealthy-pre-repair": self.objects_unhealthy, + "count-objects-unrecoverable-pre-repair": self.objects_unrecoverable, + "count-objects-healthy-post-repair": self.objects_healthy_post_repair, + "count-objects-unhealthy-post-repair": self.objects_unhealthy_post_repair, + "count-objects-unrecoverable-post-repair": self.objects_unrecoverable_post_repair, + "count-repairs-attempted": self.repairs_attempted, + "count-repairs-successful": self.repairs_successful, + "count-repairs-unsuccessful": self.repairs_unsuccessful, + "count-corrupt-shares-pre-repair": len(self.corrupt_shares), + "count-corrupt-shares-post-repair": len(self.corrupt_shares_post_repair), + } + + def get_remaining_corrupt_shares(self): + return self.corrupt_shares_post_repair diff --git a/src/allmydata/checker_results.py b/src/allmydata/checker_results.py deleted file mode 100644 index bfcc7906..00000000 --- a/src/allmydata/checker_results.py +++ /dev/null @@ -1,222 +0,0 @@ - -from zope.interface import implements -from allmydata.interfaces import ICheckerResults, ICheckAndRepairResults, \ - IDeepCheckResults, IDeepCheckAndRepairResults, IURI -from allmydata.util import base32 - -class CheckerResults: - implements(ICheckerResults) - - def __init__(self, uri, storage_index): - assert IURI.providedBy(uri), uri - self.uri = uri - self.storage_index = storage_index - self.problems = [] - self.data = {"count-corrupt-shares": 0, - "list-corrupt-shares": [], - } - self.summary = "" - self.report = [] - - def set_healthy(self, healthy): - self.healthy = bool(healthy) - if self.healthy: - assert (not hasattr(self, 'recoverable')) or self.recoverable, hasattr(self, 'recoverable') and self.recoverable - self.recoverable = True - self.summary = "healthy" - else: - self.summary = "not healthy" - def set_recoverable(self, recoverable): - self.recoverable = recoverable - if not self.recoverable: - assert (not hasattr(self, 'healthy')) or not self.healthy - self.healthy = False - def set_needs_rebalancing(self, needs_rebalancing): - self.needs_rebalancing_p = bool(needs_rebalancing) - def set_data(self, data): - self.data.update(data) - def set_summary(self, summary): - assert isinstance(summary, str) # should be a single string - self.summary = summary - def set_report(self, report): - assert not isinstance(report, str) # should be list of strings - self.report = report - - def set_servermap(self, smap): - # mutable only - self.servermap = smap - - - def get_storage_index(self): - return self.storage_index - def get_storage_index_string(self): - return base32.b2a(self.storage_index) - def get_uri(self): - return self.uri - - def is_healthy(self): - return self.healthy - def is_recoverable(self): - return self.recoverable - - def needs_rebalancing(self): - return self.needs_rebalancing_p - def get_data(self): - return self.data - - def get_summary(self): - return self.summary - def get_report(self): - return self.report - def get_servermap(self): - return self.servermap - -class CheckAndRepairResults: - implements(ICheckAndRepairResults) - - def __init__(self, storage_index): - self.storage_index = storage_index - self.repair_attempted = False - - def get_storage_index(self): - return self.storage_index - def get_storage_index_string(self): - return base32.b2a(self.storage_index) - def get_repair_attempted(self): - return self.repair_attempted - def get_repair_successful(self): - if not self.repair_attempted: - return False - return self.repair_successful - def get_pre_repair_results(self): - return self.pre_repair_results - def get_post_repair_results(self): - return self.post_repair_results - - -class DeepResultsBase: - - def __init__(self, root_storage_index): - self.root_storage_index = root_storage_index - if root_storage_index is None: - self.root_storage_index_s = "" - else: - self.root_storage_index_s = base32.b2a(root_storage_index) - - self.objects_checked = 0 - self.objects_healthy = 0 - self.objects_unhealthy = 0 - self.objects_unrecoverable = 0 - self.corrupt_shares = [] - self.all_results = {} - self.all_results_by_storage_index = {} - self.stats = {} - - def update_stats(self, new_stats): - self.stats.update(new_stats) - - def get_root_storage_index_string(self): - return self.root_storage_index_s - - def get_corrupt_shares(self): - return self.corrupt_shares - - def get_all_results(self): - return self.all_results - - def get_results_for_storage_index(self, storage_index): - return self.all_results_by_storage_index[storage_index] - - def get_stats(self): - return self.stats - - -class DeepCheckResults(DeepResultsBase): - implements(IDeepCheckResults) - - def add_check(self, r, path): - if not r: - return # non-distributed object, i.e. LIT file - r = ICheckerResults(r) - assert isinstance(path, (list, tuple)) - self.objects_checked += 1 - if r.is_healthy(): - self.objects_healthy += 1 - else: - self.objects_unhealthy += 1 - if not r.is_recoverable(): - self.objects_unrecoverable += 1 - self.all_results[tuple(path)] = r - self.all_results_by_storage_index[r.get_storage_index()] = r - self.corrupt_shares.extend(r.get_data()["list-corrupt-shares"]) - - def get_counters(self): - return {"count-objects-checked": self.objects_checked, - "count-objects-healthy": self.objects_healthy, - "count-objects-unhealthy": self.objects_unhealthy, - "count-objects-unrecoverable": self.objects_unrecoverable, - "count-corrupt-shares": len(self.corrupt_shares), - } - - -class DeepCheckAndRepairResults(DeepResultsBase): - implements(IDeepCheckAndRepairResults) - - def __init__(self, root_storage_index): - DeepResultsBase.__init__(self, root_storage_index) - self.objects_healthy_post_repair = 0 - self.objects_unhealthy_post_repair = 0 - self.objects_unrecoverable_post_repair = 0 - self.repairs_attempted = 0 - self.repairs_successful = 0 - self.repairs_unsuccessful = 0 - self.corrupt_shares_post_repair = [] - - def add_check_and_repair(self, r, path): - if not r: - return # non-distributed object, i.e. LIT file - r = ICheckAndRepairResults(r) - assert isinstance(path, (list, tuple)) - pre_repair = r.get_pre_repair_results() - post_repair = r.get_post_repair_results() - self.objects_checked += 1 - if pre_repair.is_healthy(): - self.objects_healthy += 1 - else: - self.objects_unhealthy += 1 - if not pre_repair.is_recoverable(): - self.objects_unrecoverable += 1 - self.corrupt_shares.extend(pre_repair.get_data()["list-corrupt-shares"]) - if r.get_repair_attempted(): - self.repairs_attempted += 1 - if r.get_repair_successful(): - self.repairs_successful += 1 - else: - self.repairs_unsuccessful += 1 - if post_repair.is_healthy(): - self.objects_healthy_post_repair += 1 - else: - self.objects_unhealthy_post_repair += 1 - if not post_repair.is_recoverable(): - self.objects_unrecoverable_post_repair += 1 - self.all_results[tuple(path)] = r - self.all_results_by_storage_index[r.get_storage_index()] = r - self.corrupt_shares_post_repair.extend(post_repair.get_data()["list-corrupt-shares"]) - - def get_counters(self): - return {"count-objects-checked": self.objects_checked, - "count-objects-healthy-pre-repair": self.objects_healthy, - "count-objects-unhealthy-pre-repair": self.objects_unhealthy, - "count-objects-unrecoverable-pre-repair": self.objects_unrecoverable, - "count-objects-healthy-post-repair": self.objects_healthy_post_repair, - "count-objects-unhealthy-post-repair": self.objects_unhealthy_post_repair, - "count-objects-unrecoverable-post-repair": self.objects_unrecoverable_post_repair, - "count-repairs-attempted": self.repairs_attempted, - "count-repairs-successful": self.repairs_successful, - "count-repairs-unsuccessful": self.repairs_unsuccessful, - "count-corrupt-shares-pre-repair": len(self.corrupt_shares), - "count-corrupt-shares-post-repair": len(self.corrupt_shares_post_repair), - } - - def get_remaining_corrupt_shares(self): - return self.corrupt_shares_post_repair diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index 9f2e1d25..9c38f86d 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -9,7 +9,7 @@ from allmydata.mutable.filenode import MutableFileNode from allmydata.interfaces import IMutableFileNode, IDirectoryNode,\ IURI, IFileNode, IMutableFileURI, IFilesystemNode, \ ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable -from allmydata.checker_results import DeepCheckResults, \ +from allmydata.check_results import DeepCheckResults, \ DeepCheckAndRepairResults from allmydata.monitor import Monitor from allmydata.util import hashutil, mathutil, base32, log diff --git a/src/allmydata/immutable/checker.py b/src/allmydata/immutable/checker.py index bcccd8a9..290d8cfd 100644 --- a/src/allmydata/immutable/checker.py +++ b/src/allmydata/immutable/checker.py @@ -1,6 +1,6 @@ from foolscap import DeadReferenceError from allmydata import hashtree -from allmydata.checker_results import CheckerResults +from allmydata.check_results import CheckerResults from allmydata.immutable import download from allmydata.uri import CHKFileVerifierURI from allmydata.util.assertutil import precondition diff --git a/src/allmydata/immutable/filenode.py b/src/allmydata/immutable/filenode.py index e8e9d96b..45a4a600 100644 --- a/src/allmydata/immutable/filenode.py +++ b/src/allmydata/immutable/filenode.py @@ -10,7 +10,7 @@ from allmydata.interfaces import IFileNode, IFileURI, ICheckable, \ IDownloadTarget from allmydata.util import log, base32 from allmydata.immutable.checker import Checker -from allmydata.checker_results import CheckAndRepairResults +from allmydata.check_results import CheckAndRepairResults from allmydata.immutable.repairer import Repairer from allmydata.immutable import download diff --git a/src/allmydata/immutable/repairer.py b/src/allmydata/immutable/repairer.py index 4f0e3f79..9d41529d 100644 --- a/src/allmydata/immutable/repairer.py +++ b/src/allmydata/immutable/repairer.py @@ -1,6 +1,6 @@ from twisted.internet import defer from allmydata import storage -from allmydata.checker_results import CheckerResults, CheckAndRepairResults +from allmydata.check_results import CheckerResults, CheckAndRepairResults from allmydata.immutable import download from allmydata.util import nummedobj from allmydata.util.assertutil import precondition diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index a176e01c..bfd0477e 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -1515,7 +1515,7 @@ class ICheckable(Interface): """Check upon my health, optionally repairing any problems. This returns a Deferred that fires with an instance that provides - ICheckerResults, or None if the object is non-distributed (i.e. LIT + ICheckResults, or None if the object is non-distributed (i.e. LIT files). The monitor will be checked periodically to see if the operation has @@ -1585,7 +1585,7 @@ class IDeepCheckable(Interface): IDeepCheckAndRepairResults object. """ -class ICheckerResults(Interface): +class ICheckResults(Interface): """I contain the detailed results of a check/verify operation. """ @@ -1704,10 +1704,10 @@ class ICheckAndRepairResults(Interface): was fully healthy afterwards. False if no repair was attempted or if a repair attempt failed.""" def get_pre_repair_results(): - """Return an ICheckerResults instance that describes the state of the + """Return an ICheckResults instance that describes the state of the file/dir before any repair was attempted.""" def get_post_repair_results(): - """Return an ICheckerResults instance that describes the state of the + """Return an ICheckResults instance that describes the state of the file/dir after any repair was attempted. If no repair was attempted, the pre-repair and post-repair results will be identical.""" @@ -1741,11 +1741,11 @@ class IDeepCheckResults(Interface): """ def get_all_results(): """Return a dictionary mapping pathname (a tuple of strings, ready to - be slash-joined) to an ICheckerResults instance, one for each object + be slash-joined) to an ICheckResults instance, one for each object that was checked.""" def get_results_for_storage_index(storage_index): - """Retrive the ICheckerResults instance for the given (binary) + """Retrive the ICheckResults instance for the given (binary) storage index. Raises KeyError if there are no results for that storage index.""" @@ -1820,15 +1820,15 @@ class IDeepCheckAndRepairResults(Interface): class IRepairable(Interface): - def repair(checker_results): + def repair(check_results): """Attempt to repair the given object. Returns a Deferred that fires with a IRepairResults object. - I must be called with an object that implements ICheckerResults, as + I must be called with an object that implements ICheckResults, as proof that you have actually discovered a problem with this file. I will use the data in the checker results to guide the repair process, such as which servers provided bad data and should therefore be - avoided. The ICheckerResults object is inside the + avoided. The ICheckResults object is inside the ICheckAndRepairResults object, which is returned by the ICheckable.check() method:: diff --git a/src/allmydata/mutable/checker.py b/src/allmydata/mutable/checker.py index 31e8744c..ed9ddb3b 100644 --- a/src/allmydata/mutable/checker.py +++ b/src/allmydata/mutable/checker.py @@ -4,7 +4,7 @@ from twisted.python import failure from allmydata import hashtree from allmydata.uri import from_string from allmydata.util import hashutil, base32, idlib, log -from allmydata.checker_results import CheckAndRepairResults, CheckerResults +from allmydata.check_results import CheckAndRepairResults, CheckerResults from common import MODE_CHECK, CorruptShareError from servermap import ServerMap, ServermapUpdater diff --git a/src/allmydata/mutable/filenode.py b/src/allmydata/mutable/filenode.py index 8caa1454..1951ff1d 100644 --- a/src/allmydata/mutable/filenode.py +++ b/src/allmydata/mutable/filenode.py @@ -6,7 +6,7 @@ from zope.interface import implements from twisted.internet import defer, reactor from foolscap.eventual import eventually from allmydata.interfaces import IMutableFileNode, IMutableFileURI, \ - ICheckable, ICheckerResults, NotEnoughSharesError + ICheckable, ICheckResults, NotEnoughSharesError from allmydata.util import hashutil, log from allmydata.util.assertutil import precondition from allmydata.uri import WriteableSSKFileURI @@ -253,9 +253,9 @@ class MutableFileNode: ################################# # IRepairable - def repair(self, checker_results, force=False): - assert ICheckerResults(checker_results) - r = Repairer(self, checker_results) + def repair(self, check_results, force=False): + assert ICheckResults(check_results) + r = Repairer(self, check_results) d = r.start(force) return d diff --git a/src/allmydata/mutable/repairer.py b/src/allmydata/mutable/repairer.py index f3ae1ce6..b7d49bcd 100644 --- a/src/allmydata/mutable/repairer.py +++ b/src/allmydata/mutable/repairer.py @@ -1,6 +1,6 @@ from zope.interface import implements -from allmydata.interfaces import IRepairResults, ICheckerResults +from allmydata.interfaces import IRepairResults, ICheckResults class RepairResults: implements(IRepairResults) @@ -15,10 +15,10 @@ class MustForceRepairError(Exception): pass class Repairer: - def __init__(self, node, checker_results): + def __init__(self, node, check_results): self.node = node - self.checker_results = ICheckerResults(checker_results) - assert checker_results.storage_index == self.node.get_storage_index() + self.check_results = ICheckResults(check_results) + assert check_results.storage_index == self.node.get_storage_index() def start(self, force=False): # download, then re-publish. If a server had a bad share, try to @@ -47,7 +47,7 @@ class Repairer: # old shares: replace old shares with the latest version # bogus shares (bad sigs): replace the bad one with a good one - smap = self.checker_results.get_servermap() + smap = self.check_results.get_servermap() if smap.unrecoverable_newer_versions(): if not force: diff --git a/src/allmydata/test/common.py b/src/allmydata/test/common.py index e21cccc8..5ed13d0b 100644 --- a/src/allmydata/test/common.py +++ b/src/allmydata/test/common.py @@ -11,7 +11,7 @@ from allmydata import uri, dirnode, client from allmydata.introducer.server import IntroducerNode from allmydata.interfaces import IURI, IMutableFileNode, IFileNode, \ FileTooLargeError, NotEnoughSharesError, ICheckable -from allmydata.checker_results import CheckerResults, CheckAndRepairResults, \ +from allmydata.check_results import CheckerResults, CheckAndRepairResults, \ DeepCheckResults, DeepCheckAndRepairResults from allmydata.mutable.common import CorruptShareError from allmydata.storage import storage_index_to_dir diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py index 916c9eaa..52e2cfd1 100644 --- a/src/allmydata/test/test_dirnode.py +++ b/src/allmydata/test/test_dirnode.py @@ -15,7 +15,7 @@ from allmydata.util import hashutil, base32 from allmydata.monitor import Monitor from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \ FakeDirectoryNode, create_chk_filenode, ErrorMixin, SystemTestMixin -from allmydata.checker_results import CheckerResults, CheckAndRepairResults +from allmydata.check_results import CheckerResults, CheckAndRepairResults import common_util as testutil # to test dirnode.py, we want to construct a tree of real DirectoryNodes that diff --git a/src/allmydata/test/test_immutable.py b/src/allmydata/test/test_immutable.py index 34ee3a84..7c7923db 100644 --- a/src/allmydata/test/test_immutable.py +++ b/src/allmydata/test/test_immutable.py @@ -1,5 +1,6 @@ from allmydata.test.common import SystemTestMixin, ShareManglingMixin from allmydata.monitor import Monitor +from allmydata import check_results from allmydata.interfaces import IURI, NotEnoughSharesError from allmydata.immutable import upload from allmydata.util import log @@ -794,8 +795,11 @@ class Test(ShareManglingMixin, unittest.TestCase): d2 = filenode.check_and_repair(Monitor(), verify=False) def _after_repair(checkandrepairresults): + assert isinstance(checkandrepairresults, check_results.CheckAndRepairResults), checkandrepairresults prerepairres = checkandrepairresults.get_pre_repair_results() + assert isinstance(prerepairres, check_results.CheckResults), prerepairres postrepairres = checkandrepairresults.get_post_repair_results() + assert isinstance(postrepairres, check_results.CheckResults), postrepairres after_repair_reads = self._count_reads() after_repair_allocates = self._count_allocates() diff --git a/src/allmydata/test/test_system.py b/src/allmydata/test/test_system.py index 0dad06fc..32968192 100644 --- a/src/allmydata/test/test_system.py +++ b/src/allmydata/test/test_system.py @@ -14,7 +14,7 @@ from allmydata.util import idlib, mathutil from allmydata.util import log, base32 from allmydata.scripts import runner from allmydata.interfaces import IDirectoryNode, IFileNode, IFileURI, \ - ICheckerResults, ICheckAndRepairResults, IDeepCheckResults, \ + ICheckResults, ICheckAndRepairResults, IDeepCheckResults, \ IDeepCheckAndRepairResults, NoSuchChildError, NotEnoughSharesError from allmydata.monitor import Monitor, OperationCancelledError from allmydata.mutable.common import NotMutableError @@ -2039,7 +2039,7 @@ class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): return d def check_is_healthy(self, cr, n, where, incomplete=False): - self.failUnless(ICheckerResults.providedBy(cr), where) + self.failUnless(ICheckResults.providedBy(cr), where) self.failUnless(cr.is_healthy(), where) self.failUnlessEqual(cr.get_storage_index(), n.get_storage_index(), where) @@ -2622,7 +2622,7 @@ class DeepCheckWebBad(DeepCheckBase, unittest.TestCase): def check_is_healthy(self, cr, where): try: - self.failUnless(ICheckerResults.providedBy(cr), (cr, type(cr), where)) + self.failUnless(ICheckResults.providedBy(cr), (cr, type(cr), where)) self.failUnless(cr.is_healthy(), (cr.get_report(), cr.is_healthy(), cr.get_summary(), where)) self.failUnless(cr.is_recoverable(), where) d = cr.get_data() @@ -2634,7 +2634,7 @@ class DeepCheckWebBad(DeepCheckBase, unittest.TestCase): raise def check_is_missing_shares(self, cr, where): - self.failUnless(ICheckerResults.providedBy(cr), where) + self.failUnless(ICheckResults.providedBy(cr), where) self.failIf(cr.is_healthy(), where) self.failUnless(cr.is_recoverable(), where) d = cr.get_data() @@ -2644,7 +2644,7 @@ class DeepCheckWebBad(DeepCheckBase, unittest.TestCase): def check_has_corrupt_shares(self, cr, where): # by "corrupt-shares" we mean the file is still recoverable - self.failUnless(ICheckerResults.providedBy(cr), where) + self.failUnless(ICheckResults.providedBy(cr), where) d = cr.get_data() self.failIf(cr.is_healthy(), (where, cr)) self.failUnless(cr.is_recoverable(), where) @@ -2655,7 +2655,7 @@ class DeepCheckWebBad(DeepCheckBase, unittest.TestCase): return cr def check_is_unrecoverable(self, cr, where): - self.failUnless(ICheckerResults.providedBy(cr), where) + self.failUnless(ICheckResults.providedBy(cr), where) d = cr.get_data() self.failIf(cr.is_healthy(), where) self.failIf(cr.is_recoverable(), where) diff --git a/src/allmydata/web/check-results.xhtml b/src/allmydata/web/check-results.xhtml new file mode 100644 index 00000000..0f43b9d7 --- /dev/null +++ b/src/allmydata/web/check-results.xhtml @@ -0,0 +1,24 @@ + + + AllMyData - Tahoe - Check Results + + + + + + +

File Check Results for SI=

+ +
+ +
+ +
+ +
+ +
+ + + diff --git a/src/allmydata/web/check_results.py b/src/allmydata/web/check_results.py new file mode 100644 index 00000000..529965ef --- /dev/null +++ b/src/allmydata/web/check_results.py @@ -0,0 +1,598 @@ + +import time +import simplejson +from nevow import rend, inevow, tags as T +from twisted.web import http, html +from allmydata.web.common import getxmlfile, get_arg, get_root, \ + IClient, WebError +from allmydata.web.operations import ReloadMixin +from allmydata.interfaces import ICheckAndRepairResults, ICheckResults +from allmydata.util import base32, idlib + +class ResultsBase: + def _render_results(self, ctx, cr): + assert ICheckResults(cr) + c = IClient(ctx) + data = cr.get_data() + r = [] + def add(name, value): + r.append(T.li[name + ": ", value]) + + add("Report", T.pre["\n".join(self._html(cr.get_report()))]) + add("Share Counts", + "need %d-of-%d, have %d" % (data["count-shares-needed"], + data["count-shares-expected"], + data["count-shares-good"])) + add("Hosts with good shares", data["count-good-share-hosts"]) + + if data["list-corrupt-shares"]: + badsharemap = [] + for (serverid, si, shnum) in data["list-corrupt-shares"]: + nickname = c.get_nickname_for_peerid(serverid) + badsharemap.append(T.tr[T.td["sh#%d" % shnum], + T.td[T.tt[base32.b2a(serverid)], + " (", nickname, ")"], + ]) + add("Corrupt shares", T.table(border="1")[badsharemap]) + else: + add("Corrupt shares", "none") + + add("Wrong Shares", data["count-wrong-shares"]) + + sharemap = [] + servers = {} + + for shareid in sorted(data["sharemap"].keys()): + serverids = data["sharemap"][shareid] + for i,serverid in enumerate(serverids): + if serverid not in servers: + servers[serverid] = [] + servers[serverid].append(shareid) + shareid_s = "" + if i == 0: + shareid_s = shareid + nickname = c.get_nickname_for_peerid(serverid) + sharemap.append(T.tr[T.td[shareid_s], + T.td[T.tt[base32.b2a(serverid)], + " (", nickname, ")"], + ]) + add("Good Shares (sorted in share order)", + T.table(border="1")[sharemap]) + + + add("Recoverable Versions", data["count-recoverable-versions"]) + add("Unrecoverable Versions", data["count-unrecoverable-versions"]) + + # this table is sorted by permuted order + permuted_peer_ids = [peerid + for (peerid, rref) + in c.get_permuted_peers("storage", + cr.get_storage_index())] + + num_shares_left = sum([len(shares) for shares in servers.values()]) + servermap = [] + for serverid in permuted_peer_ids: + nickname = c.get_nickname_for_peerid(serverid) + shareids = servers.get(serverid, []) + shareids.reverse() + shareids_s = [ T.tt[shareid, " "] for shareid in shareids ] + servermap.append(T.tr[T.td[T.tt[base32.b2a(serverid)], + " (", nickname, ")"], + T.td[shareids_s] ]) + num_shares_left -= len(shareids) + if not num_shares_left: + break + add("Share Balancing (servers in permuted order)", + T.table(border="1")[servermap]) + + return T.ul[r] + + def _json_check_and_repair_results(self, r): + data = {} + data["storage-index"] = r.get_storage_index_string() + data["repair-attempted"] = r.get_repair_attempted() + data["repair-successful"] = r.get_repair_successful() + pre = r.get_pre_repair_results() + data["pre-repair-results"] = self._json_check_results(pre) + post = r.get_post_repair_results() + data["post-repair-results"] = self._json_check_results(post) + return data + + def _json_check_results(self, r): + data = {} + data["storage-index"] = r.get_storage_index_string() + data["summary"] = r.get_summary() + data["results"] = self._json_check_counts(r.get_data()) + data["results"]["needs-rebalancing"] = r.needs_rebalancing() + data["results"]["healthy"] = r.is_healthy() + data["results"]["recoverable"] = r.is_recoverable() + return data + + def _json_check_counts(self, d): + r = {} + r["count-shares-good"] = d["count-shares-good"] + r["count-shares-needed"] = d["count-shares-needed"] + r["count-shares-expected"] = d["count-shares-expected"] + r["count-good-share-hosts"] = d["count-good-share-hosts"] + r["count-corrupt-shares"] = d["count-corrupt-shares"] + r["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid), + base32.b2a(si), shnum) + for (serverid, si, shnum) + in d["list-corrupt-shares"] ] + r["servers-responding"] = [idlib.nodeid_b2a(serverid) + for serverid in d["servers-responding"]] + sharemap = {} + for (shareid, serverids) in d["sharemap"].items(): + sharemap[shareid] = [idlib.nodeid_b2a(serverid) + for serverid in serverids] + r["sharemap"] = sharemap + + r["count-wrong-shares"] = d["count-wrong-shares"] + r["count-recoverable-versions"] = d["count-recoverable-versions"] + r["count-unrecoverable-versions"] = d["count-unrecoverable-versions"] + + return r + + def _html(self, s): + if isinstance(s, (str, unicode)): + return html.escape(s) + assert isinstance(s, (list, tuple)) + return [html.escape(w) for w in s] + + def want_json(self, ctx): + output = get_arg(inevow.IRequest(ctx), "output", "").lower() + if output.lower() == "json": + return True + return False + + def _render_si_link(self, ctx, storage_index): + si_s = base32.b2a(storage_index) + root = get_root(ctx) + req = inevow.IRequest(ctx) + ophandle = req.prepath[-1] + target = "%s/operations/%s/%s" % (get_root(ctx), ophandle, si_s) + output = get_arg(ctx, "output") + if output: + target = target + "?output=%s" % output + return T.a(href=target)[si_s] + +class LiteralCheckerResults(rend.Page, ResultsBase): + docFactory = getxmlfile("literal-check-results.xhtml") + + def renderHTTP(self, ctx): + if self.want_json(ctx): + return self.json(ctx) + return rend.Page.renderHTTP(self, ctx) + + def json(self, ctx): + inevow.IRequest(ctx).setHeader("content-type", "text/plain") + data = {"storage-index": "", + "results": {"healthy": True}, + } + return simplejson.dumps(data, indent=1) + "\n" + +class CheckerBase: + + def renderHTTP(self, ctx): + if self.want_json(ctx): + return self.json(ctx) + return rend.Page.renderHTTP(self, ctx) + + def render_storage_index(self, ctx, data): + return self.r.get_storage_index_string() + + def render_return(self, ctx, data): + req = inevow.IRequest(ctx) + return_to = get_arg(req, "return_to", None) + if return_to: + return T.div[T.a(href=return_to)["Return to parent directory"]] + return "" + +class CheckerResults(CheckerBase, rend.Page, ResultsBase): + docFactory = getxmlfile("check-results.xhtml") + + def __init__(self, results): + self.r = ICheckResults(results) + + def json(self, ctx): + inevow.IRequest(ctx).setHeader("content-type", "text/plain") + data = self._json_check_results(self.r) + return simplejson.dumps(data, indent=1) + "\n" + + def render_summary(self, ctx, data): + results = [] + if self.r.is_healthy(): + results.append("Healthy") + elif self.r.is_recoverable(): + results.append("Not Healthy!") + else: + results.append("Not Recoverable!") + results.append(" : ") + results.append(self._html(self.r.get_summary())) + return ctx.tag[results] + + def render_repair(self, ctx, data): + if self.r.is_healthy(): + return "" + repair = T.form(action=".", method="post", + enctype="multipart/form-data")[ + T.fieldset[ + T.input(type="hidden", name="t", value="check"), + T.input(type="hidden", name="repair", value="true"), + T.input(type="submit", value="Repair"), + ]] + return ctx.tag[repair] + + def render_rebalance(self, ctx, data): + if self.r.needs_rebalancing(): + return ctx.tag["(needs rebalancing)"] + return ctx.tag["(does not need rebalancing)"] + + def render_results(self, ctx, data): + cr = self._render_results(ctx, self.r) + return ctx.tag[cr] + +class CheckAndRepairResults(CheckerBase, rend.Page, ResultsBase): + docFactory = getxmlfile("check-and-repair-results.xhtml") + + def __init__(self, results): + self.r = ICheckAndRepairResults(results) + + def json(self, ctx): + inevow.IRequest(ctx).setHeader("content-type", "text/plain") + data = self._json_check_and_repair_results(self.r) + return simplejson.dumps(data, indent=1) + "\n" + + def render_summary(self, ctx, data): + cr = self.r.get_post_repair_results() + results = [] + if cr.is_healthy(): + results.append("Healthy") + elif cr.is_recoverable(): + results.append("Not Healthy!") + else: + results.append("Not Recoverable!") + results.append(" : ") + results.append(self._html(cr.get_summary())) + return ctx.tag[results] + + def render_repair_results(self, ctx, data): + if self.r.get_repair_attempted(): + if self.r.get_repair_successful(): + return ctx.tag["Repair successful"] + else: + return ctx.tag["Repair unsuccessful"] + return ctx.tag["No repair necessary"] + + def render_post_repair_results(self, ctx, data): + cr = self._render_results(ctx, self.r.get_post_repair_results()) + return ctx.tag[cr] + + def render_maybe_pre_repair_results(self, ctx, data): + if self.r.get_repair_attempted(): + cr = self._render_results(ctx, self.r.get_pre_repair_results()) + return ctx.tag[T.div["Pre-Repair Checker Results:"], cr] + return "" + + +class DeepCheckResults(rend.Page, ResultsBase, ReloadMixin): + docFactory = getxmlfile("deep-check-results.xhtml") + + def __init__(self, monitor): + self.monitor = monitor + + def childFactory(self, ctx, name): + if not name: + return self + # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information + # about a specific file or directory that was checked + si = base32.a2b(name) + r = self.monitor.get_status() + try: + return CheckerResults(r.get_results_for_storage_index(si)) + except KeyError: + raise WebError("No detailed results for SI %s" % html.escape(name), + http.NOT_FOUND) + + def renderHTTP(self, ctx): + if self.want_json(ctx): + return self.json(ctx) + return rend.Page.renderHTTP(self, ctx) + + def json(self, ctx): + inevow.IRequest(ctx).setHeader("content-type", "text/plain") + data = {} + data["finished"] = self.monitor.is_finished() + res = self.monitor.get_status() + data["root-storage-index"] = res.get_root_storage_index_string() + c = res.get_counters() + data["count-objects-checked"] = c["count-objects-checked"] + data["count-objects-healthy"] = c["count-objects-healthy"] + data["count-objects-unhealthy"] = c["count-objects-unhealthy"] + data["count-corrupt-shares"] = c["count-corrupt-shares"] + data["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid), + base32.b2a(storage_index), + shnum) + for (serverid, storage_index, shnum) + in res.get_corrupt_shares() ] + data["list-unhealthy-files"] = [ (path_t, self._json_check_results(r)) + for (path_t, r) + in res.get_all_results().items() + if not r.is_healthy() ] + data["stats"] = res.get_stats() + return simplejson.dumps(data, indent=1) + "\n" + + def render_root_storage_index(self, ctx, data): + return self.monitor.get_status().get_root_storage_index_string() + + def data_objects_checked(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-checked"] + def data_objects_healthy(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-healthy"] + def data_objects_unhealthy(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-unhealthy"] + def data_objects_unrecoverable(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-unrecoverable"] + + def data_count_corrupt_shares(self, ctx, data): + return self.monitor.get_status().get_counters()["count-corrupt-shares"] + + def render_problems_p(self, ctx, data): + c = self.monitor.get_status().get_counters() + if c["count-objects-unhealthy"]: + return ctx.tag + return "" + + def data_problems(self, ctx, data): + all_objects = self.monitor.get_status().get_all_results() + for path in sorted(all_objects.keys()): + cr = all_objects[path] + assert ICheckResults.providedBy(cr) + if not cr.is_healthy(): + yield path, cr + + def render_problem(self, ctx, data): + path, cr = data + summary_text = "" + summary = cr.get_summary() + if summary: + summary_text = ": " + summary + summary_text += " [SI: %s]" % cr.get_storage_index_string() + return ctx.tag["/".join(self._html(path)), self._html(summary_text)] + + + def render_servers_with_corrupt_shares_p(self, ctx, data): + if self.monitor.get_status().get_counters()["count-corrupt-shares"]: + return ctx.tag + return "" + + def data_servers_with_corrupt_shares(self, ctx, data): + servers = [serverid + for (serverid, storage_index, sharenum) + in self.monitor.get_status().get_corrupt_shares()] + servers.sort() + return servers + + def render_server_problem(self, ctx, data): + serverid = data + data = [idlib.shortnodeid_b2a(serverid)] + c = IClient(ctx) + nickname = c.get_nickname_for_peerid(serverid) + if nickname: + data.append(" (%s)" % self._html(nickname)) + return ctx.tag[data] + + + def render_corrupt_shares_p(self, ctx, data): + if self.monitor.get_status().get_counters()["count-corrupt-shares"]: + return ctx.tag + return "" + def data_corrupt_shares(self, ctx, data): + return self.monitor.get_status().get_corrupt_shares() + def render_share_problem(self, ctx, data): + serverid, storage_index, sharenum = data + nickname = IClient(ctx).get_nickname_for_peerid(serverid) + ctx.fillSlots("serverid", idlib.shortnodeid_b2a(serverid)) + if nickname: + ctx.fillSlots("nickname", self._html(nickname)) + ctx.fillSlots("si", self._render_si_link(ctx, storage_index)) + ctx.fillSlots("shnum", str(sharenum)) + return ctx.tag + + def render_return(self, ctx, data): + req = inevow.IRequest(ctx) + return_to = get_arg(req, "return_to", None) + if return_to: + return T.div[T.a(href=return_to)["Return to parent directory"]] + return "" + + def data_all_objects(self, ctx, data): + r = self.monitor.get_status().get_all_results() + for path in sorted(r.keys()): + yield (path, r[path]) + + def render_object(self, ctx, data): + path, r = data + if path: + pathstring = "/".join(self._html(path)) + else: + pathstring = "" + ctx.fillSlots("path", pathstring) + ctx.fillSlots("healthy", str(r.is_healthy())) + ctx.fillSlots("recoverable", str(r.is_recoverable())) + storage_index = r.get_storage_index() + ctx.fillSlots("storage_index", self._render_si_link(ctx, storage_index)) + ctx.fillSlots("summary", self._html(r.get_summary())) + return ctx.tag + + def render_runtime(self, ctx, data): + req = inevow.IRequest(ctx) + runtime = time.time() - req.processing_started_timestamp + return ctx.tag["runtime: %s seconds" % runtime] + +class DeepCheckAndRepairResults(rend.Page, ResultsBase, ReloadMixin): + docFactory = getxmlfile("deep-check-and-repair-results.xhtml") + + def __init__(self, monitor): + #assert IDeepCheckAndRepairResults(results) + #self.r = results + self.monitor = monitor + + def renderHTTP(self, ctx): + if self.want_json(ctx): + return self.json(ctx) + return rend.Page.renderHTTP(self, ctx) + + def json(self, ctx): + inevow.IRequest(ctx).setHeader("content-type", "text/plain") + res = self.monitor.get_status() + data = {} + data["finished"] = self.monitor.is_finished() + data["root-storage-index"] = res.get_root_storage_index_string() + c = res.get_counters() + data["count-objects-checked"] = c["count-objects-checked"] + + data["count-objects-healthy-pre-repair"] = c["count-objects-healthy-pre-repair"] + data["count-objects-unhealthy-pre-repair"] = c["count-objects-unhealthy-pre-repair"] + data["count-objects-healthy-post-repair"] = c["count-objects-healthy-post-repair"] + data["count-objects-unhealthy-post-repair"] = c["count-objects-unhealthy-post-repair"] + + data["count-repairs-attempted"] = c["count-repairs-attempted"] + data["count-repairs-successful"] = c["count-repairs-successful"] + data["count-repairs-unsuccessful"] = c["count-repairs-unsuccessful"] + + data["count-corrupt-shares-pre-repair"] = c["count-corrupt-shares-pre-repair"] + data["count-corrupt-shares-post-repair"] = c["count-corrupt-shares-pre-repair"] + + data["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid), + base32.b2a(storage_index), + shnum) + for (serverid, storage_index, shnum) + in res.get_corrupt_shares() ] + + remaining_corrupt = [ (idlib.nodeid_b2a(serverid), + base32.b2a(storage_index), + shnum) + for (serverid, storage_index, shnum) + in res.get_remaining_corrupt_shares() ] + data["list-remaining-corrupt-shares"] = remaining_corrupt + + unhealthy = [ (path_t, + self._json_check_results(crr.get_pre_repair_results())) + for (path_t, crr) + in res.get_all_results().items() + if not crr.get_pre_repair_results().is_healthy() ] + data["list-unhealthy-files"] = unhealthy + data["stats"] = res.get_stats() + return simplejson.dumps(data, indent=1) + "\n" + + def render_root_storage_index(self, ctx, data): + return self.monitor.get_status().get_root_storage_index_string() + + def data_objects_checked(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-checked"] + + def data_objects_healthy(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-healthy-pre-repair"] + def data_objects_unhealthy(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-unhealthy-pre-repair"] + def data_corrupt_shares(self, ctx, data): + return self.monitor.get_status().get_counters()["count-corrupt-shares-pre-repair"] + + def data_repairs_attempted(self, ctx, data): + return self.monitor.get_status().get_counters()["count-repairs-attempted"] + def data_repairs_successful(self, ctx, data): + return self.monitor.get_status().get_counters()["count-repairs-successful"] + def data_repairs_unsuccessful(self, ctx, data): + return self.monitor.get_status().get_counters()["count-repairs-unsuccessful"] + + def data_objects_healthy_post(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-healthy-post-repair"] + def data_objects_unhealthy_post(self, ctx, data): + return self.monitor.get_status().get_counters()["count-objects-unhealthy-post-repair"] + def data_corrupt_shares_post(self, ctx, data): + return self.monitor.get_status().get_counters()["count-corrupt-shares-post-repair"] + + def render_pre_repair_problems_p(self, ctx, data): + c = self.monitor.get_status().get_counters() + if c["count-objects-unhealthy-pre-repair"]: + return ctx.tag + return "" + + def data_pre_repair_problems(self, ctx, data): + all_objects = self.monitor.get_status().get_all_results() + for path in sorted(all_objects.keys()): + r = all_objects[path] + assert ICheckAndRepairResults.providedBy(r) + cr = r.get_pre_repair_results() + if not cr.is_healthy(): + yield path, cr + + def render_problem(self, ctx, data): + path, cr = data + return ["/".join(self._html(path)), ": ", self._html(cr.get_summary())] + + def render_post_repair_problems_p(self, ctx, data): + c = self.monitor.get_status().get_counters() + if (c["count-objects-unhealthy-post-repair"] + or c["count-corrupt-shares-post-repair"]): + return ctx.tag + return "" + + def data_post_repair_problems(self, ctx, data): + all_objects = self.monitor.get_status().get_all_results() + for path in sorted(all_objects.keys()): + r = all_objects[path] + assert ICheckAndRepairResults.providedBy(r) + cr = r.get_post_repair_results() + if not cr.is_healthy(): + yield path, cr + + def render_servers_with_corrupt_shares_p(self, ctx, data): + if self.monitor.get_status().get_counters()["count-corrupt-shares-pre-repair"]: + return ctx.tag + return "" + def data_servers_with_corrupt_shares(self, ctx, data): + return [] # TODO + def render_server_problem(self, ctx, data): + pass + + + def render_remaining_corrupt_shares_p(self, ctx, data): + if self.monitor.get_status().get_counters()["count-corrupt-shares-post-repair"]: + return ctx.tag + return "" + def data_post_repair_corrupt_shares(self, ctx, data): + return [] # TODO + + def render_share_problem(self, ctx, data): + pass + + + def render_return(self, ctx, data): + req = inevow.IRequest(ctx) + return_to = get_arg(req, "return_to", None) + if return_to: + return T.div[T.a(href=return_to)["Return to parent directory"]] + return "" + + def data_all_objects(self, ctx, data): + r = self.monitor.get_status().get_all_results() + for path in sorted(r.keys()): + yield (path, r[path]) + + def render_object(self, ctx, data): + path, r = data + ctx.fillSlots("path", "/".join(self._html(path))) + ctx.fillSlots("healthy_pre_repair", + str(r.get_pre_repair_results().is_healthy())) + ctx.fillSlots("healthy_post_repair", + str(r.get_post_repair_results().is_healthy())) + ctx.fillSlots("summary", + self._html(r.get_pre_repair_results().get_summary())) + return ctx.tag + + def render_runtime(self, ctx, data): + req = inevow.IRequest(ctx) + runtime = time.time() - req.processing_started_timestamp + return ctx.tag["runtime: %s seconds" % runtime] diff --git a/src/allmydata/web/checker-results.xhtml b/src/allmydata/web/checker-results.xhtml deleted file mode 100644 index 0f43b9d7..00000000 --- a/src/allmydata/web/checker-results.xhtml +++ /dev/null @@ -1,24 +0,0 @@ - - - AllMyData - Tahoe - Check Results - - - - - - -

File Check Results for SI=

- -
- -
- -
- -
- -
- - - diff --git a/src/allmydata/web/checker_results.py b/src/allmydata/web/checker_results.py deleted file mode 100644 index 12fd955d..00000000 --- a/src/allmydata/web/checker_results.py +++ /dev/null @@ -1,598 +0,0 @@ - -import time -import simplejson -from nevow import rend, inevow, tags as T -from twisted.web import http, html -from allmydata.web.common import getxmlfile, get_arg, get_root, \ - IClient, WebError -from allmydata.web.operations import ReloadMixin -from allmydata.interfaces import ICheckAndRepairResults, ICheckerResults -from allmydata.util import base32, idlib - -class ResultsBase: - def _render_results(self, ctx, cr): - assert ICheckerResults(cr) - c = IClient(ctx) - data = cr.get_data() - r = [] - def add(name, value): - r.append(T.li[name + ": ", value]) - - add("Report", T.pre["\n".join(self._html(cr.get_report()))]) - add("Share Counts", - "need %d-of-%d, have %d" % (data["count-shares-needed"], - data["count-shares-expected"], - data["count-shares-good"])) - add("Hosts with good shares", data["count-good-share-hosts"]) - - if data["list-corrupt-shares"]: - badsharemap = [] - for (serverid, si, shnum) in data["list-corrupt-shares"]: - nickname = c.get_nickname_for_peerid(serverid) - badsharemap.append(T.tr[T.td["sh#%d" % shnum], - T.td[T.tt[base32.b2a(serverid)], - " (", nickname, ")"], - ]) - add("Corrupt shares", T.table(border="1")[badsharemap]) - else: - add("Corrupt shares", "none") - - add("Wrong Shares", data["count-wrong-shares"]) - - sharemap = [] - servers = {} - - for shareid in sorted(data["sharemap"].keys()): - serverids = data["sharemap"][shareid] - for i,serverid in enumerate(serverids): - if serverid not in servers: - servers[serverid] = [] - servers[serverid].append(shareid) - shareid_s = "" - if i == 0: - shareid_s = shareid - nickname = c.get_nickname_for_peerid(serverid) - sharemap.append(T.tr[T.td[shareid_s], - T.td[T.tt[base32.b2a(serverid)], - " (", nickname, ")"], - ]) - add("Good Shares (sorted in share order)", - T.table(border="1")[sharemap]) - - - add("Recoverable Versions", data["count-recoverable-versions"]) - add("Unrecoverable Versions", data["count-unrecoverable-versions"]) - - # this table is sorted by permuted order - permuted_peer_ids = [peerid - for (peerid, rref) - in c.get_permuted_peers("storage", - cr.get_storage_index())] - - num_shares_left = sum([len(shares) for shares in servers.values()]) - servermap = [] - for serverid in permuted_peer_ids: - nickname = c.get_nickname_for_peerid(serverid) - shareids = servers.get(serverid, []) - shareids.reverse() - shareids_s = [ T.tt[shareid, " "] for shareid in shareids ] - servermap.append(T.tr[T.td[T.tt[base32.b2a(serverid)], - " (", nickname, ")"], - T.td[shareids_s] ]) - num_shares_left -= len(shareids) - if not num_shares_left: - break - add("Share Balancing (servers in permuted order)", - T.table(border="1")[servermap]) - - return T.ul[r] - - def _json_check_and_repair_results(self, r): - data = {} - data["storage-index"] = r.get_storage_index_string() - data["repair-attempted"] = r.get_repair_attempted() - data["repair-successful"] = r.get_repair_successful() - pre = r.get_pre_repair_results() - data["pre-repair-results"] = self._json_check_results(pre) - post = r.get_post_repair_results() - data["post-repair-results"] = self._json_check_results(post) - return data - - def _json_check_results(self, r): - data = {} - data["storage-index"] = r.get_storage_index_string() - data["summary"] = r.get_summary() - data["results"] = self._json_check_counts(r.get_data()) - data["results"]["needs-rebalancing"] = r.needs_rebalancing() - data["results"]["healthy"] = r.is_healthy() - data["results"]["recoverable"] = r.is_recoverable() - return data - - def _json_check_counts(self, d): - r = {} - r["count-shares-good"] = d["count-shares-good"] - r["count-shares-needed"] = d["count-shares-needed"] - r["count-shares-expected"] = d["count-shares-expected"] - r["count-good-share-hosts"] = d["count-good-share-hosts"] - r["count-corrupt-shares"] = d["count-corrupt-shares"] - r["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid), - base32.b2a(si), shnum) - for (serverid, si, shnum) - in d["list-corrupt-shares"] ] - r["servers-responding"] = [idlib.nodeid_b2a(serverid) - for serverid in d["servers-responding"]] - sharemap = {} - for (shareid, serverids) in d["sharemap"].items(): - sharemap[shareid] = [idlib.nodeid_b2a(serverid) - for serverid in serverids] - r["sharemap"] = sharemap - - r["count-wrong-shares"] = d["count-wrong-shares"] - r["count-recoverable-versions"] = d["count-recoverable-versions"] - r["count-unrecoverable-versions"] = d["count-unrecoverable-versions"] - - return r - - def _html(self, s): - if isinstance(s, (str, unicode)): - return html.escape(s) - assert isinstance(s, (list, tuple)) - return [html.escape(w) for w in s] - - def want_json(self, ctx): - output = get_arg(inevow.IRequest(ctx), "output", "").lower() - if output.lower() == "json": - return True - return False - - def _render_si_link(self, ctx, storage_index): - si_s = base32.b2a(storage_index) - root = get_root(ctx) - req = inevow.IRequest(ctx) - ophandle = req.prepath[-1] - target = "%s/operations/%s/%s" % (get_root(ctx), ophandle, si_s) - output = get_arg(ctx, "output") - if output: - target = target + "?output=%s" % output - return T.a(href=target)[si_s] - -class LiteralCheckerResults(rend.Page, ResultsBase): - docFactory = getxmlfile("literal-checker-results.xhtml") - - def renderHTTP(self, ctx): - if self.want_json(ctx): - return self.json(ctx) - return rend.Page.renderHTTP(self, ctx) - - def json(self, ctx): - inevow.IRequest(ctx).setHeader("content-type", "text/plain") - data = {"storage-index": "", - "results": {"healthy": True}, - } - return simplejson.dumps(data, indent=1) + "\n" - -class CheckerBase: - - def renderHTTP(self, ctx): - if self.want_json(ctx): - return self.json(ctx) - return rend.Page.renderHTTP(self, ctx) - - def render_storage_index(self, ctx, data): - return self.r.get_storage_index_string() - - def render_return(self, ctx, data): - req = inevow.IRequest(ctx) - return_to = get_arg(req, "return_to", None) - if return_to: - return T.div[T.a(href=return_to)["Return to parent directory"]] - return "" - -class CheckerResults(CheckerBase, rend.Page, ResultsBase): - docFactory = getxmlfile("checker-results.xhtml") - - def __init__(self, results): - self.r = ICheckerResults(results) - - def json(self, ctx): - inevow.IRequest(ctx).setHeader("content-type", "text/plain") - data = self._json_check_results(self.r) - return simplejson.dumps(data, indent=1) + "\n" - - def render_summary(self, ctx, data): - results = [] - if self.r.is_healthy(): - results.append("Healthy") - elif self.r.is_recoverable(): - results.append("Not Healthy!") - else: - results.append("Not Recoverable!") - results.append(" : ") - results.append(self._html(self.r.get_summary())) - return ctx.tag[results] - - def render_repair(self, ctx, data): - if self.r.is_healthy(): - return "" - repair = T.form(action=".", method="post", - enctype="multipart/form-data")[ - T.fieldset[ - T.input(type="hidden", name="t", value="check"), - T.input(type="hidden", name="repair", value="true"), - T.input(type="submit", value="Repair"), - ]] - return ctx.tag[repair] - - def render_rebalance(self, ctx, data): - if self.r.needs_rebalancing(): - return ctx.tag["(needs rebalancing)"] - return ctx.tag["(does not need rebalancing)"] - - def render_results(self, ctx, data): - cr = self._render_results(ctx, self.r) - return ctx.tag[cr] - -class CheckAndRepairResults(CheckerBase, rend.Page, ResultsBase): - docFactory = getxmlfile("check-and-repair-results.xhtml") - - def __init__(self, results): - self.r = ICheckAndRepairResults(results) - - def json(self, ctx): - inevow.IRequest(ctx).setHeader("content-type", "text/plain") - data = self._json_check_and_repair_results(self.r) - return simplejson.dumps(data, indent=1) + "\n" - - def render_summary(self, ctx, data): - cr = self.r.get_post_repair_results() - results = [] - if cr.is_healthy(): - results.append("Healthy") - elif cr.is_recoverable(): - results.append("Not Healthy!") - else: - results.append("Not Recoverable!") - results.append(" : ") - results.append(self._html(cr.get_summary())) - return ctx.tag[results] - - def render_repair_results(self, ctx, data): - if self.r.get_repair_attempted(): - if self.r.get_repair_successful(): - return ctx.tag["Repair successful"] - else: - return ctx.tag["Repair unsuccessful"] - return ctx.tag["No repair necessary"] - - def render_post_repair_results(self, ctx, data): - cr = self._render_results(ctx, self.r.get_post_repair_results()) - return ctx.tag[cr] - - def render_maybe_pre_repair_results(self, ctx, data): - if self.r.get_repair_attempted(): - cr = self._render_results(ctx, self.r.get_pre_repair_results()) - return ctx.tag[T.div["Pre-Repair Checker Results:"], cr] - return "" - - -class DeepCheckResults(rend.Page, ResultsBase, ReloadMixin): - docFactory = getxmlfile("deep-check-results.xhtml") - - def __init__(self, monitor): - self.monitor = monitor - - def childFactory(self, ctx, name): - if not name: - return self - # /operation/$OPHANDLE/$STORAGEINDEX provides detailed information - # about a specific file or directory that was checked - si = base32.a2b(name) - r = self.monitor.get_status() - try: - return CheckerResults(r.get_results_for_storage_index(si)) - except KeyError: - raise WebError("No detailed results for SI %s" % html.escape(name), - http.NOT_FOUND) - - def renderHTTP(self, ctx): - if self.want_json(ctx): - return self.json(ctx) - return rend.Page.renderHTTP(self, ctx) - - def json(self, ctx): - inevow.IRequest(ctx).setHeader("content-type", "text/plain") - data = {} - data["finished"] = self.monitor.is_finished() - res = self.monitor.get_status() - data["root-storage-index"] = res.get_root_storage_index_string() - c = res.get_counters() - data["count-objects-checked"] = c["count-objects-checked"] - data["count-objects-healthy"] = c["count-objects-healthy"] - data["count-objects-unhealthy"] = c["count-objects-unhealthy"] - data["count-corrupt-shares"] = c["count-corrupt-shares"] - data["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid), - base32.b2a(storage_index), - shnum) - for (serverid, storage_index, shnum) - in res.get_corrupt_shares() ] - data["list-unhealthy-files"] = [ (path_t, self._json_check_results(r)) - for (path_t, r) - in res.get_all_results().items() - if not r.is_healthy() ] - data["stats"] = res.get_stats() - return simplejson.dumps(data, indent=1) + "\n" - - def render_root_storage_index(self, ctx, data): - return self.monitor.get_status().get_root_storage_index_string() - - def data_objects_checked(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-checked"] - def data_objects_healthy(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-healthy"] - def data_objects_unhealthy(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-unhealthy"] - def data_objects_unrecoverable(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-unrecoverable"] - - def data_count_corrupt_shares(self, ctx, data): - return self.monitor.get_status().get_counters()["count-corrupt-shares"] - - def render_problems_p(self, ctx, data): - c = self.monitor.get_status().get_counters() - if c["count-objects-unhealthy"]: - return ctx.tag - return "" - - def data_problems(self, ctx, data): - all_objects = self.monitor.get_status().get_all_results() - for path in sorted(all_objects.keys()): - cr = all_objects[path] - assert ICheckerResults.providedBy(cr) - if not cr.is_healthy(): - yield path, cr - - def render_problem(self, ctx, data): - path, cr = data - summary_text = "" - summary = cr.get_summary() - if summary: - summary_text = ": " + summary - summary_text += " [SI: %s]" % cr.get_storage_index_string() - return ctx.tag["/".join(self._html(path)), self._html(summary_text)] - - - def render_servers_with_corrupt_shares_p(self, ctx, data): - if self.monitor.get_status().get_counters()["count-corrupt-shares"]: - return ctx.tag - return "" - - def data_servers_with_corrupt_shares(self, ctx, data): - servers = [serverid - for (serverid, storage_index, sharenum) - in self.monitor.get_status().get_corrupt_shares()] - servers.sort() - return servers - - def render_server_problem(self, ctx, data): - serverid = data - data = [idlib.shortnodeid_b2a(serverid)] - c = IClient(ctx) - nickname = c.get_nickname_for_peerid(serverid) - if nickname: - data.append(" (%s)" % self._html(nickname)) - return ctx.tag[data] - - - def render_corrupt_shares_p(self, ctx, data): - if self.monitor.get_status().get_counters()["count-corrupt-shares"]: - return ctx.tag - return "" - def data_corrupt_shares(self, ctx, data): - return self.monitor.get_status().get_corrupt_shares() - def render_share_problem(self, ctx, data): - serverid, storage_index, sharenum = data - nickname = IClient(ctx).get_nickname_for_peerid(serverid) - ctx.fillSlots("serverid", idlib.shortnodeid_b2a(serverid)) - if nickname: - ctx.fillSlots("nickname", self._html(nickname)) - ctx.fillSlots("si", self._render_si_link(ctx, storage_index)) - ctx.fillSlots("shnum", str(sharenum)) - return ctx.tag - - def render_return(self, ctx, data): - req = inevow.IRequest(ctx) - return_to = get_arg(req, "return_to", None) - if return_to: - return T.div[T.a(href=return_to)["Return to parent directory"]] - return "" - - def data_all_objects(self, ctx, data): - r = self.monitor.get_status().get_all_results() - for path in sorted(r.keys()): - yield (path, r[path]) - - def render_object(self, ctx, data): - path, r = data - if path: - pathstring = "/".join(self._html(path)) - else: - pathstring = "" - ctx.fillSlots("path", pathstring) - ctx.fillSlots("healthy", str(r.is_healthy())) - ctx.fillSlots("recoverable", str(r.is_recoverable())) - storage_index = r.get_storage_index() - ctx.fillSlots("storage_index", self._render_si_link(ctx, storage_index)) - ctx.fillSlots("summary", self._html(r.get_summary())) - return ctx.tag - - def render_runtime(self, ctx, data): - req = inevow.IRequest(ctx) - runtime = time.time() - req.processing_started_timestamp - return ctx.tag["runtime: %s seconds" % runtime] - -class DeepCheckAndRepairResults(rend.Page, ResultsBase, ReloadMixin): - docFactory = getxmlfile("deep-check-and-repair-results.xhtml") - - def __init__(self, monitor): - #assert IDeepCheckAndRepairResults(results) - #self.r = results - self.monitor = monitor - - def renderHTTP(self, ctx): - if self.want_json(ctx): - return self.json(ctx) - return rend.Page.renderHTTP(self, ctx) - - def json(self, ctx): - inevow.IRequest(ctx).setHeader("content-type", "text/plain") - res = self.monitor.get_status() - data = {} - data["finished"] = self.monitor.is_finished() - data["root-storage-index"] = res.get_root_storage_index_string() - c = res.get_counters() - data["count-objects-checked"] = c["count-objects-checked"] - - data["count-objects-healthy-pre-repair"] = c["count-objects-healthy-pre-repair"] - data["count-objects-unhealthy-pre-repair"] = c["count-objects-unhealthy-pre-repair"] - data["count-objects-healthy-post-repair"] = c["count-objects-healthy-post-repair"] - data["count-objects-unhealthy-post-repair"] = c["count-objects-unhealthy-post-repair"] - - data["count-repairs-attempted"] = c["count-repairs-attempted"] - data["count-repairs-successful"] = c["count-repairs-successful"] - data["count-repairs-unsuccessful"] = c["count-repairs-unsuccessful"] - - data["count-corrupt-shares-pre-repair"] = c["count-corrupt-shares-pre-repair"] - data["count-corrupt-shares-post-repair"] = c["count-corrupt-shares-pre-repair"] - - data["list-corrupt-shares"] = [ (idlib.nodeid_b2a(serverid), - base32.b2a(storage_index), - shnum) - for (serverid, storage_index, shnum) - in res.get_corrupt_shares() ] - - remaining_corrupt = [ (idlib.nodeid_b2a(serverid), - base32.b2a(storage_index), - shnum) - for (serverid, storage_index, shnum) - in res.get_remaining_corrupt_shares() ] - data["list-remaining-corrupt-shares"] = remaining_corrupt - - unhealthy = [ (path_t, - self._json_check_results(crr.get_pre_repair_results())) - for (path_t, crr) - in res.get_all_results().items() - if not crr.get_pre_repair_results().is_healthy() ] - data["list-unhealthy-files"] = unhealthy - data["stats"] = res.get_stats() - return simplejson.dumps(data, indent=1) + "\n" - - def render_root_storage_index(self, ctx, data): - return self.monitor.get_status().get_root_storage_index_string() - - def data_objects_checked(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-checked"] - - def data_objects_healthy(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-healthy-pre-repair"] - def data_objects_unhealthy(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-unhealthy-pre-repair"] - def data_corrupt_shares(self, ctx, data): - return self.monitor.get_status().get_counters()["count-corrupt-shares-pre-repair"] - - def data_repairs_attempted(self, ctx, data): - return self.monitor.get_status().get_counters()["count-repairs-attempted"] - def data_repairs_successful(self, ctx, data): - return self.monitor.get_status().get_counters()["count-repairs-successful"] - def data_repairs_unsuccessful(self, ctx, data): - return self.monitor.get_status().get_counters()["count-repairs-unsuccessful"] - - def data_objects_healthy_post(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-healthy-post-repair"] - def data_objects_unhealthy_post(self, ctx, data): - return self.monitor.get_status().get_counters()["count-objects-unhealthy-post-repair"] - def data_corrupt_shares_post(self, ctx, data): - return self.monitor.get_status().get_counters()["count-corrupt-shares-post-repair"] - - def render_pre_repair_problems_p(self, ctx, data): - c = self.monitor.get_status().get_counters() - if c["count-objects-unhealthy-pre-repair"]: - return ctx.tag - return "" - - def data_pre_repair_problems(self, ctx, data): - all_objects = self.monitor.get_status().get_all_results() - for path in sorted(all_objects.keys()): - r = all_objects[path] - assert ICheckAndRepairResults.providedBy(r) - cr = r.get_pre_repair_results() - if not cr.is_healthy(): - yield path, cr - - def render_problem(self, ctx, data): - path, cr = data - return ["/".join(self._html(path)), ": ", self._html(cr.get_summary())] - - def render_post_repair_problems_p(self, ctx, data): - c = self.monitor.get_status().get_counters() - if (c["count-objects-unhealthy-post-repair"] - or c["count-corrupt-shares-post-repair"]): - return ctx.tag - return "" - - def data_post_repair_problems(self, ctx, data): - all_objects = self.monitor.get_status().get_all_results() - for path in sorted(all_objects.keys()): - r = all_objects[path] - assert ICheckAndRepairResults.providedBy(r) - cr = r.get_post_repair_results() - if not cr.is_healthy(): - yield path, cr - - def render_servers_with_corrupt_shares_p(self, ctx, data): - if self.monitor.get_status().get_counters()["count-corrupt-shares-pre-repair"]: - return ctx.tag - return "" - def data_servers_with_corrupt_shares(self, ctx, data): - return [] # TODO - def render_server_problem(self, ctx, data): - pass - - - def render_remaining_corrupt_shares_p(self, ctx, data): - if self.monitor.get_status().get_counters()["count-corrupt-shares-post-repair"]: - return ctx.tag - return "" - def data_post_repair_corrupt_shares(self, ctx, data): - return [] # TODO - - def render_share_problem(self, ctx, data): - pass - - - def render_return(self, ctx, data): - req = inevow.IRequest(ctx) - return_to = get_arg(req, "return_to", None) - if return_to: - return T.div[T.a(href=return_to)["Return to parent directory"]] - return "" - - def data_all_objects(self, ctx, data): - r = self.monitor.get_status().get_all_results() - for path in sorted(r.keys()): - yield (path, r[path]) - - def render_object(self, ctx, data): - path, r = data - ctx.fillSlots("path", "/".join(self._html(path))) - ctx.fillSlots("healthy_pre_repair", - str(r.get_pre_repair_results().is_healthy())) - ctx.fillSlots("healthy_post_repair", - str(r.get_post_repair_results().is_healthy())) - ctx.fillSlots("summary", - self._html(r.get_pre_repair_results().get_summary())) - return ctx.tag - - def render_runtime(self, ctx, data): - req = inevow.IRequest(ctx) - runtime = time.time() - req.processing_started_timestamp - return ctx.tag["runtime: %s seconds" % runtime] diff --git a/src/allmydata/web/directory.py b/src/allmydata/web/directory.py index 23306729..0ac24bc2 100644 --- a/src/allmydata/web/directory.py +++ b/src/allmydata/web/directory.py @@ -23,7 +23,7 @@ from allmydata.web.common import text_plain, WebError, \ getxmlfile, RenderMixin from allmydata.web.filenode import ReplaceMeMixin, \ FileNodeHandler, PlaceHolderNodeHandler -from allmydata.web.checker_results import CheckerResults, \ +from allmydata.web.check_results import CheckerResults, \ CheckAndRepairResults, DeepCheckResults, DeepCheckAndRepairResults from allmydata.web.info import MoreInfo from allmydata.web.operations import ReloadMixin diff --git a/src/allmydata/web/filenode.py b/src/allmydata/web/filenode.py index e8d52229..0061805f 100644 --- a/src/allmydata/web/filenode.py +++ b/src/allmydata/web/filenode.py @@ -14,7 +14,7 @@ from allmydata.util import log, base32 from allmydata.web.common import text_plain, WebError, IClient, RenderMixin, \ boolean_of_arg, get_arg, should_create_intermediate_directories -from allmydata.web.checker_results import CheckerResults, \ +from allmydata.web.check_results import CheckerResults, \ CheckAndRepairResults, LiteralCheckerResults from allmydata.web.info import MoreInfo diff --git a/src/allmydata/web/literal-check-results.xhtml b/src/allmydata/web/literal-check-results.xhtml new file mode 100644 index 00000000..4e4aad67 --- /dev/null +++ b/src/allmydata/web/literal-check-results.xhtml @@ -0,0 +1,18 @@ + + + AllMyData - Tahoe - Check Results + + + + + + +

File Check Results for LIT file

+ +
Literal files are always healthy: their data is contained in the URI
+ +
+ + + diff --git a/src/allmydata/web/literal-checker-results.xhtml b/src/allmydata/web/literal-checker-results.xhtml deleted file mode 100644 index 4e4aad67..00000000 --- a/src/allmydata/web/literal-checker-results.xhtml +++ /dev/null @@ -1,18 +0,0 @@ - - - AllMyData - Tahoe - Check Results - - - - - - -

File Check Results for LIT file

- -
Literal files are always healthy: their data is contained in the URI
- -
- - -