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