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.
--- /dev/null
+
+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 = "<none>"
+ 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
+++ /dev/null
-
-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 = "<none>"
- 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
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
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
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
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
"""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
IDeepCheckAndRepairResults object.
"""
-class ICheckerResults(Interface):
+class ICheckResults(Interface):
"""I contain the detailed results of a check/verify operation.
"""
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."""
"""
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."""
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::
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
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
#################################
# 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
from zope.interface import implements
-from allmydata.interfaces import IRepairResults, ICheckerResults
+from allmydata.interfaces import IRepairResults, ICheckResults
class RepairResults:
implements(IRepairResults)
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
# 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:
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
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
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
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()
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
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)
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()
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()
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)
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)
--- /dev/null
+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
+ <head>
+ <title>AllMyData - Tahoe - Check Results</title>
+ <!-- <link href="http://www.allmydata.com/common/css/styles.css"
+ rel="stylesheet" type="text/css"/> -->
+ <link href="/webform_css" rel="stylesheet" type="text/css"/>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ </head>
+ <body>
+
+<h1>File Check Results for SI=<span n:render="storage_index" /></h1>
+
+<div>
+ <span n:render="summary" />
+</div>
+
+<div n:render="repair" />
+
+<div n:render="results" />
+
+<div n:render="return" />
+
+ </body>
+</html>
--- /dev/null
+
+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 = "<root>"
+ 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]
+++ /dev/null
-<html xmlns:n="http://nevow.com/ns/nevow/0.1">
- <head>
- <title>AllMyData - Tahoe - Check Results</title>
- <!-- <link href="http://www.allmydata.com/common/css/styles.css"
- rel="stylesheet" type="text/css"/> -->
- <link href="/webform_css" rel="stylesheet" type="text/css"/>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- </head>
- <body>
-
-<h1>File Check Results for SI=<span n:render="storage_index" /></h1>
-
-<div>
- <span n:render="summary" />
-</div>
-
-<div n:render="repair" />
-
-<div n:render="results" />
-
-<div n:render="return" />
-
- </body>
-</html>
+++ /dev/null
-
-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 = "<root>"
- 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]
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
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
--- /dev/null
+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
+ <head>
+ <title>AllMyData - Tahoe - Check Results</title>
+ <!-- <link href="http://www.allmydata.com/common/css/styles.css"
+ rel="stylesheet" type="text/css"/> -->
+ <link href="/webform_css" rel="stylesheet" type="text/css"/>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ </head>
+ <body>
+
+<h1>File Check Results for LIT file</h1>
+
+<div>Literal files are always healthy: their data is contained in the URI</div>
+
+<div n:render="return" />
+
+ </body>
+</html>
+++ /dev/null
-<html xmlns:n="http://nevow.com/ns/nevow/0.1">
- <head>
- <title>AllMyData - Tahoe - Check Results</title>
- <!-- <link href="http://www.allmydata.com/common/css/styles.css"
- rel="stylesheet" type="text/css"/> -->
- <link href="/webform_css" rel="stylesheet" type="text/css"/>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- </head>
- <body>
-
-<h1>File Check Results for LIT file</h1>
-
-<div>Literal files are always healthy: their data is contained in the URI</div>
-
-<div n:render="return" />
-
- </body>
-</html>