Ticket #1689: fix-1689.diff

File fix-1689.diff, 12.7 KB (added by warner, at 2012-03-31T07:00:15Z)

fix and test for the bug

  • src/allmydata/mutable/checker.py

    diff --git a/src/allmydata/mutable/checker.py b/src/allmydata/mutable/checker.py
    index ea288a0..6b629ec 100644
    a b from allmydata.uri import from_string 
    33from allmydata.util import base32, log
    44from allmydata.check_results import CheckAndRepairResults, CheckResults
    55
    6 from allmydata.mutable.common import MODE_CHECK, CorruptShareError
     6from allmydata.mutable.common import MODE_CHECK, MODE_WRITE, CorruptShareError
    77from allmydata.mutable.servermap import ServerMap, ServermapUpdater
    88from allmydata.mutable.retrieve import Retrieve # for verifying
    99
    1010class MutableChecker:
     11    SERVERMAP_MODE = MODE_CHECK
    1112
    1213    def __init__(self, node, storage_broker, history, monitor):
    1314        self._node = node
    class MutableChecker: 
    2627        # of finding all of the shares, and getting a good idea of
    2728        # recoverability, etc, without verifying.
    2829        u = ServermapUpdater(self._node, self._storage_broker, self._monitor,
    29                              servermap, MODE_CHECK, add_lease=add_lease)
     30                             servermap, self.SERVERMAP_MODE,
     31                             add_lease=add_lease)
    3032        if self._history:
    3133            self._history.notify_mapupdate(u.get_status())
    3234        d = u.update()
    class MutableChecker: 
    241243
    242244
    243245class MutableCheckAndRepairer(MutableChecker):
     246    SERVERMAP_MODE = MODE_WRITE # needed to get the privkey
     247
    244248    def __init__(self, node, storage_broker, history, monitor):
    245249        MutableChecker.__init__(self, node, storage_broker, history, monitor)
    246250        self.cr_results = CheckAndRepairResults(self._storage_index)
    class MutableCheckAndRepairer(MutableChecker): 
    264268            self.cr_results.repair_attempted = False
    265269            return
    266270        self.cr_results.repair_attempted = True
    267         d = self._node.repair(self.results)
     271        d = self._node.repair(self.results, monitor=self._monitor)
    268272        def _repair_finished(repair_results):
    269273            self.cr_results.repair_successful = repair_results.get_successful()
    270274            r = CheckResults(from_string(self._node.get_uri()), self._storage_index)
  • src/allmydata/mutable/common.py

    diff --git a/src/allmydata/mutable/common.py b/src/allmydata/mutable/common.py
    index 9ce11c5..9ce8e37 100644
    a b MODE_ANYTHING = "MODE_ANYTHING" # one recoverable version 
    66MODE_WRITE = "MODE_WRITE" # replace all shares, probably.. not for initial
    77                          # creation
    88MODE_READ = "MODE_READ"
     9MODE_REPAIR = "MODE_REPAIR" # query all peers, get the privkey
    910
    1011class NotWriteableError(Exception):
    1112    pass
  • src/allmydata/mutable/filenode.py

    diff --git a/src/allmydata/mutable/filenode.py b/src/allmydata/mutable/filenode.py
    index 74ed7f0..61ccd39 100644
    a b class MutableFileNode: 
    308308    #################################
    309309    # IRepairable
    310310
    311     def repair(self, check_results, force=False):
     311    def repair(self, check_results, force=False, monitor=None):
    312312        assert ICheckResults(check_results)
    313         r = Repairer(self, check_results)
     313        r = Repairer(self, check_results, self._storage_broker,
     314                     self._history, monitor)
    314315        d = r.start(force)
    315316        return d
    316317
  • src/allmydata/mutable/publish.py

    diff --git a/src/allmydata/mutable/publish.py b/src/allmydata/mutable/publish.py
    index 4f61ad1..c611a6f 100644
    a b from allmydata.storage.server import si_b2a 
    1515from pycryptopp.cipher.aes import AES
    1616from foolscap.api import eventually, fireEventually
    1717
    18 from allmydata.mutable.common import MODE_WRITE, MODE_CHECK, \
     18from allmydata.mutable.common import MODE_WRITE, MODE_CHECK, MODE_REPAIR, \
    1919     UncoordinatedWriteError, NotEnoughServersError
    2020from allmydata.mutable.servermap import ServerMap
    2121from allmydata.mutable.layout import get_version_from_checkstring,\
    class Publish: 
    187187        # servermap was updated in MODE_WRITE, so we can depend upon the
    188188        # serverlist computed by that process instead of computing our own.
    189189        assert self._servermap
    190         assert self._servermap.get_last_update()[0] in (MODE_WRITE, MODE_CHECK)
     190        assert self._servermap.get_last_update()[0] in (MODE_WRITE, MODE_CHECK, MODE_REPAIR)
    191191        # we will push a version that is one larger than anything present
    192192        # in the grid, according to the servermap.
    193193        self._new_seqnum = self._servermap.highest_seqnum() + 1
    class Publish: 
    373373        # servermap was updated in MODE_WRITE, so we can depend upon the
    374374        # serverlist computed by that process instead of computing our own.
    375375        if self._servermap:
    376             assert self._servermap.get_last_update()[0] in (MODE_WRITE, MODE_CHECK)
     376            assert self._servermap.get_last_update()[0] in (MODE_WRITE, MODE_CHECK, MODE_REPAIR)
    377377            # we will push a version that is one larger than anything present
    378378            # in the grid, according to the servermap.
    379379            self._new_seqnum = self._servermap.highest_seqnum() + 1
  • src/allmydata/mutable/repairer.py

    diff --git a/src/allmydata/mutable/repairer.py b/src/allmydata/mutable/repairer.py
    index d0bfeff..94641e4 100644
    a b from zope.interface import implements 
    33from twisted.internet import defer
    44from allmydata.interfaces import IRepairResults, ICheckResults
    55from allmydata.mutable.publish import MutableData
     6from allmydata.mutable.common import MODE_REPAIR
     7from allmydata.mutable.servermap import ServerMap, ServermapUpdater
    68
    79class RepairResults:
    810    implements(IRepairResults)
    class MustForceRepairError(Exception): 
    2325    pass
    2426
    2527class Repairer:
    26     def __init__(self, node, check_results):
     28    def __init__(self, node, check_results, storage_broker, history, monitor):
    2729        self.node = node
    2830        self.check_results = ICheckResults(check_results)
    2931        assert check_results.storage_index == self.node.get_storage_index()
     32        self._storage_broker = storage_broker
     33        self._history = history
     34        self._monitor = monitor
    3035
    3136    def start(self, force=False):
    3237        # download, then re-publish. If a server had a bad share, try to
    class Repairer: 
    5560        #  old shares: replace old shares with the latest version
    5661        #  bogus shares (bad sigs): replace the bad one with a good one
    5762
    58         smap = self.check_results.get_servermap()
     63        # first, update the servermap in MODE_REPAIR, which files all shares
     64        # and makes sure we get the privkey.
     65        u = ServermapUpdater(self.node, self._storage_broker, self._monitor,
     66                             ServerMap(), MODE_REPAIR)
     67        if self._history:
     68            self._history.notify_mapupdate(u.get_status())
     69        d = u.update()
     70        d.addCallback(self._got_full_servermap, force)
     71        return d
    5972
     73    def _got_full_servermap(self, smap, force):
    6074        best_version = smap.best_recoverable_version()
    6175        if not best_version:
    6276            # the file is damaged beyond repair
  • src/allmydata/mutable/servermap.py

    diff --git a/src/allmydata/mutable/servermap.py b/src/allmydata/mutable/servermap.py
    index df75518..e908e42 100644
    a b from allmydata.storage.server import si_b2a 
    1212from allmydata.interfaces import IServermapUpdaterStatus
    1313from pycryptopp.publickey import rsa
    1414
    15 from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \
    16      CorruptShareError
     15from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, \
     16     MODE_READ, MODE_REPAIR, CorruptShareError
    1717from allmydata.mutable.layout import SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy
    1818
    1919class UpdateStatus:
    class ServermapUpdater: 
    426426            self._read_size = 1000
    427427        self._need_privkey = False
    428428
    429         if mode == MODE_WRITE and not self._node.get_privkey():
     429        if mode in (MODE_WRITE, MODE_REPAIR) and not self._node.get_privkey():
    430430            self._need_privkey = True
    431431        # check+repair: repair requires the privkey, so if we didn't happen
    432432        # to ask for it during the check, we'll have problems doing the
    class ServermapUpdater: 
    497497        # might not wait for all of their answers to come back)
    498498        self.num_servers_to_query = k + self.EPSILON
    499499
    500         if self.mode == MODE_CHECK:
     500        if self.mode in (MODE_CHECK, MODE_REPAIR):
    501501            # We want to query all of the servers.
    502502            initial_servers_to_query = list(full_serverlist)
    503503            must_query = set(initial_servers_to_query)
    class ServermapUpdater: 
    10631063                         parent=lp)
    10641064                return self._done()
    10651065
    1066         if self.mode == MODE_CHECK:
     1066        if self.mode == (MODE_CHECK, MODE_REPAIR):
    10671067            # we used self._must_query, and we know there aren't any
    10681068            # responses still waiting, so that means we must be done
    10691069            self.log("done", parent=lp)
  • src/allmydata/test/test_mutable.py

    diff --git a/src/allmydata/test/test_mutable.py b/src/allmydata/test/test_mutable.py
    index c878117..a13a00e 100644
    a b from allmydata.test.test_download import PausingConsumer, \ 
    3939     PausingAndStoppingConsumer, StoppingConsumer, \
    4040     ImmediatelyStoppingConsumer
    4141
     42def eventuaaaaaly(res=None):
     43    d = fireEventually(res)
     44    d.addCallback(fireEventually)
     45    d.addCallback(fireEventually)
     46    return d
     47
    4248
    4349# this "FakeStorage" exists to put the share data in RAM and avoid using real
    4450# network connections, both to speed up the tests and to reduce the amount of
    class FakeStorage: 
    6975    def read(self, peerid, storage_index):
    7076        shares = self._peers.get(peerid, {})
    7177        if self._sequence is None:
    72             return defer.succeed(shares)
    73         d = defer.Deferred()
     78            return eventuaaaaaly(shares)
     79        d = eventuaaaaaly()
    7480        if not self._pending:
    7581            self._pending_timer = reactor.callLater(1.0, self._fire_readers)
    7682        if peerid not in self._pending:
    def make_storagebroker(s=None, num_peers=10): 
    233239        storage_broker.test_add_rref(peerid, fss, ann)
    234240    return storage_broker
    235241
    236 def make_nodemaker(s=None, num_peers=10):
     242def make_nodemaker(s=None, num_peers=10, keysize=TEST_RSA_KEY_SIZE):
    237243    storage_broker = make_storagebroker(s, num_peers)
    238244    sh = client.SecretHolder("lease secret", "convergence secret")
    239245    keygen = client.KeyGenerator()
    240     keygen.set_default_keysize(TEST_RSA_KEY_SIZE)
     246    if keysize:
     247        keygen.set_default_keysize(keysize)
    241248    nodemaker = NodeMaker(storage_broker, sh, None,
    242249                          None, None,
    243250                          {"k": 3, "n": 10}, SDMF_VERSION, keygen)
    class PublishMixin: 
    957964        d.addCallback(_created)
    958965        return d
    959966
     967    def publish_empty_sdmf(self):
     968        self.CONTENTS = ""
     969        self.uploadable = MutableData(self.CONTENTS)
     970        self._storage = FakeStorage()
     971        self._nodemaker = make_nodemaker(self._storage, keysize=None)
     972        self._storage_broker = self._nodemaker.storage_broker
     973        d = self._nodemaker.create_mutable_file(self.uploadable,
     974                                                version=SDMF_VERSION)
     975        def _created(node):
     976            self._fn = node
     977            self._fn2 = self._nodemaker.create_from_cap(node.get_uri())
     978        d.addCallback(_created)
     979        return d
     980
    960981
    961982    def publish_multiple(self, version=0):
    962983        self.CONTENTS = ["Contents 0",
    class Repair(unittest.TestCase, PublishMixin, ShouldFailMixin): 
    21572178        d.addCallback(_check_results)
    21582179        return d
    21592180
     2181    def test_repair_empty(self):
     2182        # bug 1689: delete one share of an empty mutable file, then repair.
     2183        # In the buggy version, the check that preceeds the retrieve+publish
     2184        # cycle uses MODE_READ, instead of MODE_REPAIR, and fails to get the
     2185        # privkey that repair needs.
     2186        d = self.publish_empty_sdmf()
     2187        def _delete_one_share(ign):
     2188            shares = self._storage._peers
     2189            for peerid in shares:
     2190                for shnum in list(shares[peerid]):
     2191                    if shnum == 0:
     2192                        del shares[peerid][shnum]
     2193        d.addCallback(_delete_one_share)
     2194        d.addCallback(lambda ign: self._fn2.check(Monitor()))
     2195        d.addCallback(lambda check_results: self._fn2.repair(check_results))
     2196        def _check(crr):
     2197            self.failUnlessEqual(crr.get_successful(), True)
     2198        d.addCallback(_check)
     2199        return d
     2200
    21602201class DevNullDictionary(dict):
    21612202    def __setitem__(self, key, value):
    21622203        return