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