]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/mutable/repairer.py
Mutable repair: use new MODE_REPAIR to query all servers *and* get privkey
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / mutable / repairer.py
1
2 from zope.interface import implements
3 from twisted.internet import defer
4 from allmydata.interfaces import IRepairResults, ICheckResults
5 from allmydata.mutable.publish import MutableData
6 from allmydata.mutable.common import MODE_REPAIR
7 from allmydata.mutable.servermap import ServerMap, ServermapUpdater
8
9 class RepairResults:
10     implements(IRepairResults)
11
12     def __init__(self, smap):
13         self.servermap = smap
14     def set_successful(self, successful):
15         self.successful = successful
16     def get_successful(self):
17         return self.successful
18     def to_string(self):
19         return ""
20
21 class RepairRequiresWritecapError(Exception):
22     """Repair currently requires a writecap."""
23
24 class MustForceRepairError(Exception):
25     pass
26
27 class Repairer:
28     def __init__(self, node, check_results, storage_broker, history, monitor):
29         self.node = node
30         self.check_results = ICheckResults(check_results)
31         assert check_results.storage_index == self.node.get_storage_index()
32         self._storage_broker = storage_broker
33         self._history = history
34         self._monitor = monitor
35
36     def start(self, force=False):
37         # download, then re-publish. If a server had a bad share, try to
38         # replace it with a good one of the same shnum.
39
40         # The normal repair operation should not be used to replace
41         # application-specific merging of alternate versions: i.e if there
42         # are multiple highest seqnums with different roothashes. In this
43         # case, the application must use node.upload() (referencing the
44         # servermap that indicates the multiple-heads condition), or
45         # node.overwrite(). The repair() operation will refuse to run in
46         # these conditions unless a force=True argument is provided. If
47         # force=True is used, then the highest root hash will be reinforced.
48
49         # Likewise, the presence of an unrecoverable latest version is an
50         # unusual event, and should ideally be handled by retrying a couple
51         # times (spaced out over hours or days) and hoping that new shares
52         # will become available. If repair(force=True) is called, data will
53         # be lost: a new seqnum will be generated with the same contents as
54         # the most recent recoverable version, skipping over the lost
55         # version. repair(force=False) will refuse to run in a situation like
56         # this.
57
58         # Repair is designed to fix the following injuries:
59         #  missing shares: add new ones to get at least N distinct ones
60         #  old shares: replace old shares with the latest version
61         #  bogus shares (bad sigs): replace the bad one with a good one
62
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
72
73     def _got_full_servermap(self, smap, force):
74         best_version = smap.best_recoverable_version()
75         if not best_version:
76             # the file is damaged beyond repair
77             rr = RepairResults(smap)
78             rr.set_successful(False)
79             return defer.succeed(rr)
80
81         if smap.unrecoverable_newer_versions():
82             if not force:
83                 raise MustForceRepairError("There were unrecoverable newer "
84                                            "versions, so force=True must be "
85                                            "passed to the repair() operation")
86             # continuing on means that node.upload() will pick a seqnum that
87             # is higher than everything visible in the servermap, effectively
88             # discarding the unrecoverable versions.
89         if smap.needs_merge():
90             if not force:
91                 raise MustForceRepairError("There were multiple recoverable "
92                                            "versions with identical seqnums, "
93                                            "so force=True must be passed to "
94                                            "the repair() operation")
95             # continuing on means that smap.best_recoverable_version() will
96             # pick the one with the highest roothash, and then node.upload()
97             # will replace all shares with its contents
98
99         # missing shares are handled during upload, which tries to find a
100         # home for every share
101
102         # old shares are handled during upload, which will replace any share
103         # that was present in the servermap
104
105         # bogus shares need to be managed here. We might notice a bogus share
106         # during mapupdate (whether done for a filecheck or just before a
107         # download) by virtue of it having an invalid signature. We might
108         # also notice a bad hash in the share during verify or download. In
109         # either case, the problem will be noted in the servermap, and the
110         # bad share (along with its checkstring) will be recorded in
111         # servermap.bad_shares . Publish knows that it should try and replace
112         # these.
113
114         # I chose to use the retrieve phase to ensure that the privkey is
115         # available, to avoid the extra roundtrip that would occur if we,
116         # say, added an smap.get_privkey() method.
117
118         if not self.node.get_writekey():
119             raise RepairRequiresWritecapError("Sorry, repair currently requires a writecap, to set the write-enabler properly.")
120
121         d = self.node.download_version(smap, best_version, fetch_privkey=True)
122         d.addCallback(lambda data:
123             MutableData(data))
124         d.addCallback(self.node.upload, smap)
125         d.addCallback(self.get_results, smap)
126         return d
127
128     def get_results(self, res, smap):
129         rr = RepairResults(smap)
130         rr.set_successful(True)
131         return rr