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