--- /dev/null
+
+from zope.interface import implements
+from allmydata.interfaces import ICheckerResults, ICheckAndRepairResults, \
+ IDeepCheckResults, IDeepCheckAndRepairResults
+from allmydata.util import base32
+
+class CheckerResults:
+ implements(ICheckerResults)
+
+ def __init__(self, storage_index):
+ 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)
+ 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 is_healthy(self):
+ return self.healthy
+
+ 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):
+ 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)[:6]
+
+ self.objects_checked = 0
+ self.objects_healthy = 0
+ self.objects_unhealthy = 0
+ self.corrupt_shares = []
+ self.all_results = {}
+
+ 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
+
+
+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
+ self.all_results[tuple(path)] = 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-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_healthy_post_repair = 0
+ self.objects_healthy_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
+ 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
+ self.all_results[tuple(path)] = 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-healthy-post-repair": self.objects_healthy_post_repair,
+ "count-objects-unhealthy-post-repair": self.objects_unhealthy_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, IVerifierURI, IFilesystemNode, \
ExistingChildError, ICheckable
-from allmydata.immutable.checker import DeepCheckResults
+from allmydata.checker_results import DeepCheckResults, \
+ DeepCheckAndRepairResults
from allmydata.util import hashutil, mathutil, base32, log
from allmydata.util.hashutil import netstring
from allmydata.util.limiter import ConcurrencyLimiter
def get_storage_index(self):
return self._uri._filenode_uri.storage_index
- def check(self, verify=False, repair=False):
+ def check(self, verify=False):
"""Perform a file check. See IChecker.check for details."""
- return self._node.check(verify, repair)
+ return self._node.check(verify)
+ def check_and_repair(self, verify=False):
+ return self._node.check_and_repair(verify)
def list(self):
"""I return a Deferred that fires with a dictionary mapping child
d.addCallback(_got_list)
return d
- def deep_check(self, verify=False, repair=False):
+ def deep_check(self, verify=False):
+ return self.deep_check_base(verify, False)
+ def deep_check_and_repair(self, verify=False):
+ return self.deep_check_base(verify, True)
+
+ def deep_check_base(self, verify, repair):
# shallow-check each object first, then traverse children
root_si = self._node.get_storage_index()
self._lp = log.msg(format="deep-check starting (%(si)s),"
" verify=%(verify)s, repair=%(repair)s",
si=base32.b2a(root_si), verify=verify, repair=repair)
- results = DeepCheckResults(root_si)
+ if repair:
+ results = DeepCheckAndRepairResults(root_si)
+ else:
+ results = DeepCheckResults(root_si)
found = set()
limiter = ConcurrencyLimiter(10)
- d = self._add_deepcheck_from_node(self, results, found, limiter,
+ d = self._add_deepcheck_from_node([], self, results, found, limiter,
verify, repair)
def _done(res):
log.msg("deep-check done", parent=self._lp)
d.addCallback(_done)
return d
- def _add_deepcheck_from_node(self, node, results, found, limiter,
+ def _add_deepcheck_from_node(self, path, node, results, found, limiter,
verify, repair):
verifier = node.get_verifier()
if verifier in found:
return None
found.add(verifier)
- d = limiter.add(node.check, verify, repair)
- d.addCallback(results.add_check)
+ if repair:
+ d = limiter.add(node.check_and_repair, verify)
+ d.addCallback(results.add_check_and_repair, path)
+ else:
+ d = limiter.add(node.check, verify)
+ d.addCallback(results.add_check, path)
+
+ # TODO: stats: split the DeepStats.foo calls out of
+ # _add_deepstats_from_node into a separate non-recursing method, call
+ # it from both here and _add_deepstats_from_node.
if IDirectoryNode.providedBy(node):
d.addCallback(lambda res: node.list())
def _got_children(children):
dl = []
for name, (child, metadata) in children.iteritems():
- d2 = self._add_deepcheck_from_node(child, results,
+ childpath = path + [name]
+ d2 = self._add_deepcheck_from_node(childpath, child,
+ results,
found, limiter,
verify, repair)
if d2:
have the share, we believe them.
"""
-from zope.interface import implements
from twisted.internet import defer
from twisted.python import log
from allmydata import storage
-from allmydata.interfaces import ICheckerResults, IDeepCheckResults
+from allmydata.checker_results import CheckerResults
from allmydata.immutable import download
-from allmydata.util import hashutil, base32
-
-class Results:
- implements(ICheckerResults)
-
- def __init__(self, storage_index):
- # storage_index might be None for, say, LIT files
- self.storage_index = storage_index
- if storage_index is None:
- self.storage_index_s = "<none>"
- else:
- self.storage_index_s = base32.b2a(storage_index)[:6]
- self.status_report = "[not generated yet]" # string
-
- def is_healthy(self):
- return self.healthy
-
- def get_storage_index(self):
- return self.storage_index
-
- def get_storage_index_string(self):
- return self.storage_index_s
-
- def get_mutability_string(self):
- if self.storage_index:
- return "immutable"
- return "literal"
-
- def to_string(self):
- s = ""
- if self.healthy:
- s += "Healthy!\n"
- else:
- s += "Not Healthy!\n"
- s += "\n"
- s += self.status_report
- s += "\n"
- return s
-
-class DeepCheckResults:
- implements(IDeepCheckResults)
-
- 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)[:6]
-
- self.objects_checked = 0
- self.objects_healthy = 0
- self.repairs_attempted = 0
- self.repairs_successful = 0
- self.problems = []
- self.all_results = {}
- self.server_problems = {}
-
- def get_root_storage_index_string(self):
- return self.root_storage_index_s
-
- def add_check(self, r):
- self.objects_checked += 1
- if r.is_healthy():
- self.objects_healthy += 1
- else:
- self.problems.append(r)
- self.all_results[r.get_storage_index()] = r
-
- def add_repair(self, is_successful):
- self.repairs_attempted += 1
- if is_successful:
- self.repairs_successful += 1
-
- def count_objects_checked(self):
- return self.objects_checked
- def count_objects_healthy(self):
- return self.objects_healthy
- def count_repairs_attempted(self):
- return self.repairs_attempted
- def count_repairs_successful(self):
- return self.repairs_successful
- def get_server_problems(self):
- return self.server_problems
- def get_problems(self):
- return self.problems
- def get_all_results(self):
- return self.all_results
+from allmydata.util import hashutil
class SimpleCHKFileChecker:
"""Return a list of (needed, total, found, sharemap), where sharemap maps
pass
def _done(self, res):
- r = Results(self.storage_index)
+ r = CheckerResults(self.storage_index)
report = []
- r.healthy = bool(len(self.found_shares) >= self.total_shares)
- r.stuff = (self.needed_shares, self.total_shares,
- len(self.found_shares), self.sharemap)
+ r.set_healthy(bool(len(self.found_shares) >= self.total_shares))
+ data = {"count-shares-good": len(self.found_shares),
+ "count-shares-needed": self.needed_shares,
+ "count-shares-expected": self.total_shares,
+ }
+ # TODO: count-good-shares-hosts, count-corrupt-shares,
+ # list-corrupt-shares, servers-responding, sharemap
+ #r.stuff = (self.needed_shares, self.total_shares,
+ # len(self.found_shares), self.sharemap)
if len(self.found_shares) < self.total_shares:
wanted = set(range(self.total_shares))
missing = wanted - self.found_shares
report.append("Missing shares: %s" %
",".join(["sh%d" % shnum
for shnum in sorted(missing)]))
- r.status_report = "\n".join(report) + "\n"
+ r.set_report(report)
+ # TODO: r.set_summary(summary)
return r
class VerifyingOutput:
self._crypttext_hash_tree = None
self._opened = False
self._results = results
- results.healthy = False
+ results.set_healthy(False)
def setup_hashtrees(self, plaintext_hashtree, crypttext_hashtree):
self._crypttext_hash_tree = crypttext_hashtree
self.crypttext_hash = self._crypttext_hasher.digest()
def finish(self):
- self._results.healthy = True
- return self._results
+ self._results.set_healthy(True)
+ # the return value of finish() is passed out of FileDownloader._done,
+ # but SimpleCHKFileVerifier overrides this with the CheckerResults
+ # instance instead.
class SimpleCHKFileVerifier(download.FileDownloader):
self._si_s = storage.si_b2a(self._storage_index)
self.init_logging()
- r = Results(self._storage_index)
+ self._check_results = r = CheckerResults(self._storage_index)
self._output = VerifyingOutput(self._size, r)
self._paused = False
self._stopped = False
# once we know that, we can download blocks from everybody
d.addCallback(self._download_all_segments)
d.addCallback(self._done)
+ d.addCallback(lambda ignored: self._check_results)
return d
from twisted.internet import defer
from allmydata.interfaces import IFileNode, IFileURI, IURI, ICheckable
from allmydata import uri
-from allmydata.immutable.checker import Results, DeepCheckResults, \
- SimpleCHKFileChecker, SimpleCHKFileVerifier
+from allmydata.immutable.checker import SimpleCHKFileChecker, \
+ SimpleCHKFileVerifier
+from allmydata.checker_results import DeepCheckResults, \
+ DeepCheckAndRepairResults
class FileNode:
implements(IFileNode, ICheckable)
def get_storage_index(self):
return self.u.storage_index
- def check(self, verify=False, repair=False):
- assert repair is False # not implemented yet
+ def check(self, verify=False):
storage_index = self.u.storage_index
k = self.u.needed_shares
N = self.u.total_shares
v = self.checker_class(self._client, storage_index, k, N)
return v.start()
- def deep_check(self, verify=False, repair=False):
- d = self.check(verify, repair)
+ def check_and_repair(self, verify=False):
+ raise NotImplementedError("not implemented yet")
+
+ def deep_check(self, verify=False):
+ d = self.check(verify)
def _done(r):
dr = DeepCheckResults(self.get_verifier().storage_index)
- dr.add_check(r)
+ dr.add_check(r, [])
+ return dr
+ d.addCallback(_done)
+ return d
+
+ def deep_check_and_repair(self, verify=False):
+ d = self.check_and_repair(verify)
+ def _done(r):
+ dr = DeepCheckAndRepairResults(self.get_verifier().storage_index)
+ dr.add_check_and_repair(r, [])
return dr
d.addCallback(_done)
return d
return None
def check(self, verify=False, repair=False):
- # neither verify= nor repair= affect LIT files
- r = Results(None)
- r.healthy = True
- r.problems = []
- return defer.succeed(r)
+ # neither verify= nor repair= affect LIT files, and the check returns
+ # no results.
+ return defer.succeed(None)
def deep_check(self, verify=False, repair=False):
- d = self.check(verify, repair)
- def _done(r):
- dr = DeepCheckResults(None)
- dr.add_check(r)
- return dr
- d.addCallback(_done)
- return d
+ dr = DeepCheckResults(None)
+ return defer.succeed(dr)
def download(self, target):
# note that this does not update the stats_provider
"""TODO: how should this work?"""
class ICheckable(Interface):
- def check(verify=False, repair=False):
+ def check(verify=False):
"""Check upon my health, optionally repairing any problems.
This returns a Deferred that fires with an instance that provides
- ICheckerResults.
+ ICheckerResults, or None if the object is non-distributed (i.e. LIT
+ files).
Filenodes and dirnodes (which provide IFilesystemNode) are also
checkable. Instances that represent verifier-caps will be checkable
but not downloadable. Some objects (like LIT files) do not actually
- live in the grid, and their checkers indicate a healthy result.
+ live in the grid, and their checkers return None (non-distributed
+ files are always healthy).
If verify=False, a relatively lightweight check will be performed: I
will ask all servers if they have a share for me, and I will believe
taken.
"""
- def deep_check(verify=False, repair=False):
+ def check_and_repair(verify=False):
+ """Like check(), but if the file/directory is not healthy, attempt to
+ repair the damage.
+
+ This returns a Deferred which fires with a tuple of (pre, post), each
+ is either None or an ICheckerResults instance. For non-distributed
+ files (i.e. a LIT file) both are None. Otherwise, 'pre' is an
+ ICheckerResults representing the state of the object before any
+ repair attempt is made. If the file was unhealthy and repair was
+ attempted, 'post' will be another ICheckerResults instance with the
+ state of the object after repair."""
+
+ def deep_check(verify=False):
"""Check upon the health of me and everything I can reach.
This is a recursive form of check(), useable on dirnodes. (it can be
I return a Deferred that fires with an IDeepCheckResults object.
"""
+ def deep_check_and_repair(verify=False):
+ """Check upon the health of me and everything I can reach. Repair
+ anything that isn't healthy.
+
+ This is a recursive form of check_and_repair(), useable on dirnodes.
+ (it can be called safely on filenodes too, but only checks/repairs
+ the one object).
+
+ I return a Deferred that fires with an IDeepCheckAndRepairResults
+ object.
+ """
+
class ICheckerResults(Interface):
+ """I contain the detailed results of a check/verify operation.
+ """
+
+ def get_storage_index():
+ """Return a string with the (binary) storage index."""
+ def get_storage_index_string():
+ """Return a string with the (printable) abbreviated storage index."""
+
+ def is_healthy():
+ """Return a boolean, True if the file/dir is fully healthy, False if
+ it is damaged in any way. Non-distributed LIT files always return
+ True."""
+
+ def needs_rebalancing():
+ """Return a boolean, True if the file/dir's reliability could be
+ improved by moving shares to new servers. Non-distributed LIT files
+ always returne False."""
+
+
+ def get_data():
+ """Return a dictionary that describes the state of the file/dir.
+ Non-distributed LIT files always return an empty dictionary. Normal
+ files and directories return a dictionary with the following keys
+ (note that these use base32-encoded strings rather than binary ones)
+ (also note that for mutable files, these counts are for the 'best'
+ version)::
+
+ count-shares-good: the number of distinct good shares that were found
+ count-shares-needed: 'k', the number of shares required for recovery
+ count-shares-expected: 'N', the number of total shares generated
+ count-good-share-hosts: the number of distinct storage servers with
+ good shares. If this number is less than
+ count-shares-good, then some shares are
+ doubled up, increasing the correlation of
+ failures. This indicates that one or more
+ shares should be moved to an otherwise unused
+ server, if one is available.
+ count-corrupt-shares: the number of shares with integrity failures
+ list-corrupt-shares: a list of 'share locators', one for each share
+ that was found to be corrupt. Each share
+ locator is a list of (serverid, storage_index,
+ sharenum).
+ servers-responding: list of base32-encoded storage server identifiers,
+ one for each server which responded to the share
+ query.
+ sharemap: dict mapping share identifier to list of serverids
+ (base32-encoded strings). This indicates which servers are
+ holding which shares. For immutable files, the shareid is
+ an integer (the share number, from 0 to N-1). For
+ immutable files, it is a string of the form
+ 'seq%d-%s-sh%d', containing the sequence number, the
+ roothash, and the share number.
+
+ Mutable files will add the following keys::
+
+ count-wrong-shares: the number of shares for versions other than
+ the 'best' one (highest sequence number, highest
+ roothash). These are either old ...
+
+ count-recoverable-versions: the number of recoverable versions of
+ the file. For a healthy file, this will
+ equal 1.
+
+ count-unrecoverable-versions: the number of unrecoverable versions
+ of the file. For a healthy file, this
+ will be 0.
+
+ """
+
+ def get_summary():
+ """Return a string with a brief (one-line) summary of the results."""
+
+ def get_report():
+ """Return a list of strings with more detailed results."""
+
+class ICheckAndRepairResults(Interface):
"""I contain the detailed results of a check/verify/repair operation.
The IFilesystemNode.check()/verify()/repair() methods all return
- instances that provide ICheckerResults.
+ instances that provide ICheckAndRepairResults.
"""
- def is_healthy():
- """Return a bool, True if the file is fully healthy, False if it is
- damaged in any way."""
-
def get_storage_index():
"""Return a string with the (binary) storage index."""
def get_storage_index_string():
"""Return a string with the (printable) abbreviated storage index."""
- def get_mutability_string():
- """Return a string with 'mutable' or 'immutable'."""
+ def get_repair_attempted():
+ """Return a boolean, True if a repair was attempted."""
+ def get_repair_successful():
+ """Return a boolean, True if repair was attempted and the file/dir
+ was fully healthy afterwards."""
+ def get_pre_repair_results():
+ """Return an ICheckerResults 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
+ file/dir after any repair was attempted. If no repair was attempted,
+ the pre-repair and post-repair results will be identical."""
- def to_string():
- """Return a string that describes the detailed results of the
- check/verify operation. This string will be displayed on a page all
- by itself."""
-
- # The old checker results (for only immutable files) were described
- # with this:
- # For filenodes, this fires with a tuple of (needed_shares,
- # total_shares, found_shares, sharemap). The first three are ints. The
- # basic health of the file is found_shares / needed_shares: if less
- # than 1.0, the file is unrecoverable.
- #
- # The sharemap has a key for each sharenum. The value is a list of
- # (binary) nodeids who hold that share. If two shares are kept on the
- # same nodeid, they will fail as a pair, and overall reliability is
- # decreased.
class IDeepCheckResults(Interface):
"""I contain the results of a deep-check operation.
def get_root_storage_index_string():
"""Return the storage index (abbreviated human-readable string) of
the first object checked."""
- def count_objects_checked():
- """Return the number of objects that were checked."""
- def count_objects_healthy():
- """Return the number of objects that were fully healthy."""
- def count_repairs_attempted():
- """Return the number of repair operations that were attempted."""
- def count_repairs_successful():
- """Return the number of repair operations that succeeded in bringing
- the object back up to full health."""
- def get_server_problems():
- """Return a dict, mapping server nodeid to a count of how many
- problems involved that server."""
- def get_problems():
- """Return a list of ICheckerResults, one for each object that
- was not fully healthy."""
+ def get_counters():
+ """Return a dictionary with the following keys::
+
+ count-objects-checked: count of how many objects were checked
+ count-objects-healthy: how many of those objects were completely
+ healthy
+ count-objects-unhealthy: how many were damaged in some way
+ count-corrupt-shares: how many shares were found to have
+ corruption, summed over all objects
+ examined
+ """
+
+ def get_corrupt_shares():
+ """Return a set of (serverid, storage_index, sharenum) for all shares
+ that were found to be corrupt. Both serverid and storage_index are
+ binary.
+ """
+ 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
+ that was checked."""
+
+class IDeepCheckAndRepairResults(Interface):
+ """I contain the results of a deep-check-and-repair operation.
+
+ This is returned by a call to ICheckable.deep_check_and_repair().
+ """
+
+ def get_root_storage_index_string():
+ """Return the storage index (abbreviated human-readable string) of
+ the first object checked."""
+ def get_counters():
+ """Return a dictionary with the following keys::
+
+ count-objects-checked: count of how many objects were checked
+ count-objects-healthy-pre-repair: how many of those objects were
+ completely healthy (before any
+ repair)
+ count-objects-unhealthy-pre-repair: how many were damaged in
+ some way
+ count-objects-healthy-post-repair: how many of those objects were
+ completely healthy (after any
+ repair)
+ count-objects-unhealthy-post-repair: how many were damaged in
+ some way
+ count-repairs-attempted: repairs were attempted on this many
+ objects. The count-repairs- keys will
+ always be provided, however unless
+ repair=true is present, they will all
+ be zero.
+ count-repairs-successful: how many repairs resulted in healthy
+ objects
+ count-repairs-unsuccessful: how many repairs resulted did not
+ results in completely healthy objects
+ count-corrupt-shares-pre-repair: how many shares were found to
+ have corruption, summed over all
+ objects examined (before any
+ repair)
+ count-corrupt-shares-post-repair: how many shares were found to
+ have corruption, summed over all
+ objects examined (after any
+ repair)
+ """
+
+ def get_corrupt_shares():
+ """Return a set of (serverid, storage_index, sharenum) for all shares
+ that were found to be corrupt before any repair was attempted. Both
+ serverid and storage_index are binary.
+ """
+ def get_remaining_corrupt_shares():
+ """Return a set of (serverid, storage_index, sharenum) for all shares
+ that were found to be corrupt after any repair was completed. Both
+ serverid and storage_index are binary. These are shares that need
+ manual inspection and probably deletion.
+ """
def get_all_results():
- """Return a dict mapping storage_index (a binary string) to an
- ICheckerResults instance, one for each object that was checked."""
+ """Return a dictionary mapping pathname (a tuple of strings, ready to
+ be slash-joined) to an ICheckAndRepairResults instance, one for each
+ object that was checked."""
+
class IRepairable(Interface):
def repair(checker_results):
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.
+ avoided. The ICheckerResults object is inside the
+ ICheckAndRepairResults object, which is returned by the
+ ICheckable.check() method::
+
+ d = filenode.check(repair=False)
+ def _got_results(check_and_repair_results):
+ check_results = check_and_repair_results.get_pre_repair_results()
+ return filenode.repair(check_results)
+ d.addCallback(_got_results)
+ return d
"""
class IRepairResults(Interface):
-from zope.interface import implements
from twisted.internet import defer
from twisted.python import failure
from allmydata import hashtree
from allmydata.util import hashutil, base32, idlib, log
-from allmydata.interfaces import ICheckerResults
+from allmydata.checker_results import CheckAndRepairResults, CheckerResults
from common import MODE_CHECK, CorruptShareError
from servermap import ServerMap, ServermapUpdater
self._node = node
self.bad_shares = [] # list of (nodeid,shnum,failure)
self._storage_index = self._node.get_storage_index()
- self.results = Results(self._storage_index)
+ self.results = CheckerResults(self._storage_index)
self.need_repair = False
- def check(self, verify=False, repair=False):
+ def check(self, verify=False):
servermap = ServerMap()
- self.results.servermap = servermap
u = ServermapUpdater(self._node, servermap, MODE_CHECK)
d = u.update()
d.addCallback(self._got_mapupdate_results)
if verify:
d.addCallback(self._verify_all_shares)
- d.addCallback(self._generate_results)
- if repair:
- d.addCallback(self._maybe_do_repair)
- d.addCallback(self._return_results)
+ d.addCallback(lambda res: servermap)
+ d.addCallback(self._fill_checker_results, self.results)
+ d.addCallback(lambda res: self.results)
return d
def _got_mapupdate_results(self, servermap):
for (shnum, peerid, timestamp) in shares:
ss = servermap.connections[peerid]
d = self._do_read(ss, peerid, self._storage_index, [shnum], readv)
- d.addCallback(self._got_answer, peerid)
+ d.addCallback(self._got_answer, peerid, servermap)
dl.append(d)
return defer.DeferredList(dl, fireOnOneErrback=True)
d = ss.callRemote("slot_readv", storage_index, shnums, readv)
return d
- def _got_answer(self, datavs, peerid):
+ def _got_answer(self, datavs, peerid, servermap):
for shnum,datav in datavs.items():
data = datav[0]
try:
self.need_repair = True
self.bad_shares.append( (peerid, shnum, f) )
prefix = data[:SIGNED_PREFIX_LENGTH]
- self.results.servermap.mark_bad_share(peerid, shnum, prefix)
+ servermap.mark_bad_share(peerid, shnum, prefix)
def check_prefix(self, peerid, shnum, data):
(seqnum, root_hash, IV, segsize, datalength, k, N, prefix,
if alleged_writekey != self._node.get_writekey():
raise CorruptShareError(peerid, shnum, "invalid privkey")
- def _generate_results(self, res):
- self.results.healthy = True
- smap = self.results.servermap
+ def _count_shares(self, smap, version):
+ available_shares = smap.shares_available()
+ (num_distinct_shares, k, N) = available_shares[version]
+ counters = {}
+ counters["count-shares-good"] = num_distinct_shares
+ counters["count-shares-needed"] = k
+ counters["count-shares-expected"] = N
+ good_hosts = smap.all_peers_for_version(version)
+ counters["count-good-share-hosts"] = good_hosts
+ vmap = smap.make_versionmap()
+ counters["count-wrong-shares"] = sum([len(shares)
+ for verinfo,shares in vmap.items()
+ if verinfo != version])
+
+ return counters
+
+ def _fill_checker_results(self, smap, r):
+ r.set_servermap(smap.copy())
+ healthy = True
+ data = {}
report = []
+ summary = []
vmap = smap.make_versionmap()
recoverable = smap.recoverable_versions()
unrecoverable = smap.unrecoverable_versions()
+ data["count-recoverable-versions"] = len(recoverable)
+ data["count-unrecoverable-versions"] = len(unrecoverable)
+
if recoverable:
report.append("Recoverable Versions: " +
"/".join(["%d*%s" % (len(vmap[v]),
smap.summarize_version(v))
for v in unrecoverable]))
if smap.unrecoverable_versions():
- self.results.healthy = False
+ healthy = False
+ summary.append("some versions are unrecoverable")
report.append("Unhealthy: some versions are unrecoverable")
if len(recoverable) == 0:
- self.results.healthy = False
+ healthy = False
+ summary.append("no versions are recoverable")
report.append("Unhealthy: no versions are recoverable")
if len(recoverable) > 1:
- self.results.healthy = False
+ healthy = False
+ summary.append("multiple versions are recoverable")
report.append("Unhealthy: there are multiple recoverable versions")
- if self.best_version:
+
+ if recoverable:
+ best_version = smap.best_recoverable_version()
report.append("Best Recoverable Version: " +
- smap.summarize_version(self.best_version))
- available_shares = smap.shares_available()
- (num_distinct_shares, k, N) = available_shares[self.best_version]
- if num_distinct_shares < N:
- self.results.healthy = False
- report.append("Unhealthy: best recoverable version has only %d shares (encoding is %d-of-%d)"
- % (num_distinct_shares, k, N))
+ smap.summarize_version(best_version))
+ counters = self._count_shares(smap, best_version)
+ data.update(counters)
+ if counters["count-shares-good"] < counters["count-shares-expected"]:
+ healthy = False
+ report.append("Unhealthy: best version has only %d shares "
+ "(encoding is %d-of-%d)"
+ % (counters["count-shares-good"],
+ counters["count-shares-needed"],
+ counters["count-shares-expected"]))
+ summary.append("%d shares (enc %d-of-%d)"
+ % (counters["count-shares-good"],
+ counters["count-shares-needed"],
+ counters["count-shares-expected"]))
+ elif unrecoverable:
+ healthy = False
+ # find a k and N from somewhere
+ first = list(unrecoverable)[0]
+ # not exactly the best version, but that doesn't matter too much
+ data.update(self._count_shares(smap, first))
+ else:
+ # couldn't find anything at all
+ data["count-shares-good"] = 0
+ data["count-shares-needed"] = 3 # arbitrary defaults
+ data["count-shares-expected"] = 10
+ data["count-good-share-hosts"] = 0
+ data["count-wrong-shares"] = 0
+
if self.bad_shares:
+ data["count-corrupt-shares"] = len(self.bad_shares)
+ data["list-corrupt-shares"] = locators = []
report.append("Corrupt Shares:")
+ summary.append("Corrupt Shares:")
for (peerid, shnum, f) in sorted(self.bad_shares):
+ locators.append( (peerid, self._storage_index, shnum) )
s = "%s-sh%d" % (idlib.shortnodeid_b2a(peerid), shnum)
if f.check(CorruptShareError):
ft = f.value.reason
else:
ft = str(f)
report.append(" %s: %s" % (s, ft))
+ summary.append(s)
p = (peerid, self._storage_index, shnum, f)
- self.results.problems.append(p)
+ r.problems.append(p)
msg = ("CorruptShareError during mutable verify, "
"peerid=%(peerid)s, si=%(si)s, shnum=%(shnum)d, "
"where=%(where)s")
shnum=shnum,
where=ft,
level=log.WEIRD, umid="EkK8QA")
+ else:
+ data["count-corrupt-shares"] = 0
+ data["list-corrupt-shares"] = []
+
+ # TODO: servers-responding, sharemap
+
+ r.set_healthy(healthy)
+ r.set_needs_rebalancing(False) # TODO
+ r.set_data(data)
+ if healthy:
+ r.set_summary("Healthy")
+ else:
+ r.set_summary("Unhealthy: " + " ".join(summary))
+ r.set_report(report)
- self.results.status_report = "\n".join(report) + "\n"
- def _maybe_do_repair(self, res):
+class MutableCheckAndRepairer(MutableChecker):
+ def __init__(self, node):
+ MutableChecker.__init__(self, node)
+ self.cr_results = CheckAndRepairResults(self._storage_index)
+ self.cr_results.pre_repair_results = self.results
+ self.need_repair = False
+
+ def check(self, verify=False):
+ d = MutableChecker.check(self, verify)
+ d.addCallback(self._maybe_repair)
+ d.addCallback(lambda res: self.cr_results)
+ return d
+
+ def _maybe_repair(self, res):
if not self.need_repair:
+ self.cr_results.post_repair_results = self.results
return
- self.results.repair_attempted = True
+ self.cr_results.repair_attempted = True
d = self._node.repair(self.results)
def _repair_finished(repair_results):
- self.results.repair_succeeded = True
- self.results.repair_results = repair_results
+ self.cr_results.repair_successful = True
+ r = CheckerResults(self._storage_index)
+ self.cr_results.post_repair_results = r
+ self._fill_checker_results(repair_results.servermap, r)
+ self.cr_results.repair_results = repair_results # TODO?
def _repair_error(f):
# I'm not sure if I want to pass through a failure or not.
- self.results.repair_succeeded = False
- self.results.repair_failure = f
+ self.cr_results.repair_successful = False
+ self.cr_results.repair_failure = f # TODO?
+ #self.cr_results.post_repair_results = ??
return f
d.addCallbacks(_repair_finished, _repair_error)
return d
-
- def _return_results(self, res):
- return self.results
-
-
-class Results:
- implements(ICheckerResults)
-
- def __init__(self, storage_index):
- self.storage_index = storage_index
- self.storage_index_s = base32.b2a(storage_index)[:6]
- self.repair_attempted = False
- self.status_report = "[not generated yet]" # string
- self.repair_report = None
- self.problems = [] # list of (peerid, storage_index, shnum, failure)
-
- def is_healthy(self):
- return self.healthy
-
- def get_storage_index(self):
- return self.storage_index
- def get_storage_index_string(self):
- return self.storage_index_s
-
- def get_mutability_string(self):
- return "mutable"
-
- def to_string(self):
- s = ""
- if self.healthy:
- s += "Healthy!\n"
- else:
- s += "Not Healthy!\n"
- s += "\n"
- s += self.status_report
- s += "\n"
- if self.repair_attempted:
- s += "Repair attempted "
- if self.repair_succeeded:
- s += "and successful\n"
- else:
- s += "and failed\n"
- s += "\n"
- s += self.repair_results.to_string()
- s += "\n"
- return s
-
from allmydata.util.assertutil import precondition
from allmydata.uri import WriteableSSKFileURI
from allmydata.immutable.encode import NotEnoughSharesError
-from allmydata.immutable.checker import DeepCheckResults
+from allmydata.checker_results import DeepCheckResults, \
+ DeepCheckAndRepairResults
from pycryptopp.publickey import rsa
from pycryptopp.cipher.aes import AES
ResponseCache, UncoordinatedWriteError
from servermap import ServerMap, ServermapUpdater
from retrieve import Retrieve
-from checker import MutableChecker
+from checker import MutableChecker, MutableCheckAndRepairer
from repair import Repairer
SIGNATURE_KEY_SIZE = 2048
DEFAULT_ENCODING = (3, 10)
checker_class = MutableChecker
+ check_and_repairer_class = MutableCheckAndRepairer
def __init__(self, client):
self._client = client
#################################
# ICheckable
- def check(self, verify=False, repair=False):
+ def check(self, verify=False):
checker = self.checker_class(self)
- return checker.check(verify, repair)
+ return checker.check(verify)
- def deep_check(self, verify=False, repair=False):
- d = self.check(verify, repair)
+ def check_and_repair(self, verify=False):
+ checker = self.check_and_repairer_class(self)
+ return checker.check(verify)
+
+ def deep_check(self, verify=False):
+ # deep-check on a filenode only gets one result
+ d = self.check(verify)
def _done(r):
dr = DeepCheckResults(self.get_storage_index())
- dr.add_check(r)
+ dr.add_check(r, [])
+ return dr
+ d.addCallback(_done)
+ return d
+
+ def deep_check_and_repair(self, verify=False):
+ d = self.check_and_repair(verify)
+ def _done(r):
+ dr = DeepCheckAndRepairResults(self.get_storage_index())
+ dr.add_check_and_repair(r, [])
return dr
d.addCallback(_done)
return d
from zope.interface import implements
-from allmydata.interfaces import IRepairResults
+from allmydata.interfaces import IRepairResults, ICheckerResults
class RepairResults:
implements(IRepairResults)
+ def __init__(self, smap):
+ self.servermap = smap
+
def to_string(self):
return ""
class Repairer:
def __init__(self, node, checker_results):
self.node = node
- self.checker_results = checker_results
+ self.checker_results = ICheckerResults(checker_results)
assert checker_results.storage_index == self.node.get_storage_index()
def start(self, force=False):
# 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.servermap
+ smap = self.checker_results.get_servermap()
if smap.unrecoverable_newer_versions():
if not force:
best_version = smap.best_recoverable_version()
d = self.node.download_version(smap, best_version, fetch_privkey=True)
d.addCallback(self.node.upload, smap)
- d.addCallback(self.get_results)
+ d.addCallback(self.get_results, smap)
return d
- def get_results(self, res):
- return RepairResults()
+ def get_results(self, res, smap):
+ return RepairResults(smap)
self.last_update_mode = None
self.last_update_time = 0
+ def copy(self):
+ s = ServerMap()
+ s.servermap = self.servermap.copy() # tuple->tuple
+ s.connections = self.connections.copy() # str->RemoteReference
+ s.unreachable_peers = set(self.unreachable_peers)
+ s.problems = self.problems[:]
+ s.bad_shares = self.bad_shares.copy() # tuple->str
+ s.last_update_mode = self.last_update_mode
+ s.last_update_time = self.last_update_time
+ return s
+
def mark_bad_share(self, peerid, shnum, checkstring):
"""This share was found to be bad, either in the checkstring or
signature (detected during mapupdate), or deeper in the share
for (peerid, shnum)
in self.servermap])
+ def all_peers_for_version(self, verinfo):
+ """Return a set of peerids that hold shares for the given version."""
+ return set([peerid
+ for ( (peerid, shnum), (verinfo2, timestamp) )
+ in self.servermap.items()
+ if verinfo == verinfo2])
+
def make_sharemap(self):
"""Return a dict that maps shnum to a set of peerds that hold it."""
sharemap = DictOfSets()
from allmydata.introducer.server import IntroducerNode
from allmydata.interfaces import IURI, IMutableFileNode, IFileNode, \
FileTooLargeError, ICheckable
-from allmydata.immutable import checker
from allmydata.immutable.encode import NotEnoughSharesError
-from allmydata.mutable.checker import Results as MutableCheckerResults
+from allmydata.checker_results import CheckerResults, CheckAndRepairResults, \
+ DeepCheckResults, DeepCheckAndRepairResults
from allmydata.mutable.common import CorruptShareError
from allmydata.util import log, testutil, fileutil
from allmydata.stats import PickleStatsGatherer
return self.my_uri
def get_verifier(self):
return IURI(self.my_uri).get_verifier()
- def check(self, verify=False, repair=False):
- r = checker.Results(None)
+ def check(self, verify=False):
+ r = CheckerResults(self.storage_index)
is_bad = self.bad_shares.get(self.storage_index, None)
+ data = {}
if is_bad:
- r.healthy = False
+ r.set_healthy(False)
r.problems = failure.Failure(CorruptShareError(is_bad))
else:
- r.healthy = True
+ r.set_healthy(True)
r.problems = []
+ r.set_data(data)
return defer.succeed(r)
+ def check_and_repair(self, verify=False):
+ d = self.check(verify)
+ def _got(cr):
+ r = CheckAndRepairResults(self.storage_index)
+ r.pre_repair_results = r.post_repair_results = cr
+ return r
+ d.addCallback(_got)
+ return d
+
def is_mutable(self):
return False
def is_readonly(self):
def get_storage_index(self):
return self.storage_index
- def check(self, verify=False, repair=False):
- r = MutableCheckerResults(self.storage_index)
+ def check(self, verify=False):
+ r = CheckerResults(self.storage_index)
is_bad = self.bad_shares.get(self.storage_index, None)
+ data = {}
+ data["list-corrupt-shares"] = []
if is_bad:
- r.healthy = False
+ r.set_healthy(False)
r.problems = failure.Failure(CorruptShareError("peerid",
0, # shnum
is_bad))
else:
- r.healthy = True
+ r.set_healthy(True)
r.problems = []
+ r.set_data(data)
return defer.succeed(r)
- def deep_check(self, verify=False, repair=False):
- d = self.check(verify, repair)
+ def check_and_repair(self, verify=False):
+ d = self.check(verify)
+ def _got(cr):
+ r = CheckAndRepairResults(self.storage_index)
+ r.pre_repair_results = r.post_repair_results = cr
+ return r
+ d.addCallback(_got)
+ return d
+
+ def deep_check(self, verify=False):
+ d = self.check(verify)
+ def _done(r):
+ dr = DeepCheckResults(self.storage_index)
+ dr.add_check(r, [])
+ return dr
+ d.addCallback(_done)
+ return d
+
+ def deep_check_and_repair(self, verify=False):
+ d = self.check_and_repair(verify)
def _done(r):
- dr = checker.DeepCheckResults(self.storage_index)
- dr.add_check(r)
+ dr = DeepCheckAndRepairResults(self.storage_index)
+ dr.add_check(r, [])
return dr
d.addCallback(_done)
return d
from twisted.trial import unittest
from twisted.internet import defer
from allmydata import uri, dirnode
-from allmydata.immutable import upload, checker
+from allmydata.immutable import upload
from allmydata.interfaces import IURI, IClient, IMutableFileNode, \
- INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode, ExistingChildError
+ INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode, \
+ ExistingChildError, IDeepCheckResults, IDeepCheckAndRepairResults
from allmydata.util import hashutil, testutil
from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \
FakeDirectoryNode, create_chk_filenode
+from allmydata.checker_results import CheckerResults, CheckAndRepairResults
# to test dirnode.py, we want to construct a tree of real DirectoryNodes that
# contain pointers to fake files. We start with a fake MutableFileNode that
def get_verifier(self):
return self.verifieruri
- def check(self, verify=False, repair=False):
- r = checker.Results(None)
- r.healthy = True
- r.problems = []
+ def check(self, verify=False):
+ r = CheckerResults(None)
+ r.set_healthy(True)
return defer.succeed(r)
+ def check_and_repair(self, verify=False):
+ d = self.check(verify)
+ def _got(cr):
+ r = CheckAndRepairResults(None)
+ r.pre_repair_results = r.post_repair_results = cr
+ return r
+ d.addCallback(_got)
+ return d
+
# dirnode requires three methods from the client: upload(),
# create_node_from_uri(), and create_empty_dirnode(). Of these, upload() is
# only used by the convenience composite method add_file().
d = self._test_deepcheck_create()
d.addCallback(lambda rootnode: rootnode.deep_check())
def _check_results(r):
- self.failUnlessEqual(r.count_objects_checked(), 3)
- self.failUnlessEqual(r.count_objects_healthy(), 3)
- self.failUnlessEqual(r.count_repairs_attempted(), 0)
- self.failUnlessEqual(r.count_repairs_successful(), 0)
- self.failUnlessEqual(len(r.get_server_problems()), 0)
- self.failUnlessEqual(len(r.get_problems()), 0)
+ self.failUnless(IDeepCheckResults.providedBy(r))
+ c = r.get_counters()
+ self.failUnlessEqual(c,
+ {"count-objects-checked": 3,
+ "count-objects-healthy": 3,
+ "count-objects-unhealthy": 0,
+ "count-corrupt-shares": 0,
+ })
+ self.failIf(r.get_corrupt_shares())
+ self.failUnlessEqual(len(r.get_all_results()), 3)
+ d.addCallback(_check_results)
+ return d
+
+ def test_deepcheck_and_repair(self):
+ d = self._test_deepcheck_create()
+ d.addCallback(lambda rootnode: rootnode.deep_check_and_repair())
+ def _check_results(r):
+ self.failUnless(IDeepCheckAndRepairResults.providedBy(r))
+ c = r.get_counters()
+ self.failUnlessEqual(c,
+ {"count-objects-checked": 3,
+ "count-objects-healthy-pre-repair": 3,
+ "count-objects-unhealthy-pre-repair": 0,
+ "count-corrupt-shares-pre-repair": 0,
+ "count-objects-healthy-post-repair": 3,
+ "count-objects-unhealthy-post-repair": 0,
+ "count-corrupt-shares-post-repair": 0,
+ "count-repairs-attempted": 0,
+ "count-repairs-successful": 0,
+ "count-repairs-unsuccessful": 0,
+ })
+ self.failIf(r.get_corrupt_shares())
+ self.failIf(r.get_remaining_corrupt_shares())
+ self.failUnlessEqual(len(r.get_all_results()), 3)
d.addCallback(_check_results)
return d
d.addCallback(lambda rootnode: self._mark_file_bad(rootnode))
d.addCallback(lambda rootnode: rootnode.deep_check())
def _check_results(r):
- self.failUnlessEqual(r.count_objects_checked(), 3)
- self.failUnlessEqual(r.count_objects_healthy(), 2)
- self.failUnlessEqual(r.count_repairs_attempted(), 0)
- self.failUnlessEqual(r.count_repairs_successful(), 0)
- self.failUnlessEqual(len(r.get_server_problems()), 0)
- self.failUnlessEqual(len(r.get_problems()), 1)
+ c = r.get_counters()
+ self.failUnlessEqual(c,
+ {"count-objects-checked": 3,
+ "count-objects-healthy": 2,
+ "count-objects-unhealthy": 1,
+ "count-corrupt-shares": 0,
+ })
+ #self.failUnlessEqual(len(r.get_problems()), 1) # TODO
d.addCallback(_check_results)
return d
from twisted.trial import unittest
from twisted.internet import defer
from allmydata import uri
-from allmydata.immutable import filenode, download, checker
+from allmydata.immutable import filenode, download
+from allmydata.checker_results import CheckerResults, CheckAndRepairResults
from allmydata.mutable.node import MutableFileNode
from allmydata.util import hashutil
d.addCallback(lambda res: fn1.check(verify=True))
d.addCallback(_check_checker_results)
+ # TODO: check-and-repair
+
d.addCallback(lambda res: fn1.deep_check())
def _check_deepcheck_results(dcr):
- self.failIf(dcr.get_problems())
+ c = dcr.get_counters()
+ self.failUnlessEqual(c["count-objects-checked"], 1)
+ self.failUnlessEqual(c["count-objects-healthy"], 1)
+ self.failUnlessEqual(c["count-objects-unhealthy"], 0)
+ self.failUnlessEqual(c["count-corrupt-shares"], 0)
+ self.failIf(dcr.get_corrupt_shares())
+ d.addCallback(_check_deepcheck_results)
+
+ d.addCallback(lambda res: fn1.deep_check(verify=True))
d.addCallback(_check_deepcheck_results)
+
return d
def test_literal_filenode(self):
d = fn1.check()
def _check_checker_results(cr):
- self.failUnless(cr.is_healthy())
+ self.failUnlessEqual(cr, None)
d.addCallback(_check_checker_results)
d.addCallback(lambda res: fn1.check(verify=True))
d.addCallback(lambda res: fn1.deep_check())
def _check_deepcheck_results(dcr):
- self.failIf(dcr.get_problems())
+ c = dcr.get_counters()
+ self.failUnlessEqual(c["count-objects-checked"], 0)
+ self.failUnlessEqual(c["count-objects-healthy"], 0)
+ self.failUnlessEqual(c["count-objects-unhealthy"], 0)
+ self.failUnlessEqual(c["count-corrupt-shares"], 0)
+ self.failIf(dcr.get_corrupt_shares())
+ d.addCallback(_check_deepcheck_results)
+
+ d.addCallback(lambda res: fn1.deep_check(verify=True))
d.addCallback(_check_deepcheck_results)
return d
n = MutableFileNode(client).init_from_uri(u)
n.checker_class = FakeMutableChecker
+ n.check_and_repairer_class = FakeMutableCheckAndRepairer
d = n.check()
def _check_checker_results(cr):
d.addCallback(lambda res: n.deep_check())
def _check_deepcheck_results(dcr):
- self.failIf(dcr.get_problems())
+ c = dcr.get_counters()
+ self.failUnlessEqual(c["count-objects-checked"], 1)
+ self.failUnlessEqual(c["count-objects-healthy"], 1)
+ self.failUnlessEqual(c["count-objects-unhealthy"], 0)
+ self.failUnlessEqual(c["count-corrupt-shares"], 0)
+ self.failIf(dcr.get_corrupt_shares())
d.addCallback(_check_deepcheck_results)
+
+ d.addCallback(lambda res: n.deep_check(verify=True))
+ d.addCallback(_check_deepcheck_results)
+
return d
class FakeMutableChecker:
def __init__(self, node):
- self.r = checker.Results(node.get_storage_index())
- self.r.healthy = True
- self.r.problems = []
+ self.r = CheckerResults(node.get_storage_index())
+ self.r.set_healthy(True)
+
+ def check(self, verify):
+ return defer.succeed(self.r)
+
+class FakeMutableCheckAndRepairer:
+ def __init__(self, node):
+ cr = CheckerResults(node.get_storage_index())
+ cr.set_healthy(True)
+ self.r = CheckAndRepairResults(node.get_storage_index())
+ self.r.pre_repair_results = self.r.post_repair_results = cr
- def check(self, verify, repair):
+ def check(self, verify):
return defer.succeed(self.r)
class FakeImmutableChecker:
def __init__(self, client, storage_index, needed_shares, total_shares):
- self.r = checker.Results(storage_index)
- self.r.healthy = True
- self.r.problems = []
+ self.r = CheckerResults(storage_index)
+ self.r.set_healthy(True)
def start(self):
return defer.succeed(self.r)
class CheckerMixin:
def check_good(self, r, where):
- self.failUnless(r.healthy, where)
- self.failIf(r.problems, where)
+ self.failUnless(r.is_healthy(), where)
return r
def check_bad(self, r, where):
- self.failIf(r.healthy, where)
+ self.failIf(r.is_healthy(), where)
return r
def check_expected_failure(self, r, expected_exception, substring, where):
def _got_lit_filenode(n):
self.failUnless(isinstance(n, filenode.LiteralFileNode))
d = n.check()
- def _check_filenode_results(r):
- self.failUnless(r.is_healthy())
- d.addCallback(_check_filenode_results)
+ def _check_lit_filenode_results(r):
+ self.failUnlessEqual(r, None)
+ d.addCallback(_check_lit_filenode_results)
d.addCallback(lambda res: n.check(verify=True))
- d.addCallback(_check_filenode_results)
+ d.addCallback(_check_lit_filenode_results)
return d
d.addCallback(_got_lit_filenode)
return d
def _check1(filenode):
before_check_reads = self._count_reads()
- d2 = filenode.check(verify=False, repair=False)
+ d2 = filenode.check(verify=False)
def _after_check(checkresults):
after_check_reads = self._count_reads()
self.failIf(after_check_reads - before_check_reads > 0, after_check_reads - before_check_reads)
d.addCallback(self._corrupt_a_share)
def _check2(ignored):
before_check_reads = self._count_reads()
- d2 = self.filenode.check(verify=False, repair=False)
+ d2 = self.filenode.check(verify=False)
def _after_check(checkresults):
after_check_reads = self._count_reads()
d.addCallback(lambda ignore: self.replace_shares({}))
def _check3(ignored):
before_check_reads = self._count_reads()
- d2 = self.filenode.check(verify=False, repair=False)
+ d2 = self.filenode.check(verify=False)
def _after_check(checkresults):
after_check_reads = self._count_reads()
def _check1(filenode):
before_check_reads = self._count_reads()
- d2 = filenode.check(verify=True, repair=False)
+ d2 = filenode.check(verify=True)
def _after_check(checkresults):
after_check_reads = self._count_reads()
# print "delta was ", after_check_reads - before_check_reads
d.addCallback(self._corrupt_a_share)
def _check2(ignored):
before_check_reads = self._count_reads()
- d2 = self.filenode.check(verify=True, repair=False)
+ d2 = self.filenode.check(verify=True)
def _after_check(checkresults):
after_check_reads = self._count_reads()
return getPage(url, method="POST")
d.addCallback(_do_check)
def _got_results(out):
- self.failUnless("<pre>Healthy!" in out, out)
+ self.failUnless("<div>Healthy!</div>" in out, out)
+ self.failUnless("Recoverable Versions: 10*seq1-" in out, out)
self.failIf("Not Healthy!" in out, out)
self.failIf("Unhealthy" in out, out)
self.failIf("Corrupt Shares" in out, out)
d.addCallback(_do_check)
def _got_results(out):
self.failUnless("Not Healthy!" in out, out)
- self.failUnless("Unhealthy: best recoverable version has only 9 shares (encoding is 3-of-10)" in out, out)
- shid_re = (r"Corrupt Shares:\s+%s: block hash tree failure" %
- self.corrupt_shareid)
- self.failUnless(re.search(shid_re, out), out)
+ self.failUnless("Unhealthy: best version has only 9 shares (encoding is 3-of-10)" in out, out)
+ self.failUnless("Corrupt Shares:" in out, out)
d.addCallback(_got_results)
# now make sure the webapi repairer can fix it
return getPage(url, method="POST")
d.addCallback(_do_repair)
def _got_repair_results(out):
- self.failUnless("Repair attempted and successful" in out)
+ self.failUnless("<div>Repair successful</div>" in out, out)
d.addCallback(_got_repair_results)
d.addCallback(_do_check)
def _got_postrepair_results(out):
self.failIf("Not Healthy!" in out, out)
- self.failUnless("Recoverable Versions: 10*seq" in out)
+ self.failUnless("Recoverable Versions: 10*seq" in out, out)
d.addCallback(_got_postrepair_results)
return d
d.addCallback(_do_check)
def _got_results(out):
self.failUnless("Not Healthy!" in out, out)
- self.failUnless("Unhealthy: best recoverable version has only 9 shares (encoding is 3-of-10)" in out, out)
+ self.failUnless("Unhealthy: best version has only 9 shares (encoding is 3-of-10)" in out, out)
self.failIf("Corrupt Shares" in out, out)
d.addCallback(_got_results)
return getPage(url, method="POST")
d.addCallback(_do_repair)
def _got_repair_results(out):
- self.failUnless("Repair attempted and successful" in out)
+ self.failUnless("Repair successful" in out)
d.addCallback(_got_repair_results)
d.addCallback(_do_check)
def _got_postrepair_results(out):
d.addCallback(_check3)
return d
+ def test_POST_FILEURL_check_and_repair(self):
+ bar_url = self.public_url + "/foo/bar.txt"
+ d = self.POST(bar_url, t="check", repair="true")
+ def _check(res):
+ self.failUnless("Healthy!" in res)
+ d.addCallback(_check)
+ redir_url = "http://allmydata.org/TARGET"
+ def _check2(statuscode, target):
+ self.failUnlessEqual(statuscode, str(http.FOUND))
+ self.failUnlessEqual(target, redir_url)
+ d.addCallback(lambda res:
+ self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
+ _check2,
+ self.POST, bar_url,
+ t="check", repair="true",
+ when_done=redir_url))
+ d.addCallback(lambda res:
+ self.POST(bar_url, t="check", return_to=redir_url))
+ def _check3(res):
+ self.failUnless("Healthy!" in res)
+ self.failUnless("Return to parent directory" in res)
+ self.failUnless(redir_url in res)
+ d.addCallback(_check3)
+ return d
+
def test_POST_DIRURL_check(self):
foo_url = self.public_url + "/foo/"
d = self.POST(foo_url, t="check")
d.addCallback(_check3)
return d
+ def test_POST_DIRURL_check_and_repair(self):
+ foo_url = self.public_url + "/foo/"
+ d = self.POST(foo_url, t="check", repair="true")
+ def _check(res):
+ self.failUnless("Healthy!" in res)
+ d.addCallback(_check)
+ redir_url = "http://allmydata.org/TARGET"
+ def _check2(statuscode, target):
+ self.failUnlessEqual(statuscode, str(http.FOUND))
+ self.failUnlessEqual(target, redir_url)
+ d.addCallback(lambda res:
+ self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
+ _check2,
+ self.POST, foo_url,
+ t="check", repair="true",
+ when_done=redir_url))
+ d.addCallback(lambda res:
+ self.POST(foo_url, t="check", return_to=redir_url))
+ def _check3(res):
+ self.failUnless("Healthy!" in res)
+ self.failUnless("Return to parent directory" in res)
+ self.failUnless(redir_url in res)
+ d.addCallback(_check3)
+ return d
+
def test_POST_DIRURL_deepcheck(self):
d = self.POST(self.public_url, t="deep-check")
def _check(res):
self.failUnless("Objects Checked: <span>8</span>" in res)
self.failUnless("Objects Healthy: <span>8</span>" in res)
+ d.addCallback(_check)
+ redir_url = "http://allmydata.org/TARGET"
+ def _check2(statuscode, target):
+ self.failUnlessEqual(statuscode, str(http.FOUND))
+ self.failUnlessEqual(target, redir_url)
+ d.addCallback(lambda res:
+ self.shouldRedirect2("test_POST_DIRURL_check",
+ _check2,
+ self.POST, self.public_url,
+ t="deep-check",
+ when_done=redir_url))
+ d.addCallback(lambda res:
+ self.POST(self.public_url, t="deep-check",
+ return_to=redir_url))
+ def _check3(res):
+ self.failUnless("Return to parent directory" in res)
+ self.failUnless(redir_url in res)
+ d.addCallback(_check3)
+ return d
+
+ def test_POST_DIRURL_deepcheck_and_repair(self):
+ d = self.POST(self.public_url, t="deep-check", repair="true")
+ def _check(res):
+ self.failUnless("Objects Checked: <span>8</span>" in res)
+
+ self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
+ self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
+ self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
+
self.failUnless("Repairs Attempted: <span>0</span>" in res)
self.failUnless("Repairs Successful: <span>0</span>" in res)
+ self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
+
+ self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
+ self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
+ self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
d.addCallback(_check)
redir_url = "http://allmydata.org/TARGET"
def _check2(statuscode, target):
--- /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 n:render="healthy" />
+
+<div n:render="repair_results" />
+
+<div n:render="post_repair_results" />
+
+<div n:render="maybe_pre_repair_results" />
+
+<div n:render="return" />
+
+ </body>
+</html>
</head>
<body>
-<h1>File Check Results for SI=<span n:render="storage_index" /> (<span n:render="mutability" />)</h1>
+<h1>File Check Results for SI=<span n:render="storage_index" /></h1>
-<pre n:render="results" />
+<div n:render="healthy" />
+
+<div n:render="results" />
<div n:render="return" />
import time
from nevow import rend, inevow, tags as T
-from allmydata.web.common import getxmlfile, get_arg
-from allmydata.interfaces import ICheckerResults, IDeepCheckResults
+from twisted.web import html
+from allmydata.web.common import getxmlfile, get_arg, IClient
+from allmydata.interfaces import ICheckAndRepairResults, ICheckerResults, \
+ IDeepCheckResults, IDeepCheckAndRepairResults
+from allmydata.util import base32, idlib
-class CheckerResults(rend.Page):
+class ResultsBase:
+ def _render_results(self, cr):
+ assert ICheckerResults(cr)
+ return T.pre["\n".join(self._html(cr.get_report()))] # TODO: more
+ 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]
+
+class CheckerResults(rend.Page, ResultsBase):
docFactory = getxmlfile("checker-results.xhtml")
def __init__(self, results):
- assert ICheckerResults(results)
- self.r = results
+ self.r = ICheckerResults(results)
def render_storage_index(self, ctx, data):
return self.r.get_storage_index_string()
- def render_mutability(self, ctx, data):
- return self.r.get_mutability_string()
+ def render_healthy(self, ctx, data):
+ if self.r.is_healthy():
+ return ctx.tag["Healthy!"]
+ return ctx.tag["Not Healthy!:", self._html(self.r.get_summary())]
def render_results(self, ctx, data):
- return ctx.tag[self.r.to_string()]
+ cr = self._render_results(self.r)
+ return ctx.tag[cr]
def render_return(self, ctx, data):
req = inevow.IRequest(ctx)
return T.div[T.a(href=return_to)["Return to parent directory"]]
return ""
-class DeepCheckResults(rend.Page):
+class CheckAndRepairResults(rend.Page, ResultsBase):
+ docFactory = getxmlfile("check-and-repair-results.xhtml")
+
+ def __init__(self, results):
+ self.r = ICheckAndRepairResults(results)
+
+ def render_storage_index(self, ctx, data):
+ return self.r.get_storage_index_string()
+
+ def render_healthy(self, ctx, data):
+ cr = self.r.get_post_repair_results()
+ if cr.is_healthy():
+ return ctx.tag["Healthy!"]
+ return ctx.tag["Not Healthy!:", self._html(cr.get_summary())]
+
+ 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(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(self.r.get_pre_repair_results())
+ return ctx.tag[T.div["Pre-Repair Checker Results:"], cr]
+ return ""
+
+ 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 DeepCheckResults(rend.Page, ResultsBase):
docFactory = getxmlfile("deep-check-results.xhtml")
def __init__(self, results):
return self.r.get_root_storage_index_string()
def data_objects_checked(self, ctx, data):
- return self.r.count_objects_checked()
+ return self.r.get_counters()["count-objects-checked"]
def data_objects_healthy(self, ctx, data):
- return self.r.count_objects_healthy()
- def data_repairs_attempted(self, ctx, data):
- return self.r.count_repairs_attempted()
- def data_repairs_successful(self, ctx, data):
- return self.r.count_repairs_successful()
+ return self.r.get_counters()["count-objects-healthy"]
+ def data_objects_unhealthy(self, ctx, data):
+ return self.r.get_counters()["count-objects-unhealthy"]
+
+ def data_count_corrupt_shares(self, ctx, data):
+ return self.r.get_counters()["count-corrupt-shares"]
+
+ def render_problems_p(self, ctx, data):
+ c = self.r.get_counters()
+ if c["count-objects-unhealthy"]:
+ return ctx.tag
+ return ""
def data_problems(self, ctx, data):
- for cr in self.r.get_problems():
- yield cr
+ all_objects = self.r.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):
- cr = data
- text = cr.get_storage_index_string()
- text += ": "
- text += cr.status_report
- return ctx.tag[text]
+ 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.r.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.r.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.r.get_counters()["count-corrupt-shares"]:
+ return ctx.tag
+ return ""
+ def data_corrupt_shares(self, ctx, data):
+ return self.r.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", base32.b2a(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.r.get_all_results()
- for storage_index in sorted(r.keys()):
- yield r[storage_index]
+ for path in sorted(r.keys()):
+ yield (path, r[path])
def render_object(self, ctx, data):
- r = data
- ctx.fillSlots("storage_index", r.get_storage_index_string())
+ path, r = data
+ ctx.fillSlots("path", "/".join(self._html(path)))
ctx.fillSlots("healthy", str(r.is_healthy()))
+ 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):
+ docFactory = getxmlfile("deep-check-and-repair-results.xhtml")
+
+ def __init__(self, results):
+ assert IDeepCheckAndRepairResults(results)
+ self.r = results
+
+ def render_root_storage_index(self, ctx, data):
+ return self.r.get_root_storage_index_string()
+
+ def data_objects_checked(self, ctx, data):
+ return self.r.get_counters()["count-objects-checked"]
+
+ def data_objects_healthy(self, ctx, data):
+ return self.r.get_counters()["count-objects-healthy-pre-repair"]
+ def data_objects_unhealthy(self, ctx, data):
+ return self.r.get_counters()["count-objects-unhealthy-pre-repair"]
+ def data_corrupt_shares(self, ctx, data):
+ return self.r.get_counters()["count-corrupt-shares-pre-repair"]
+
+ def data_repairs_attempted(self, ctx, data):
+ return self.r.get_counters()["count-repairs-attempted"]
+ def data_repairs_successful(self, ctx, data):
+ return self.r.get_counters()["count-repairs-successful"]
+ def data_repairs_unsuccessful(self, ctx, data):
+ return self.r.get_counters()["count-repairs-unsuccessful"]
+
+ def data_objects_healthy_post(self, ctx, data):
+ return self.r.get_counters()["count-objects-healthy-post-repair"]
+ def data_objects_unhealthy_post(self, ctx, data):
+ return self.r.get_counters()["count-objects-unhealthy-post-repair"]
+ def data_corrupt_shares_post(self, ctx, data):
+ return self.r.get_counters()["count-corrupt-shares-post-repair"]
+
+ def render_pre_repair_problems_p(self, ctx, data):
+ c = self.r.get_counters()
+ if c["count-objects-unhealthy-pre-repair"]:
+ return ctx.tag
+ return ""
+
+ def data_pre_repair_problems(self, ctx, data):
+ all_objects = self.r.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.r.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.r.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.r.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.r.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)
return T.div[T.a(href=return_to)["Return to parent directory"]]
return ""
+ def data_all_objects(self, ctx, data):
+ r = self.r.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
--- /dev/null
+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
+ <head>
+ <title>AllMyData - Tahoe - Deep 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>Deep-Check-And-Repair Results for root
+ SI=<span n:render="root_storage_index" /></h1>
+
+<p>Counters:</p>
+<ul>
+ <li>Objects Checked: <span n:render="data" n:data="objects_checked" /></li>
+
+ <li>Objects Healthy (before repair): <span n:render="data" n:data="objects_healthy" /></li>
+ <li>Objects Unhealthy (before repair): <span n:render="data" n:data="objects_unhealthy" /></li>
+ <li>Corrupt Shares (before repair): <span n:render="data" n:data="corrupt_shares" /></li>
+
+ <li>Repairs Attempted: <span n:render="data" n:data="repairs_attempted" /></li>
+ <li>Repairs Successful: <span n:render="data" n:data="repairs_successful" /></li>
+ <li>Repairs Unsuccessful: <span n:render="data" n:data="repairs_unsuccessful" /></li>
+
+ <li>Objects Healthy (after repair): <span n:render="data" n:data="objects_healthy_post" /></li>
+ <li>Objects Unhealthy (after repair): <span n:render="data" n:data="objects_unhealthy_post" /></li>
+ <li>Corrupt Shares (after repair): <span n:render="data" n:data="corrupt_shares_post" /></li>
+
+</ul>
+
+<div n:render="pre_repair_problems_p">
+<h2>Files/Directories That Had Problems:</h2>
+
+<ul n:render="sequence" n:data="pre_repair_problems">
+ <li n:pattern="item" n:render="problem"/>
+ <li n:pattern="empty">None</li>
+</ul>
+</div>
+
+
+<div n:render="post_repair_problems_p">
+<h2>Files/Directories That Still Have Problems:</h2>
+<ul n:render="sequence" n:data="post_repair_problems">
+ <li n:pattern="item" n:render="problem"/>
+ <li n:pattern="empty">None</li>
+</ul>
+</div>
+
+<div n:render="servers_with_corrupt_shares_p">
+<h2>Servers on which corrupt shares were found</h2>
+<ul n:render="sequence" n:data="servers_with_corrupt_shares">
+ <li n:pattern="item" n:render="server_problem"/>
+ <li n:pattern="empty">None</li>
+</ul>
+</div>
+
+<div n:render="remaining_corrupt_shares_p">
+<h2>Remaining Corrupt Shares</h2>
+<p>These shares need to be manually inspected and removed.</p>
+<ul n:render="sequence" n:data="post_repair_corrupt_shares">
+ <li n:pattern="item" n:render="share_problem"/>
+ <li n:pattern="empty">None</li>
+</ul>
+</div>
+
+<div n:render="return" />
+
+<div>
+<table n:render="sequence" n:data="all_objects">
+ <tr n:pattern="header">
+ <td>Relative Path</td>
+ <td>Healthy</td>
+ <td>Post-Repair</td>
+ <td>Summary</td>
+ </tr>
+ <tr n:pattern="item" n:render="object">
+ <td><n:slot name="path"/></td>
+ <td><n:slot name="healthy_pre_repair"/></td>
+ <td><n:slot name="healthy_post_repair"/></td>
+ <td><n:slot name="summary"/></td>
+ </tr>
+</table>
+</div>
+
+<div n:render="runtime" />
+
+ </body>
+</html>
<h1>Deep-Check Results for root SI=<span n:render="root_storage_index" /></h1>
+<p>Counters:</p>
<ul>
<li>Objects Checked: <span n:render="data" n:data="objects_checked" /></li>
<li>Objects Healthy: <span n:render="data" n:data="objects_healthy" /></li>
+ <li>Objects Unhealthy: <span n:render="data" n:data="objects_unhealthy" /></li>
+ <li>Corrupt Shares: <span n:render="data" n:data="count_corrupt_shares" /></li>
+
</ul>
-<h2>Problems:</h2>
+<div n:render="problems_p">
+<h2>Files/Directories That Had Problems:</h2>
<ul n:render="sequence" n:data="problems">
<li n:pattern="item" n:render="problem"/>
<li n:pattern="empty">None</li>
</ul>
+</div>
-<h2>Repair Results:</h2>
-<ul>
- <li>Repairs Attempted: <span n:render="data" n:data="repairs_attempted" /></li>
- <li>Repairs Successful: <span n:render="data" n:data="repairs_successful" /></li>
+
+<div n:render="servers_with_corrupt_shares_p">
+<h2>Servers on which corrupt shares were found</h2>
+<ul n:render="sequence" n:data="servers_with_corrupt_shares">
+ <li n:pattern="item" n:render="server_problem"/>
+ <li n:pattern="empty">None</li>
</ul>
+</div>
+
+<div n:render="corrupt_shares_p">
+<h2>Corrupt Shares</h2>
+<p>If repair fails, these shares need to be manually inspected and removed.</p>
+<table n:render="sequence" n:data="corrupt_shares" border="1">
+ <tr n:pattern="header">
+ <td>Server</td>
+ <td>Server Nickname</td>
+ <td>Storage Index</td>
+ <td>Share Number</td>
+ </tr>
+ <tr n:pattern="item" n:render="share_problem">
+ <td><n:slot name="serverid"/></td>
+ <td><n:slot name="nickname"/></td>
+ <td><n:slot name="si"/></td>
+ <td><n:slot name="shnum"/></td>
+ </tr>
+</table>
+</div>
+
+<div n:render="return" />
-<h2>Objects Checked</h2>
<div>
+<h2>All Results</h2>
<table n:render="sequence" n:data="all_objects" border="1">
<tr n:pattern="header">
- <td>Storage Index</td>
- <td>Healthy?</td>
+ <td>Relative Path</td>
+ <td>Healthy</td>
+ <td>Summary</td>
</tr>
<tr n:pattern="item" n:render="object">
- <td><n:slot name="storage_index"/></td>
+ <td><n:slot name="path"/></td>
<td><n:slot name="healthy"/></td>
+ <td><n:slot name="summary"/></td>
</tr>
-
- <tr n:pattern="empty"><td>no objects?</td></tr>
-
</table>
</div>
-
-<div n:render="return" />
-
<div n:render="runtime" />
</body>
getxmlfile, RenderMixin
from allmydata.web.filenode import ReplaceMeMixin, \
FileNodeHandler, PlaceHolderNodeHandler
-from allmydata.web.checker_results import CheckerResults, DeepCheckResults
+from allmydata.web.checker_results import CheckerResults, DeepCheckResults, \
+ DeepCheckAndRepairResults
class BlockingFileError(Exception):
# TODO: catch and transform
# check this directory and everything reachable from it
verify = boolean_of_arg(get_arg(req, "verify", "false"))
repair = boolean_of_arg(get_arg(req, "repair", "false"))
- d = self.node.deep_check(verify, repair)
- d.addCallback(lambda res: DeepCheckResults(res))
+ if repair:
+ d = self.node.deep_check_and_repair(verify)
+ d.addCallback(lambda res: DeepCheckAndRepairResults(res))
+ else:
+ d = self.node.deep_check(verify)
+ d.addCallback(lambda res: DeepCheckResults(res))
return d
def _POST_set_children(self, req):
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.checker_results import CheckerResults, CheckAndRepairResults
class ReplaceMeMixin:
def _POST_check(self, req):
verify = boolean_of_arg(get_arg(req, "verify", "false"))
repair = boolean_of_arg(get_arg(req, "repair", "false"))
- d = self.node.check(verify, repair)
- d.addCallback(lambda res: CheckerResults(res))
+ if repair:
+ d = self.node.check_and_repair(verify)
+ d.addCallback(lambda res: CheckAndRepairResults(res))
+ else:
+ d = self.node.check(verify)
+ d.addCallback(lambda res: CheckerResults(res))
return d
def render_DELETE(self, ctx):