mutable repair: return successful=False when numshares<k (thus repair fails),

instead of weird errors. Closes #874 and #786.

Previously, if the file had 0 shares, this would raise TypeError as it tried
to call download_version(None). If the file had some shares but fewer than
'k', it would incorrectly raise MustForceRepairError.

Added get_successful() to the IRepairResults API, to give repair() a place to
report non-code-bug problems like this.
This commit is contained in:
Brian Warner 2009-12-29 15:37:46 -08:00
parent acd211765c
commit ba0690c9d7
4 changed files with 53 additions and 4 deletions

View File

@ -2002,6 +2002,10 @@ class IRepairable(Interface):
class IRepairResults(Interface): class IRepairResults(Interface):
"""I contain the results of a repair operation.""" """I contain the results of a repair operation."""
def get_successful(self):
"""Returns a boolean: True if the repair made the file healthy, False
if not. Repair failure generally indicates a file that has been
damaged beyond repair."""
class IClient(Interface): class IClient(Interface):

View File

@ -306,7 +306,7 @@ class MutableCheckAndRepairer(MutableChecker):
self.cr_results.repair_attempted = True self.cr_results.repair_attempted = True
d = self._node.repair(self.results) d = self._node.repair(self.results)
def _repair_finished(repair_results): def _repair_finished(repair_results):
self.cr_results.repair_successful = True self.cr_results.repair_successful = repair_results.get_successful()
r = CheckResults(from_string(self._node.get_uri()), self._storage_index) r = CheckResults(from_string(self._node.get_uri()), self._storage_index)
self.cr_results.post_repair_results = r self.cr_results.post_repair_results = r
self._fill_checker_results(repair_results.servermap, r) self._fill_checker_results(repair_results.servermap, r)

View File

@ -1,5 +1,6 @@
from zope.interface import implements from zope.interface import implements
from twisted.internet import defer
from allmydata.interfaces import IRepairResults, ICheckResults from allmydata.interfaces import IRepairResults, ICheckResults
class RepairResults: class RepairResults:
@ -7,7 +8,10 @@ class RepairResults:
def __init__(self, smap): def __init__(self, smap):
self.servermap = smap self.servermap = smap
def set_successful(self, successful):
self.successful = successful
def get_successful(self):
return self.successful
def to_string(self): def to_string(self):
return "" return ""
@ -52,6 +56,13 @@ class Repairer:
smap = self.check_results.get_servermap() smap = self.check_results.get_servermap()
best_version = smap.best_recoverable_version()
if not best_version:
# the file is damaged beyond repair
rr = RepairResults(smap)
rr.set_successful(False)
return defer.succeed(rr)
if smap.unrecoverable_newer_versions(): if smap.unrecoverable_newer_versions():
if not force: if not force:
raise MustForceRepairError("There were unrecoverable newer " raise MustForceRepairError("There were unrecoverable newer "
@ -92,11 +103,12 @@ class Repairer:
if not self.node.get_writekey(): if not self.node.get_writekey():
raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.") raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
best_version = smap.best_recoverable_version()
d = self.node.download_version(smap, best_version, fetch_privkey=True) d = self.node.download_version(smap, best_version, fetch_privkey=True)
d.addCallback(self.node.upload, smap) d.addCallback(self.node.upload, smap)
d.addCallback(self.get_results, smap) d.addCallback(self.get_results, smap)
return d return d
def get_results(self, res, smap): def get_results(self, res, smap):
return RepairResults(smap) rr = RepairResults(smap)
rr.set_successful(True)
return rr

View File

@ -1296,6 +1296,7 @@ class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
d.addCallback(lambda check_results: self._fn.repair(check_results)) d.addCallback(lambda check_results: self._fn.repair(check_results))
def _check_results(rres): def _check_results(rres):
self.failUnless(IRepairResults.providedBy(rres)) self.failUnless(IRepairResults.providedBy(rres))
self.failUnless(rres.get_successful())
# TODO: examine results # TODO: examine results
self.copy_shares() self.copy_shares()
@ -1338,6 +1339,36 @@ class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
current_shares = self.old_shares[-1] current_shares = self.old_shares[-1]
self.failUnlessEqual(old_shares, current_shares) self.failUnlessEqual(old_shares, current_shares)
def test_unrepairable_0shares(self):
d = self.publish_one()
def _delete_all_shares(ign):
shares = self._storage._peers
for peerid in shares:
shares[peerid] = {}
d.addCallback(_delete_all_shares)
d.addCallback(lambda ign: self._fn.check(Monitor()))
d.addCallback(lambda check_results: self._fn.repair(check_results))
def _check(crr):
self.failUnlessEqual(crr.get_successful(), False)
d.addCallback(_check)
return d
def test_unrepairable_1share(self):
d = self.publish_one()
def _delete_all_shares(ign):
shares = self._storage._peers
for peerid in shares:
for shnum in list(shares[peerid]):
if shnum > 0:
del shares[peerid][shnum]
d.addCallback(_delete_all_shares)
d.addCallback(lambda ign: self._fn.check(Monitor()))
d.addCallback(lambda check_results: self._fn.repair(check_results))
def _check(crr):
self.failUnlessEqual(crr.get_successful(), False)
d.addCallback(_check)
return d
def test_merge(self): def test_merge(self):
self.old_shares = [] self.old_shares = []
d = self.publish_multiple() d = self.publish_multiple()
@ -1361,6 +1392,7 @@ class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
self._fn.repair(check_results, force=True)) self._fn.repair(check_results, force=True))
# this should give us 10 shares of the highest roothash # this should give us 10 shares of the highest roothash
def _check_repair_results(rres): def _check_repair_results(rres):
self.failUnless(rres.get_successful())
pass # TODO pass # TODO
d.addCallback(_check_repair_results) d.addCallback(_check_repair_results)
d.addCallback(lambda res: self._fn.get_servermap(MODE_CHECK)) d.addCallback(lambda res: self._fn.get_servermap(MODE_CHECK))
@ -1395,6 +1427,7 @@ class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin):
d.addCallback(lambda check_results: self._fn.repair(check_results)) d.addCallback(lambda check_results: self._fn.repair(check_results))
# this should give us 10 shares of v3 # this should give us 10 shares of v3
def _check_repair_results(rres): def _check_repair_results(rres):
self.failUnless(rres.get_successful())
pass # TODO pass # TODO
d.addCallback(_check_repair_results) d.addCallback(_check_repair_results)
d.addCallback(lambda res: self._fn.get_servermap(MODE_CHECK)) d.addCallback(lambda res: self._fn.get_servermap(MODE_CHECK))