1 | Sun May 30 18:43:46 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
2 | * Code cleanup |
---|
3 | |
---|
4 | - Change 'readv' to 'readvs' in remote_slot_readv in the storage |
---|
5 | server, to more adaquately convey what the argument is. |
---|
6 | |
---|
7 | Fri Jun 4 12:48:04 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
8 | * Add a notion of the mutable file version number to interfaces.py |
---|
9 | |
---|
10 | Fri Jun 4 12:52:17 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
11 | * Add a salt hasher for MDMF uploads |
---|
12 | |
---|
13 | Fri Jun 4 12:55:27 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
14 | * Add MDMF and SDMF version numbers to interfaces.py |
---|
15 | |
---|
16 | Fri Jun 11 12:17:29 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
17 | * Alter the mutable file servermap to read MDMF files |
---|
18 | |
---|
19 | Fri Jun 11 12:21:50 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
20 | * Add tests for new MDMF proxies |
---|
21 | |
---|
22 | Mon Jun 14 14:34:59 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
23 | * Alter MDMF proxy tests to reflect the new form of caching |
---|
24 | |
---|
25 | Mon Jun 14 14:37:21 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
26 | * Add tests and support functions for servermap tests |
---|
27 | |
---|
28 | Tue Jun 22 17:13:32 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
29 | * Make a segmented downloader |
---|
30 | |
---|
31 | Rework the current mutable file Retrieve class to download segmented |
---|
32 | files. The rewrite preserves the semantics and basic conceptual state |
---|
33 | machine of the old Retrieve class, but adapts them to work with |
---|
34 | files with more than one segment, which involves a fairly substantial |
---|
35 | rewrite. |
---|
36 | |
---|
37 | I've also adapted some existing SDMF tests to work with the new |
---|
38 | downloader, as necessary. |
---|
39 | |
---|
40 | TODO: |
---|
41 | - Write tests for MDMF functionality. |
---|
42 | - Finish writing and testing salt functionality |
---|
43 | |
---|
44 | Tue Jun 22 17:17:08 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
45 | * Tell NodeMaker and MutableFileNode about the distinction between SDMF and MDMF |
---|
46 | |
---|
47 | Tue Jun 22 17:17:32 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
48 | * Assorted servermap fixes |
---|
49 | |
---|
50 | - Check for failure when setting the private key |
---|
51 | - Check for failure when setting other things |
---|
52 | - Check for doneness in a way that is resilient to hung servers |
---|
53 | - Remove dead code |
---|
54 | - Reorganize error and success handling methods, and make sure they get |
---|
55 | used. |
---|
56 | |
---|
57 | |
---|
58 | Tue Jun 22 17:18:33 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
59 | * A first stab at a segmented uploader |
---|
60 | |
---|
61 | This uploader will upload, segment-by-segment, MDMF files. It will only |
---|
62 | do this if it thinks that the filenode that it is uploading represents |
---|
63 | an MDMF file; otherwise, it uploads the file as SDMF. |
---|
64 | |
---|
65 | My TODO list so far: |
---|
66 | - More robust peer selection; we'll want to use something like |
---|
67 | servers of happiness to figure out reliability and unreliability. |
---|
68 | - Clean up. |
---|
69 | |
---|
70 | Tue Jun 22 17:19:12 PDT 2010 Kevan Carstensen <kevan@isnotajoke.com> |
---|
71 | * Add objects for MDMF shares in support of a new segmented uploader |
---|
72 | |
---|
73 | This patch adds the following: |
---|
74 | - MDMFSlotWriteProxy, which can write MDMF shares to the storage |
---|
75 | server in the new format. |
---|
76 | - MDMFSlotReadProxy, which can read both SDMF and MDMF shares from |
---|
77 | the storage server. |
---|
78 | |
---|
79 | This patch also includes tests for these new object. |
---|
80 | |
---|
81 | New patches: |
---|
82 | |
---|
83 | [Code cleanup |
---|
84 | Kevan Carstensen <kevan@isnotajoke.com>**20100531014346 |
---|
85 | Ignore-this: 697378037e83290267f108a4a88b8776 |
---|
86 | |
---|
87 | - Change 'readv' to 'readvs' in remote_slot_readv in the storage |
---|
88 | server, to more adaquately convey what the argument is. |
---|
89 | ] { |
---|
90 | hunk ./src/allmydata/storage/server.py 569 |
---|
91 | self) |
---|
92 | return share |
---|
93 | |
---|
94 | - def remote_slot_readv(self, storage_index, shares, readv): |
---|
95 | + def remote_slot_readv(self, storage_index, shares, readvs): |
---|
96 | start = time.time() |
---|
97 | self.count("readv") |
---|
98 | si_s = si_b2a(storage_index) |
---|
99 | hunk ./src/allmydata/storage/server.py 590 |
---|
100 | if sharenum in shares or not shares: |
---|
101 | filename = os.path.join(bucketdir, sharenum_s) |
---|
102 | msf = MutableShareFile(filename, self) |
---|
103 | - datavs[sharenum] = msf.readv(readv) |
---|
104 | + datavs[sharenum] = msf.readv(readvs) |
---|
105 | log.msg("returning shares %s" % (datavs.keys(),), |
---|
106 | facility="tahoe.storage", level=log.NOISY, parent=lp) |
---|
107 | self.add_latency("readv", time.time() - start) |
---|
108 | } |
---|
109 | [Add a notion of the mutable file version number to interfaces.py |
---|
110 | Kevan Carstensen <kevan@isnotajoke.com>**20100604194804 |
---|
111 | Ignore-this: fd767043437c3cd694807687e6dc677 |
---|
112 | ] hunk ./src/allmydata/interfaces.py 807 |
---|
113 | writer-visible data using this writekey. |
---|
114 | """ |
---|
115 | |
---|
116 | + def set_version(version): |
---|
117 | + """Tahoe-LAFS supports SDMF and MDMF mutable files. By default, |
---|
118 | + we upload in SDMF for reasons of compatibility. If you want to |
---|
119 | + change this, set_version will let you do that. |
---|
120 | + |
---|
121 | + To say that this file should be uploaded in SDMF, pass in a 0. To |
---|
122 | + say that the file should be uploaded as MDMF, pass in a 1. |
---|
123 | + """ |
---|
124 | + |
---|
125 | + def get_version(): |
---|
126 | + """Returns the mutable file protocol version.""" |
---|
127 | + |
---|
128 | class NotEnoughSharesError(Exception): |
---|
129 | """Download was unable to get enough shares""" |
---|
130 | |
---|
131 | [Add a salt hasher for MDMF uploads |
---|
132 | Kevan Carstensen <kevan@isnotajoke.com>**20100604195217 |
---|
133 | Ignore-this: 3072f4c4e75efa078f31aac3a56d36b2 |
---|
134 | ] { |
---|
135 | hunk ./src/allmydata/util/hashutil.py 90 |
---|
136 | MUTABLE_READKEY_TAG = "allmydata_mutable_writekey_to_readkey_v1" |
---|
137 | MUTABLE_DATAKEY_TAG = "allmydata_mutable_readkey_to_datakey_v1" |
---|
138 | MUTABLE_STORAGEINDEX_TAG = "allmydata_mutable_readkey_to_storage_index_v1" |
---|
139 | +MUTABLE_SALT_TAG = "allmydata_mutable_segment_salt_v1" |
---|
140 | |
---|
141 | # dirnodes |
---|
142 | DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1" |
---|
143 | hunk ./src/allmydata/util/hashutil.py 134 |
---|
144 | def plaintext_segment_hasher(): |
---|
145 | return tagged_hasher(PLAINTEXT_SEGMENT_TAG) |
---|
146 | |
---|
147 | +def mutable_salt_hash(data): |
---|
148 | + return tagged_hash(MUTABLE_SALT_TAG, data) |
---|
149 | +def mutable_salt_hasher(): |
---|
150 | + return tagged_hasher(MUTABLE_SALT_TAG) |
---|
151 | + |
---|
152 | KEYLEN = 16 |
---|
153 | IVLEN = 16 |
---|
154 | |
---|
155 | } |
---|
156 | [Add MDMF and SDMF version numbers to interfaces.py |
---|
157 | Kevan Carstensen <kevan@isnotajoke.com>**20100604195527 |
---|
158 | Ignore-this: 5736d229076ea432b9cf40fcee9b4749 |
---|
159 | ] hunk ./src/allmydata/interfaces.py 8 |
---|
160 | |
---|
161 | HASH_SIZE=32 |
---|
162 | |
---|
163 | +SDMF_VERSION=0 |
---|
164 | +MDMF_VERSION=1 |
---|
165 | + |
---|
166 | Hash = StringConstraint(maxLength=HASH_SIZE, |
---|
167 | minLength=HASH_SIZE)# binary format 32-byte SHA256 hash |
---|
168 | Nodeid = StringConstraint(maxLength=20, |
---|
169 | [Alter the mutable file servermap to read MDMF files |
---|
170 | Kevan Carstensen <kevan@isnotajoke.com>**20100611191729 |
---|
171 | Ignore-this: f05748597749f07b16cdbb711fae92e5 |
---|
172 | ] { |
---|
173 | hunk ./src/allmydata/mutable/servermap.py 7 |
---|
174 | from itertools import count |
---|
175 | from twisted.internet import defer |
---|
176 | from twisted.python import failure |
---|
177 | -from foolscap.api import DeadReferenceError, RemoteException, eventually |
---|
178 | +from foolscap.api import DeadReferenceError, RemoteException, eventually, \ |
---|
179 | + fireEventually |
---|
180 | from allmydata.util import base32, hashutil, idlib, log |
---|
181 | from allmydata.storage.server import si_b2a |
---|
182 | from allmydata.interfaces import IServermapUpdaterStatus |
---|
183 | hunk ./src/allmydata/mutable/servermap.py 17 |
---|
184 | from allmydata.mutable.common import MODE_CHECK, MODE_ANYTHING, MODE_WRITE, MODE_READ, \ |
---|
185 | DictOfSets, CorruptShareError, NeedMoreDataError |
---|
186 | from allmydata.mutable.layout import unpack_prefix_and_signature, unpack_header, unpack_share, \ |
---|
187 | - SIGNED_PREFIX_LENGTH |
---|
188 | + SIGNED_PREFIX_LENGTH, MDMFSlotReadProxy |
---|
189 | |
---|
190 | class UpdateStatus: |
---|
191 | implements(IServermapUpdaterStatus) |
---|
192 | hunk ./src/allmydata/mutable/servermap.py 254 |
---|
193 | """Return a set of versionids, one for each version that is currently |
---|
194 | recoverable.""" |
---|
195 | versionmap = self.make_versionmap() |
---|
196 | - |
---|
197 | recoverable_versions = set() |
---|
198 | for (verinfo, shares) in versionmap.items(): |
---|
199 | (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
200 | hunk ./src/allmydata/mutable/servermap.py 366 |
---|
201 | self._servers_responded = set() |
---|
202 | |
---|
203 | # how much data should we read? |
---|
204 | + # SDMF: |
---|
205 | # * if we only need the checkstring, then [0:75] |
---|
206 | # * if we need to validate the checkstring sig, then [543ish:799ish] |
---|
207 | # * if we need the verification key, then [107:436ish] |
---|
208 | hunk ./src/allmydata/mutable/servermap.py 374 |
---|
209 | # * if we need the encrypted private key, we want [-1216ish:] |
---|
210 | # * but we can't read from negative offsets |
---|
211 | # * the offset table tells us the 'ish', also the positive offset |
---|
212 | - # A future version of the SMDF slot format should consider using |
---|
213 | - # fixed-size slots so we can retrieve less data. For now, we'll just |
---|
214 | - # read 2000 bytes, which also happens to read enough actual data to |
---|
215 | - # pre-fetch a 9-entry dirnode. |
---|
216 | + # MDMF: |
---|
217 | + # * Checkstring? [0:72] |
---|
218 | + # * If we want to validate the checkstring, then [0:72], [143:?] -- |
---|
219 | + # the offset table will tell us for sure. |
---|
220 | + # * If we need the verification key, we have to consult the offset |
---|
221 | + # table as well. |
---|
222 | + # At this point, we don't know which we are. Our filenode can |
---|
223 | + # tell us, but it might be lying -- in some cases, we're |
---|
224 | + # responsible for telling it which kind of file it is. |
---|
225 | self._read_size = 4000 |
---|
226 | if mode == MODE_CHECK: |
---|
227 | # we use unpack_prefix_and_signature, so we need 1k |
---|
228 | hunk ./src/allmydata/mutable/servermap.py 432 |
---|
229 | self._queries_completed = 0 |
---|
230 | |
---|
231 | sb = self._storage_broker |
---|
232 | + # All of the peers, permuted by the storage index, as usual. |
---|
233 | full_peerlist = sb.get_servers_for_index(self._storage_index) |
---|
234 | self.full_peerlist = full_peerlist # for use later, immutable |
---|
235 | self.extra_peers = full_peerlist[:] # peers are removed as we use them |
---|
236 | hunk ./src/allmydata/mutable/servermap.py 439 |
---|
237 | self._good_peers = set() # peers who had some shares |
---|
238 | self._empty_peers = set() # peers who don't have any shares |
---|
239 | self._bad_peers = set() # peers to whom our queries failed |
---|
240 | + self._readers = {} # peerid -> dict(sharewriters), filled in |
---|
241 | + # after responses come in. |
---|
242 | |
---|
243 | k = self._node.get_required_shares() |
---|
244 | hunk ./src/allmydata/mutable/servermap.py 443 |
---|
245 | + # For what cases can these conditions work? |
---|
246 | if k is None: |
---|
247 | # make a guess |
---|
248 | k = 3 |
---|
249 | hunk ./src/allmydata/mutable/servermap.py 456 |
---|
250 | self.num_peers_to_query = k + self.EPSILON |
---|
251 | |
---|
252 | if self.mode == MODE_CHECK: |
---|
253 | + # We want to query all of the peers. |
---|
254 | initial_peers_to_query = dict(full_peerlist) |
---|
255 | must_query = set(initial_peers_to_query.keys()) |
---|
256 | self.extra_peers = [] |
---|
257 | hunk ./src/allmydata/mutable/servermap.py 464 |
---|
258 | # we're planning to replace all the shares, so we want a good |
---|
259 | # chance of finding them all. We will keep searching until we've |
---|
260 | # seen epsilon that don't have a share. |
---|
261 | + # We don't query all of the peers because that could take a while. |
---|
262 | self.num_peers_to_query = N + self.EPSILON |
---|
263 | initial_peers_to_query, must_query = self._build_initial_querylist() |
---|
264 | self.required_num_empty_peers = self.EPSILON |
---|
265 | hunk ./src/allmydata/mutable/servermap.py 474 |
---|
266 | # might also avoid the round trip required to read the encrypted |
---|
267 | # private key. |
---|
268 | |
---|
269 | - else: |
---|
270 | + else: # MODE_READ, MODE_ANYTHING |
---|
271 | + # 2k peers is good enough. |
---|
272 | initial_peers_to_query, must_query = self._build_initial_querylist() |
---|
273 | |
---|
274 | # this is a set of peers that we are required to get responses from: |
---|
275 | hunk ./src/allmydata/mutable/servermap.py 485 |
---|
276 | # set as we get responses. |
---|
277 | self._must_query = must_query |
---|
278 | |
---|
279 | + # This tells the done check whether requests are still being |
---|
280 | + # processed. We should wait before returning until at least |
---|
281 | + # updated correctly (and dealing with connection errors. |
---|
282 | + self._processing = 0 |
---|
283 | + |
---|
284 | # now initial_peers_to_query contains the peers that we should ask, |
---|
285 | # self.must_query contains the peers that we must have heard from |
---|
286 | # before we can consider ourselves finished, and self.extra_peers |
---|
287 | hunk ./src/allmydata/mutable/servermap.py 495 |
---|
288 | # contains the overflow (peers that we should tap if we don't get |
---|
289 | # enough responses) |
---|
290 | + # I guess that self._must_query is a subset of |
---|
291 | + # initial_peers_to_query? |
---|
292 | + assert set(must_query).issubset(set(initial_peers_to_query)) |
---|
293 | |
---|
294 | self._send_initial_requests(initial_peers_to_query) |
---|
295 | self._status.timings["initial_queries"] = time.time() - self._started |
---|
296 | hunk ./src/allmydata/mutable/servermap.py 554 |
---|
297 | # errors that aren't handled by _query_failed (and errors caused by |
---|
298 | # _query_failed) get logged, but we still want to check for doneness. |
---|
299 | d.addErrback(log.err) |
---|
300 | - d.addBoth(self._check_for_done) |
---|
301 | d.addErrback(self._fatal_error) |
---|
302 | return d |
---|
303 | |
---|
304 | hunk ./src/allmydata/mutable/servermap.py 584 |
---|
305 | self._servermap.reachable_peers.add(peerid) |
---|
306 | self._must_query.discard(peerid) |
---|
307 | self._queries_completed += 1 |
---|
308 | + # self._processing counts the number of queries that have |
---|
309 | + # completed, but are still processing. We wait until all queries |
---|
310 | + # are done processing before returning a result to the client. |
---|
311 | + # TODO: Should we do this? A response to the initial query means |
---|
312 | + # that we may not have to query the server for anything else, |
---|
313 | + # but if we're dealing with an MDMF share, we'll probably have |
---|
314 | + # to ask it for its signature, unless we cache those sometplace, |
---|
315 | + # and even then. |
---|
316 | + self._processing += 1 |
---|
317 | if not self._running: |
---|
318 | self.log("but we're not running, so we'll ignore it", parent=lp, |
---|
319 | level=log.NOISY) |
---|
320 | hunk ./src/allmydata/mutable/servermap.py 605 |
---|
321 | else: |
---|
322 | self._empty_peers.add(peerid) |
---|
323 | |
---|
324 | - last_verinfo = None |
---|
325 | - last_shnum = None |
---|
326 | + ss, storage_index = stuff |
---|
327 | + ds = [] |
---|
328 | + |
---|
329 | + |
---|
330 | + def _tattle(ignored, status): |
---|
331 | + print status |
---|
332 | + print ignored |
---|
333 | + return ignored |
---|
334 | + |
---|
335 | + def _cache(verinfo, shnum, now, data): |
---|
336 | + self._queries_oustand |
---|
337 | + self._node._add_to_cache(verinfo, shnum, 0, data, now) |
---|
338 | + return shnum, verinfo |
---|
339 | + |
---|
340 | + def _corrupt(e, shnum, data): |
---|
341 | + # This gets raised when there was something wrong with |
---|
342 | + # the remote server. Specifically, when there was an |
---|
343 | + # error unpacking the remote data from the server, or |
---|
344 | + # when the signature is invalid. |
---|
345 | + print e |
---|
346 | + f = failure.Failure() |
---|
347 | + self.log(format="bad share: %(f_value)s", f_value=str(f.value), |
---|
348 | + failure=f, parent=lp, level=log.WEIRD, umid="h5llHg") |
---|
349 | + # Notify the server that its share is corrupt. |
---|
350 | + self.notify_server_corruption(peerid, shnum, str(e)) |
---|
351 | + # By flagging this as a bad peer, we won't count any of |
---|
352 | + # the other shares on that peer as valid, though if we |
---|
353 | + # happen to find a valid version string amongst those |
---|
354 | + # shares, we'll keep track of it so that we don't need |
---|
355 | + # to validate the signature on those again. |
---|
356 | + self._bad_peers.add(peerid) |
---|
357 | + self._last_failure = f |
---|
358 | + # 393CHANGE: Use the reader for this. |
---|
359 | + checkstring = data[:SIGNED_PREFIX_LENGTH] |
---|
360 | + self._servermap.mark_bad_share(peerid, shnum, checkstring) |
---|
361 | + self._servermap.problems.append(f) |
---|
362 | + |
---|
363 | for shnum,datav in datavs.items(): |
---|
364 | data = datav[0] |
---|
365 | hunk ./src/allmydata/mutable/servermap.py 644 |
---|
366 | - try: |
---|
367 | - verinfo = self._got_results_one_share(shnum, data, peerid, lp) |
---|
368 | - last_verinfo = verinfo |
---|
369 | - last_shnum = shnum |
---|
370 | - self._node._add_to_cache(verinfo, shnum, 0, data, now) |
---|
371 | - except CorruptShareError, e: |
---|
372 | - # log it and give the other shares a chance to be processed |
---|
373 | - f = failure.Failure() |
---|
374 | - self.log(format="bad share: %(f_value)s", f_value=str(f.value), |
---|
375 | - failure=f, parent=lp, level=log.WEIRD, umid="h5llHg") |
---|
376 | - self.notify_server_corruption(peerid, shnum, str(e)) |
---|
377 | - self._bad_peers.add(peerid) |
---|
378 | - self._last_failure = f |
---|
379 | - checkstring = data[:SIGNED_PREFIX_LENGTH] |
---|
380 | - self._servermap.mark_bad_share(peerid, shnum, checkstring) |
---|
381 | - self._servermap.problems.append(f) |
---|
382 | - pass |
---|
383 | - |
---|
384 | - self._status.timings["cumulative_verify"] += (time.time() - now) |
---|
385 | + reader = MDMFSlotReadProxy(ss, |
---|
386 | + storage_index, |
---|
387 | + shnum, |
---|
388 | + data) |
---|
389 | + self._readers.setdefault(peerid, dict())[shnum] = reader |
---|
390 | + # our goal, with each response, is to validate the version |
---|
391 | + # information and share data as best we can at this point -- |
---|
392 | + # we do this by validating the signature. To do this, we |
---|
393 | + # need to do the following: |
---|
394 | + # - If we don't already have the public key, fetch the |
---|
395 | + # public key. We use this to validate the signature. |
---|
396 | + friendly_peer = idlib.shortnodeid_b2a(peerid) |
---|
397 | + if not self._node.get_pubkey(): |
---|
398 | + # fetch and set the public key. |
---|
399 | + d = reader.get_verification_key() |
---|
400 | + d.addCallback(self._try_to_set_pubkey) |
---|
401 | + else: |
---|
402 | + # we already have the public key. |
---|
403 | + d = defer.succeed(None) |
---|
404 | + # Neither of these two branches return anything of |
---|
405 | + # consequence, so the first entry in our deferredlist will |
---|
406 | + # be None. |
---|
407 | |
---|
408 | hunk ./src/allmydata/mutable/servermap.py 667 |
---|
409 | - if self._need_privkey and last_verinfo: |
---|
410 | - # send them a request for the privkey. We send one request per |
---|
411 | - # server. |
---|
412 | - lp2 = self.log("sending privkey request", |
---|
413 | - parent=lp, level=log.NOISY) |
---|
414 | - (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
415 | - offsets_tuple) = last_verinfo |
---|
416 | - o = dict(offsets_tuple) |
---|
417 | + # - Next, we need the version information. We almost |
---|
418 | + # certainly got this by reading the first thousand or so |
---|
419 | + # bytes of the share on the storage server, so we |
---|
420 | + # shouldn't need to fetch anything at this step. |
---|
421 | + d2 = reader.get_verinfo() |
---|
422 | + # - Next, we need the signature. For an SDMF share, it is |
---|
423 | + # likely that we fetched this when doing our initial fetch |
---|
424 | + # to get the version information. In MDMF, this lives at |
---|
425 | + # the end of the share, so unless the file is quite small, |
---|
426 | + # we'll need to do a remote fetch to get it. |
---|
427 | + d3 = reader.get_signature() |
---|
428 | + # Once we have all three of these responses, we can move on |
---|
429 | + # to validating the signature |
---|
430 | |
---|
431 | hunk ./src/allmydata/mutable/servermap.py 681 |
---|
432 | - self._queries_outstanding.add(peerid) |
---|
433 | - readv = [ (o['enc_privkey'], (o['EOF'] - o['enc_privkey'])) ] |
---|
434 | - ss = self._servermap.connections[peerid] |
---|
435 | - privkey_started = time.time() |
---|
436 | - d = self._do_read(ss, peerid, self._storage_index, |
---|
437 | - [last_shnum], readv) |
---|
438 | - d.addCallback(self._got_privkey_results, peerid, last_shnum, |
---|
439 | - privkey_started, lp2) |
---|
440 | - d.addErrback(self._privkey_query_failed, peerid, last_shnum, lp2) |
---|
441 | - d.addErrback(log.err) |
---|
442 | - d.addCallback(self._check_for_done) |
---|
443 | - d.addErrback(self._fatal_error) |
---|
444 | + # Does the node already have a privkey? If not, we'll try to |
---|
445 | + # fetch it here. |
---|
446 | + if not self._node.get_privkey(): |
---|
447 | + d4 = reader.get_encprivkey() |
---|
448 | + d4.addCallback(lambda results, shnum=shnum, peerid=peerid: |
---|
449 | + self._try_to_validate_privkey(results, peerid, shnum, lp)) |
---|
450 | + else: |
---|
451 | + d4 = defer.succeed(None) |
---|
452 | |
---|
453 | hunk ./src/allmydata/mutable/servermap.py 690 |
---|
454 | + dl = defer.DeferredList([d, d2, d3, d4]) |
---|
455 | + dl.addCallback(lambda results, shnum=shnum, peerid=peerid: |
---|
456 | + self._got_signature_one_share(results, shnum, peerid, lp)) |
---|
457 | + dl.addErrback(lambda error, shnum=shnum, data=data: |
---|
458 | + _corrupt(error, shnum, data)) |
---|
459 | + ds.append(dl) |
---|
460 | + # dl is a deferred list that will fire when all of the shares |
---|
461 | + # that we found on this peer are done processing. When dl fires, |
---|
462 | + # we know that processing is done, so we can decrement the |
---|
463 | + # semaphore-like thing that we incremented earlier. |
---|
464 | + dl = defer.DeferredList(ds) |
---|
465 | + def _done_processing(ignored): |
---|
466 | + self._processing -= 1 |
---|
467 | + return ignored |
---|
468 | + dl.addCallback(_done_processing) |
---|
469 | + # Are we done? Done means that there are no more queries to |
---|
470 | + # send, that there are no outstanding queries, and that we |
---|
471 | + # haven't received any queries that are still processing. If we |
---|
472 | + # are done, self._check_for_done will cause the done deferred |
---|
473 | + # that we returned to our caller to fire, which tells them that |
---|
474 | + # they have a complete servermap, and that we won't be touching |
---|
475 | + # the servermap anymore. |
---|
476 | + dl.addBoth(self._check_for_done) |
---|
477 | + dl.addErrback(self._fatal_error) |
---|
478 | # all done! |
---|
479 | hunk ./src/allmydata/mutable/servermap.py 715 |
---|
480 | + return dl |
---|
481 | self.log("_got_results done", parent=lp, level=log.NOISY) |
---|
482 | |
---|
483 | hunk ./src/allmydata/mutable/servermap.py 718 |
---|
484 | + def _try_to_set_pubkey(self, pubkey_s): |
---|
485 | + if self._node.get_pubkey(): |
---|
486 | + return # don't go through this again if we don't have to |
---|
487 | + fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s) |
---|
488 | + assert len(fingerprint) == 32 |
---|
489 | + if fingerprint != self._node.get_fingerprint(): |
---|
490 | + raise CorruptShareError(peerid, shnum, |
---|
491 | + "pubkey doesn't match fingerprint") |
---|
492 | + self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s)) |
---|
493 | + assert self._node.get_pubkey() |
---|
494 | + |
---|
495 | + |
---|
496 | def notify_server_corruption(self, peerid, shnum, reason): |
---|
497 | ss = self._servermap.connections[peerid] |
---|
498 | ss.callRemoteOnly("advise_corrupt_share", |
---|
499 | hunk ./src/allmydata/mutable/servermap.py 735 |
---|
500 | "mutable", self._storage_index, shnum, reason) |
---|
501 | |
---|
502 | - def _got_results_one_share(self, shnum, data, peerid, lp): |
---|
503 | + |
---|
504 | + def _got_signature_one_share(self, results, shnum, peerid, lp): |
---|
505 | + # It is our job to give versioninfo to our caller. We need to |
---|
506 | + # raise CorruptShareError if the share is corrupt for any |
---|
507 | + # reason, something that our caller will handle. |
---|
508 | self.log(format="_got_results: got shnum #%(shnum)d from peerid %(peerid)s", |
---|
509 | shnum=shnum, |
---|
510 | peerid=idlib.shortnodeid_b2a(peerid), |
---|
511 | hunk ./src/allmydata/mutable/servermap.py 745 |
---|
512 | level=log.NOISY, |
---|
513 | parent=lp) |
---|
514 | - |
---|
515 | - # this might raise NeedMoreDataError, if the pubkey and signature |
---|
516 | - # live at some weird offset. That shouldn't happen, so I'm going to |
---|
517 | - # treat it as a bad share. |
---|
518 | - (seqnum, root_hash, IV, k, N, segsize, datalength, |
---|
519 | - pubkey_s, signature, prefix) = unpack_prefix_and_signature(data) |
---|
520 | - |
---|
521 | - if not self._node.get_pubkey(): |
---|
522 | - fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s) |
---|
523 | - assert len(fingerprint) == 32 |
---|
524 | - if fingerprint != self._node.get_fingerprint(): |
---|
525 | - raise CorruptShareError(peerid, shnum, |
---|
526 | - "pubkey doesn't match fingerprint") |
---|
527 | - self._node._populate_pubkey(self._deserialize_pubkey(pubkey_s)) |
---|
528 | - |
---|
529 | - if self._need_privkey: |
---|
530 | - self._try_to_extract_privkey(data, peerid, shnum, lp) |
---|
531 | - |
---|
532 | - (ig_version, ig_seqnum, ig_root_hash, ig_IV, ig_k, ig_N, |
---|
533 | - ig_segsize, ig_datalen, offsets) = unpack_header(data) |
---|
534 | + _, verinfo, signature, __ = results |
---|
535 | + (seqnum, |
---|
536 | + root_hash, |
---|
537 | + saltish, |
---|
538 | + segsize, |
---|
539 | + datalen, |
---|
540 | + k, |
---|
541 | + n, |
---|
542 | + prefix, |
---|
543 | + offsets) = verinfo[1] |
---|
544 | offsets_tuple = tuple( [(key,value) for key,value in offsets.items()] ) |
---|
545 | |
---|
546 | hunk ./src/allmydata/mutable/servermap.py 757 |
---|
547 | - verinfo = (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
548 | + # XXX: This should be done for us in the method, so |
---|
549 | + # presumably you can go in there and fix it. |
---|
550 | + verinfo = (seqnum, |
---|
551 | + root_hash, |
---|
552 | + saltish, |
---|
553 | + segsize, |
---|
554 | + datalen, |
---|
555 | + k, |
---|
556 | + n, |
---|
557 | + prefix, |
---|
558 | offsets_tuple) |
---|
559 | hunk ./src/allmydata/mutable/servermap.py 768 |
---|
560 | + # This tuple uniquely identifies a share on the grid; we use it |
---|
561 | + # to keep track of the ones that we've already seen. |
---|
562 | |
---|
563 | if verinfo not in self._valid_versions: |
---|
564 | hunk ./src/allmydata/mutable/servermap.py 772 |
---|
565 | - # it's a new pair. Verify the signature. |
---|
566 | - valid = self._node.get_pubkey().verify(prefix, signature) |
---|
567 | + # This is a new version tuple, and we need to validate it |
---|
568 | + # against the public key before keeping track of it. |
---|
569 | + valid = self._node.get_pubkey().verify(prefix, signature[1]) |
---|
570 | if not valid: |
---|
571 | hunk ./src/allmydata/mutable/servermap.py 776 |
---|
572 | - raise CorruptShareError(peerid, shnum, "signature is invalid") |
---|
573 | + raise CorruptShareError(peerid, shnum, |
---|
574 | + "signature is invalid") |
---|
575 | |
---|
576 | hunk ./src/allmydata/mutable/servermap.py 779 |
---|
577 | - # ok, it's a valid verinfo. Add it to the list of validated |
---|
578 | - # versions. |
---|
579 | - self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d" |
---|
580 | - % (seqnum, base32.b2a(root_hash)[:4], |
---|
581 | - idlib.shortnodeid_b2a(peerid), shnum, |
---|
582 | - k, N, segsize, datalength), |
---|
583 | - parent=lp) |
---|
584 | - self._valid_versions.add(verinfo) |
---|
585 | - # We now know that this is a valid candidate verinfo. |
---|
586 | + # ok, it's a valid verinfo. Add it to the list of validated |
---|
587 | + # versions. |
---|
588 | + self.log(" found valid version %d-%s from %s-sh%d: %d-%d/%d/%d" |
---|
589 | + % (seqnum, base32.b2a(root_hash)[:4], |
---|
590 | + idlib.shortnodeid_b2a(peerid), shnum, |
---|
591 | + k, n, segsize, datalen), |
---|
592 | + parent=lp) |
---|
593 | + self._valid_versions.add(verinfo) |
---|
594 | + # We now know that this is a valid candidate verinfo. Whether or |
---|
595 | + # not this instance of it is valid is a matter for the next |
---|
596 | + # statement; at this point, we just know that if we see this |
---|
597 | + # version info again, that its signature checks out and that |
---|
598 | + # we're okay to skip the signature-checking step. |
---|
599 | |
---|
600 | hunk ./src/allmydata/mutable/servermap.py 793 |
---|
601 | + # (peerid, shnum) are bound in the method invocation. |
---|
602 | if (peerid, shnum) in self._servermap.bad_shares: |
---|
603 | # we've been told that the rest of the data in this share is |
---|
604 | # unusable, so don't add it to the servermap. |
---|
605 | hunk ./src/allmydata/mutable/servermap.py 808 |
---|
606 | self.versionmap.add(verinfo, (shnum, peerid, timestamp)) |
---|
607 | return verinfo |
---|
608 | |
---|
609 | + |
---|
610 | def _deserialize_pubkey(self, pubkey_s): |
---|
611 | verifier = rsa.create_verifying_key_from_string(pubkey_s) |
---|
612 | return verifier |
---|
613 | hunk ./src/allmydata/mutable/servermap.py 813 |
---|
614 | |
---|
615 | - def _try_to_extract_privkey(self, data, peerid, shnum, lp): |
---|
616 | - try: |
---|
617 | - r = unpack_share(data) |
---|
618 | - except NeedMoreDataError, e: |
---|
619 | - # this share won't help us. oh well. |
---|
620 | - offset = e.encprivkey_offset |
---|
621 | - length = e.encprivkey_length |
---|
622 | - self.log("shnum %d on peerid %s: share was too short (%dB) " |
---|
623 | - "to get the encprivkey; [%d:%d] ought to hold it" % |
---|
624 | - (shnum, idlib.shortnodeid_b2a(peerid), len(data), |
---|
625 | - offset, offset+length), |
---|
626 | - parent=lp) |
---|
627 | - # NOTE: if uncoordinated writes are taking place, someone might |
---|
628 | - # change the share (and most probably move the encprivkey) before |
---|
629 | - # we get a chance to do one of these reads and fetch it. This |
---|
630 | - # will cause us to see a NotEnoughSharesError(unable to fetch |
---|
631 | - # privkey) instead of an UncoordinatedWriteError . This is a |
---|
632 | - # nuisance, but it will go away when we move to DSA-based mutable |
---|
633 | - # files (since the privkey will be small enough to fit in the |
---|
634 | - # write cap). |
---|
635 | - |
---|
636 | - return |
---|
637 | - |
---|
638 | - (seqnum, root_hash, IV, k, N, segsize, datalen, |
---|
639 | - pubkey, signature, share_hash_chain, block_hash_tree, |
---|
640 | - share_data, enc_privkey) = r |
---|
641 | - |
---|
642 | - return self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp) |
---|
643 | |
---|
644 | def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp): |
---|
645 | hunk ./src/allmydata/mutable/servermap.py 815 |
---|
646 | - |
---|
647 | + """ |
---|
648 | + Given a writekey from a remote server, I validate it against the |
---|
649 | + writekey stored in my node. If it is valid, then I set the |
---|
650 | + privkey and encprivkey properties of the node. |
---|
651 | + """ |
---|
652 | alleged_privkey_s = self._node._decrypt_privkey(enc_privkey) |
---|
653 | alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s) |
---|
654 | if alleged_writekey != self._node.get_writekey(): |
---|
655 | hunk ./src/allmydata/mutable/servermap.py 925 |
---|
656 | # return self._send_more_queries(outstanding) : send some more queries |
---|
657 | # return self._done() : all done |
---|
658 | # return : keep waiting, no new queries |
---|
659 | - |
---|
660 | lp = self.log(format=("_check_for_done, mode is '%(mode)s', " |
---|
661 | "%(outstanding)d queries outstanding, " |
---|
662 | "%(extra)d extra peers available, " |
---|
663 | hunk ./src/allmydata/mutable/servermap.py 943 |
---|
664 | self.log("but we're not running", parent=lp, level=log.NOISY) |
---|
665 | return |
---|
666 | |
---|
667 | + if self._processing > 0: |
---|
668 | + # wait until more results are done before returning. |
---|
669 | + return |
---|
670 | + |
---|
671 | if self._must_query: |
---|
672 | # we are still waiting for responses from peers that used to have |
---|
673 | # a share, so we must continue to wait. No additional queries are |
---|
674 | hunk ./src/allmydata/mutable/servermap.py 1134 |
---|
675 | self._servermap.last_update_time = self._started |
---|
676 | # the servermap will not be touched after this |
---|
677 | self.log("servermap: %s" % self._servermap.summarize_versions()) |
---|
678 | + |
---|
679 | eventually(self._done_deferred.callback, self._servermap) |
---|
680 | |
---|
681 | def _fatal_error(self, f): |
---|
682 | } |
---|
683 | [Add tests for new MDMF proxies |
---|
684 | Kevan Carstensen <kevan@isnotajoke.com>**20100611192150 |
---|
685 | Ignore-this: 986d2cb867cbd4477b131cd951cd9eac |
---|
686 | ] { |
---|
687 | hunk ./src/allmydata/test/test_storage.py 2 |
---|
688 | |
---|
689 | -import time, os.path, stat, re, simplejson, struct |
---|
690 | +import time, os.path, stat, re, simplejson, struct, shutil |
---|
691 | |
---|
692 | from twisted.trial import unittest |
---|
693 | |
---|
694 | hunk ./src/allmydata/test/test_storage.py 22 |
---|
695 | from allmydata.storage.expirer import LeaseCheckingCrawler |
---|
696 | from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \ |
---|
697 | ReadBucketProxy |
---|
698 | -from allmydata.interfaces import BadWriteEnablerError |
---|
699 | -from allmydata.test.common import LoggingServiceParent |
---|
700 | +from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \ |
---|
701 | + LayoutInvalid |
---|
702 | +from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \ |
---|
703 | + SDMF_VERSION |
---|
704 | +from allmydata.test.common import LoggingServiceParent, ShouldFailMixin |
---|
705 | from allmydata.test.common_web import WebRenderingMixin |
---|
706 | from allmydata.web.storage import StorageStatus, remove_prefix |
---|
707 | |
---|
708 | hunk ./src/allmydata/test/test_storage.py 1286 |
---|
709 | self.failUnless(os.path.exists(prefixdir), prefixdir) |
---|
710 | self.failIf(os.path.exists(bucketdir), bucketdir) |
---|
711 | |
---|
712 | + |
---|
713 | +class MDMFProxies(unittest.TestCase, ShouldFailMixin): |
---|
714 | + def setUp(self): |
---|
715 | + self.sparent = LoggingServiceParent() |
---|
716 | + self._lease_secret = itertools.count() |
---|
717 | + self.ss = self.create("MDMFProxies storage test server") |
---|
718 | + self.rref = RemoteBucket() |
---|
719 | + self.rref.target = self.ss |
---|
720 | + self.secrets = (self.write_enabler("we_secret"), |
---|
721 | + self.renew_secret("renew_secret"), |
---|
722 | + self.cancel_secret("cancel_secret")) |
---|
723 | + self.segment = "aaaaaa" |
---|
724 | + self.block = "aa" |
---|
725 | + self.salt = "a" * 16 |
---|
726 | + self.block_hash = "a" * 32 |
---|
727 | + self.block_hash_tree = [self.block_hash for i in xrange(6)] |
---|
728 | + self.share_hash = self.block_hash |
---|
729 | + self.share_hash_chain = dict([(i, self.share_hash) for i in xrange(6)]) |
---|
730 | + self.signature = "foobarbaz" |
---|
731 | + self.verification_key = "vvvvvv" |
---|
732 | + self.encprivkey = "private" |
---|
733 | + self.root_hash = self.block_hash |
---|
734 | + self.salt_hash = self.root_hash |
---|
735 | + self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree) |
---|
736 | + self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain) |
---|
737 | + |
---|
738 | + |
---|
739 | + def tearDown(self): |
---|
740 | + self.sparent.stopService() |
---|
741 | + shutil.rmtree(self.workdir("MDMFProxies storage test server")) |
---|
742 | + |
---|
743 | + |
---|
744 | + def write_enabler(self, we_tag): |
---|
745 | + return hashutil.tagged_hash("we_blah", we_tag) |
---|
746 | + |
---|
747 | + |
---|
748 | + def renew_secret(self, tag): |
---|
749 | + return hashutil.tagged_hash("renew_blah", str(tag)) |
---|
750 | + |
---|
751 | + |
---|
752 | + def cancel_secret(self, tag): |
---|
753 | + return hashutil.tagged_hash("cancel_blah", str(tag)) |
---|
754 | + |
---|
755 | + |
---|
756 | + def workdir(self, name): |
---|
757 | + basedir = os.path.join("storage", "MutableServer", name) |
---|
758 | + return basedir |
---|
759 | + |
---|
760 | + |
---|
761 | + def create(self, name): |
---|
762 | + workdir = self.workdir(name) |
---|
763 | + ss = StorageServer(workdir, "\x00" * 20) |
---|
764 | + ss.setServiceParent(self.sparent) |
---|
765 | + return ss |
---|
766 | + |
---|
767 | + |
---|
768 | + def build_test_mdmf_share(self, tail_segment=False, empty=False): |
---|
769 | + # Start with the checkstring |
---|
770 | + data = struct.pack(">BQ32s32s", |
---|
771 | + 1, |
---|
772 | + 0, |
---|
773 | + self.root_hash, |
---|
774 | + self.salt_hash) |
---|
775 | + self.checkstring = data |
---|
776 | + # Next, the encoding parameters |
---|
777 | + if tail_segment: |
---|
778 | + data += struct.pack(">BBQQ", |
---|
779 | + 3, |
---|
780 | + 10, |
---|
781 | + 6, |
---|
782 | + 33) |
---|
783 | + elif empty: |
---|
784 | + data += struct.pack(">BBQQ", |
---|
785 | + 3, |
---|
786 | + 10, |
---|
787 | + 0, |
---|
788 | + 0) |
---|
789 | + else: |
---|
790 | + data += struct.pack(">BBQQ", |
---|
791 | + 3, |
---|
792 | + 10, |
---|
793 | + 6, |
---|
794 | + 36) |
---|
795 | + # Now we'll build the offsets. |
---|
796 | + # The header -- everything up to the salts -- is 143 bytes long. |
---|
797 | + # The shares come after the salts. |
---|
798 | + if empty: |
---|
799 | + salts = "" |
---|
800 | + else: |
---|
801 | + salts = self.salt * 6 |
---|
802 | + share_offset = 143 + len(salts) |
---|
803 | + if tail_segment: |
---|
804 | + sharedata = self.block * 6 |
---|
805 | + elif empty: |
---|
806 | + sharedata = "" |
---|
807 | + else: |
---|
808 | + sharedata = self.block * 6 + "a" |
---|
809 | + # The encrypted private key comes after the shares |
---|
810 | + encrypted_private_key_offset = share_offset + len(sharedata) |
---|
811 | + # The blockhashes come after the private key |
---|
812 | + blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey) |
---|
813 | + # The sharehashes come after the blockhashes |
---|
814 | + sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s) |
---|
815 | + # The signature comes after the share hash chain |
---|
816 | + signature_offset = sharehashes_offset + len(self.share_hash_chain_s) |
---|
817 | + # The verification key comes after the signature |
---|
818 | + verification_offset = signature_offset + len(self.signature) |
---|
819 | + # The EOF comes after the verification key |
---|
820 | + eof_offset = verification_offset + len(self.verification_key) |
---|
821 | + data += struct.pack(">LQQQQQQ", |
---|
822 | + share_offset, |
---|
823 | + encrypted_private_key_offset, |
---|
824 | + blockhashes_offset, |
---|
825 | + sharehashes_offset, |
---|
826 | + signature_offset, |
---|
827 | + verification_offset, |
---|
828 | + eof_offset) |
---|
829 | + self.offsets = {} |
---|
830 | + self.offsets['share_data'] = share_offset |
---|
831 | + self.offsets['enc_privkey'] = encrypted_private_key_offset |
---|
832 | + self.offsets['block_hash_tree'] = blockhashes_offset |
---|
833 | + self.offsets['share_hash_chain'] = sharehashes_offset |
---|
834 | + self.offsets['signature'] = signature_offset |
---|
835 | + self.offsets['verification_key'] = verification_offset |
---|
836 | + self.offsets['EOF'] = eof_offset |
---|
837 | + # Next, we'll add in the salts, |
---|
838 | + data += salts |
---|
839 | + # the share data, |
---|
840 | + data += sharedata |
---|
841 | + # the private key, |
---|
842 | + data += self.encprivkey |
---|
843 | + # the block hash tree, |
---|
844 | + data += self.block_hash_tree_s |
---|
845 | + # the share hash chain, |
---|
846 | + data += self.share_hash_chain_s |
---|
847 | + # the signature, |
---|
848 | + data += self.signature |
---|
849 | + # and the verification key |
---|
850 | + data += self.verification_key |
---|
851 | + return data |
---|
852 | + |
---|
853 | + |
---|
854 | + def write_test_share_to_server(self, |
---|
855 | + storage_index, |
---|
856 | + tail_segment=False, |
---|
857 | + empty=False): |
---|
858 | + """ |
---|
859 | + I write some data for the read tests to read to self.ss |
---|
860 | + |
---|
861 | + If tail_segment=True, then I will write a share that has a |
---|
862 | + smaller tail segment than other segments. |
---|
863 | + """ |
---|
864 | + write = self.ss.remote_slot_testv_and_readv_and_writev |
---|
865 | + data = self.build_test_mdmf_share(tail_segment, empty) |
---|
866 | + # Finally, we write the whole thing to the storage server in one |
---|
867 | + # pass. |
---|
868 | + testvs = [(0, 1, "eq", "")] |
---|
869 | + tws = {} |
---|
870 | + tws[0] = (testvs, [(0, data)], None) |
---|
871 | + readv = [(0, 1)] |
---|
872 | + results = write(storage_index, self.secrets, tws, readv) |
---|
873 | + self.failUnless(results[0]) |
---|
874 | + |
---|
875 | + |
---|
876 | + def build_test_sdmf_share(self, empty=False): |
---|
877 | + if empty: |
---|
878 | + sharedata = "" |
---|
879 | + else: |
---|
880 | + sharedata = self.segment * 6 |
---|
881 | + blocksize = len(sharedata) / 3 |
---|
882 | + block = sharedata[:blocksize] |
---|
883 | + prefix = struct.pack(">BQ32s16s BBQQ", |
---|
884 | + 0, # version, |
---|
885 | + 0, |
---|
886 | + self.root_hash, |
---|
887 | + self.salt, |
---|
888 | + 3, |
---|
889 | + 10, |
---|
890 | + len(sharedata), |
---|
891 | + len(sharedata), |
---|
892 | + ) |
---|
893 | + post_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ") |
---|
894 | + signature_offset = post_offset + len(self.verification_key) |
---|
895 | + sharehashes_offset = signature_offset + len(self.signature) |
---|
896 | + blockhashes_offset = sharehashes_offset + len(self.share_hash_chain_s) |
---|
897 | + sharedata_offset = blockhashes_offset + len(self.block_hash_tree_s) |
---|
898 | + encprivkey_offset = sharedata_offset + len(block) |
---|
899 | + eof_offset = encprivkey_offset + len(self.encprivkey) |
---|
900 | + offsets = struct.pack(">LLLLQQ", |
---|
901 | + signature_offset, |
---|
902 | + sharehashes_offset, |
---|
903 | + blockhashes_offset, |
---|
904 | + sharedata_offset, |
---|
905 | + encprivkey_offset, |
---|
906 | + eof_offset) |
---|
907 | + final_share = "".join([prefix, |
---|
908 | + offsets, |
---|
909 | + self.verification_key, |
---|
910 | + self.signature, |
---|
911 | + self.share_hash_chain_s, |
---|
912 | + self.block_hash_tree_s, |
---|
913 | + block, |
---|
914 | + self.encprivkey]) |
---|
915 | + self.offsets = {} |
---|
916 | + self.offsets['signature'] = signature_offset |
---|
917 | + self.offsets['share_hash_chain'] = sharehashes_offset |
---|
918 | + self.offsets['block_hash_tree'] = blockhashes_offset |
---|
919 | + self.offsets['share_data'] = sharedata_offset |
---|
920 | + self.offsets['enc_privkey'] = encprivkey_offset |
---|
921 | + self.offsets['EOF'] = eof_offset |
---|
922 | + return final_share |
---|
923 | + |
---|
924 | + |
---|
925 | + def write_sdmf_share_to_server(self, |
---|
926 | + storage_index, |
---|
927 | + empty=False): |
---|
928 | + # Some tests need SDMF shares to verify that we can still |
---|
929 | + # read them. This method writes one, which resembles but is not |
---|
930 | + assert self.rref |
---|
931 | + write = self.ss.remote_slot_testv_and_readv_and_writev |
---|
932 | + share = self.build_test_sdmf_share(empty) |
---|
933 | + testvs = [(0, 1, "eq", "")] |
---|
934 | + tws = {} |
---|
935 | + tws[0] = (testvs, [(0, share)], None) |
---|
936 | + readv = [] |
---|
937 | + results = write(storage_index, self.secrets, tws, readv) |
---|
938 | + self.failUnless(results[0]) |
---|
939 | + |
---|
940 | + |
---|
941 | + def test_read(self): |
---|
942 | + self.write_test_share_to_server("si1") |
---|
943 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
944 | + # Check that every method equals what we expect it to. |
---|
945 | + d = defer.succeed(None) |
---|
946 | + def _check_block_and_salt((block, salt)): |
---|
947 | + self.failUnlessEqual(block, self.block) |
---|
948 | + self.failUnlessEqual(salt, self.salt) |
---|
949 | + |
---|
950 | + for i in xrange(6): |
---|
951 | + d.addCallback(lambda ignored, i=i: |
---|
952 | + mr.get_block_and_salt(i)) |
---|
953 | + d.addCallback(_check_block_and_salt) |
---|
954 | + |
---|
955 | + d.addCallback(lambda ignored: |
---|
956 | + mr.get_encprivkey()) |
---|
957 | + d.addCallback(lambda encprivkey: |
---|
958 | + self.failUnlessEqual(self.encprivkey, encprivkey)) |
---|
959 | + |
---|
960 | + d.addCallback(lambda ignored: |
---|
961 | + mr.get_blockhashes()) |
---|
962 | + d.addCallback(lambda blockhashes: |
---|
963 | + self.failUnlessEqual(self.block_hash_tree, blockhashes)) |
---|
964 | + |
---|
965 | + d.addCallback(lambda ignored: |
---|
966 | + mr.get_sharehashes()) |
---|
967 | + d.addCallback(lambda sharehashes: |
---|
968 | + self.failUnlessEqual(self.share_hash_chain, sharehashes)) |
---|
969 | + |
---|
970 | + d.addCallback(lambda ignored: |
---|
971 | + mr.get_signature()) |
---|
972 | + d.addCallback(lambda signature: |
---|
973 | + self.failUnlessEqual(signature, self.signature)) |
---|
974 | + |
---|
975 | + d.addCallback(lambda ignored: |
---|
976 | + mr.get_verification_key()) |
---|
977 | + d.addCallback(lambda verification_key: |
---|
978 | + self.failUnlessEqual(verification_key, self.verification_key)) |
---|
979 | + |
---|
980 | + d.addCallback(lambda ignored: |
---|
981 | + mr.get_seqnum()) |
---|
982 | + d.addCallback(lambda seqnum: |
---|
983 | + self.failUnlessEqual(seqnum, 0)) |
---|
984 | + |
---|
985 | + d.addCallback(lambda ignored: |
---|
986 | + mr.get_root_hash()) |
---|
987 | + d.addCallback(lambda root_hash: |
---|
988 | + self.failUnlessEqual(self.root_hash, root_hash)) |
---|
989 | + |
---|
990 | + d.addCallback(lambda ignored: |
---|
991 | + mr.get_salt_hash()) |
---|
992 | + d.addCallback(lambda salt_hash: |
---|
993 | + self.failUnlessEqual(self.salt_hash, salt_hash)) |
---|
994 | + |
---|
995 | + d.addCallback(lambda ignored: |
---|
996 | + mr.get_seqnum()) |
---|
997 | + d.addCallback(lambda seqnum: |
---|
998 | + self.failUnlessEqual(0, seqnum)) |
---|
999 | + |
---|
1000 | + d.addCallback(lambda ignored: |
---|
1001 | + mr.get_encoding_parameters()) |
---|
1002 | + def _check_encoding_parameters((k, n, segsize, datalen)): |
---|
1003 | + self.failUnlessEqual(k, 3) |
---|
1004 | + self.failUnlessEqual(n, 10) |
---|
1005 | + self.failUnlessEqual(segsize, 6) |
---|
1006 | + self.failUnlessEqual(datalen, 36) |
---|
1007 | + d.addCallback(_check_encoding_parameters) |
---|
1008 | + |
---|
1009 | + d.addCallback(lambda ignored: |
---|
1010 | + mr.get_checkstring()) |
---|
1011 | + d.addCallback(lambda checkstring: |
---|
1012 | + self.failUnlessEqual(checkstring, checkstring)) |
---|
1013 | + return d |
---|
1014 | + |
---|
1015 | + |
---|
1016 | + def test_read_with_different_tail_segment_size(self): |
---|
1017 | + self.write_test_share_to_server("si1", tail_segment=True) |
---|
1018 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1019 | + d = mr.get_block_and_salt(5) |
---|
1020 | + def _check_tail_segment(results): |
---|
1021 | + block, salt = results |
---|
1022 | + self.failUnlessEqual(len(block), 1) |
---|
1023 | + self.failUnlessEqual(block, "a") |
---|
1024 | + d.addCallback(_check_tail_segment) |
---|
1025 | + return d |
---|
1026 | + |
---|
1027 | + |
---|
1028 | + def test_get_block_with_invalid_segnum(self): |
---|
1029 | + self.write_test_share_to_server("si1") |
---|
1030 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1031 | + d = defer.succeed(None) |
---|
1032 | + d.addCallback(lambda ignored: |
---|
1033 | + self.shouldFail(LayoutInvalid, "test invalid segnum", |
---|
1034 | + None, |
---|
1035 | + mr.get_block_and_salt, 7)) |
---|
1036 | + return d |
---|
1037 | + |
---|
1038 | + |
---|
1039 | + def test_get_encoding_parameters_first(self): |
---|
1040 | + self.write_test_share_to_server("si1") |
---|
1041 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1042 | + d = mr.get_encoding_parameters() |
---|
1043 | + def _check_encoding_parameters((k, n, segment_size, datalen)): |
---|
1044 | + self.failUnlessEqual(k, 3) |
---|
1045 | + self.failUnlessEqual(n, 10) |
---|
1046 | + self.failUnlessEqual(segment_size, 6) |
---|
1047 | + self.failUnlessEqual(datalen, 36) |
---|
1048 | + d.addCallback(_check_encoding_parameters) |
---|
1049 | + return d |
---|
1050 | + |
---|
1051 | + |
---|
1052 | + def test_get_seqnum_first(self): |
---|
1053 | + self.write_test_share_to_server("si1") |
---|
1054 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1055 | + d = mr.get_seqnum() |
---|
1056 | + d.addCallback(lambda seqnum: |
---|
1057 | + self.failUnlessEqual(seqnum, 0)) |
---|
1058 | + return d |
---|
1059 | + |
---|
1060 | + |
---|
1061 | + def test_get_root_hash_first(self): |
---|
1062 | + self.write_test_share_to_server("si1") |
---|
1063 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1064 | + d = mr.get_root_hash() |
---|
1065 | + d.addCallback(lambda root_hash: |
---|
1066 | + self.failUnlessEqual(root_hash, self.root_hash)) |
---|
1067 | + return d |
---|
1068 | + |
---|
1069 | + |
---|
1070 | + def test_get_salt_hash_first(self): |
---|
1071 | + self.write_test_share_to_server("si1") |
---|
1072 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1073 | + d = mr.get_salt_hash() |
---|
1074 | + d.addCallback(lambda salt_hash: |
---|
1075 | + self.failUnlessEqual(salt_hash, self.salt_hash)) |
---|
1076 | + return d |
---|
1077 | + |
---|
1078 | + |
---|
1079 | + def test_get_checkstring_first(self): |
---|
1080 | + self.write_test_share_to_server("si1") |
---|
1081 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1082 | + d = mr.get_checkstring() |
---|
1083 | + d.addCallback(lambda checkstring: |
---|
1084 | + self.failUnlessEqual(checkstring, self.checkstring)) |
---|
1085 | + return d |
---|
1086 | + |
---|
1087 | + |
---|
1088 | + def test_write_read_vectors(self): |
---|
1089 | + # When writing for us, the storage server will return to us a |
---|
1090 | + # read vector, along with its result. If a write fails because |
---|
1091 | + # the test vectors failed, this read vector can help us to |
---|
1092 | + # diagnose the problem. This test ensures that the read vector |
---|
1093 | + # is working appropriately. |
---|
1094 | + mw = self._make_new_mw("si1", 0) |
---|
1095 | + d = defer.succeed(None) |
---|
1096 | + |
---|
1097 | + # Write one share. This should return a checkstring of nothing, |
---|
1098 | + # since there is no data there. |
---|
1099 | + d.addCallback(lambda ignored: |
---|
1100 | + mw.put_block(self.block, 0, self.salt)) |
---|
1101 | + def _check_first_write(results): |
---|
1102 | + result, readvs = results |
---|
1103 | + self.failUnless(result) |
---|
1104 | + self.failIf(readvs) |
---|
1105 | + d.addCallback(_check_first_write) |
---|
1106 | + # Now, there should be a different checkstring returned when |
---|
1107 | + # we write other shares |
---|
1108 | + d.addCallback(lambda ignored: |
---|
1109 | + mw.put_block(self.block, 1, self.salt)) |
---|
1110 | + def _check_next_write(results): |
---|
1111 | + result, readvs = results |
---|
1112 | + self.failUnless(result) |
---|
1113 | + self.expected_checkstring = mw.get_checkstring() |
---|
1114 | + self.failUnlessIn(0, readvs) |
---|
1115 | + self.failUnlessEqual(readvs[0][0], self.expected_checkstring) |
---|
1116 | + d.addCallback(_check_next_write) |
---|
1117 | + # Add the other four shares |
---|
1118 | + for i in xrange(2, 6): |
---|
1119 | + d.addCallback(lambda ignored, i=i: |
---|
1120 | + mw.put_block(self.block, i, self.salt)) |
---|
1121 | + d.addCallback(_check_next_write) |
---|
1122 | + # Add the encrypted private key |
---|
1123 | + d.addCallback(lambda ignored: |
---|
1124 | + mw.put_encprivkey(self.encprivkey)) |
---|
1125 | + d.addCallback(_check_next_write) |
---|
1126 | + # Add the block hash tree and share hash tree |
---|
1127 | + d.addCallback(lambda ignored: |
---|
1128 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1129 | + d.addCallback(_check_next_write) |
---|
1130 | + d.addCallback(lambda ignored: |
---|
1131 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1132 | + d.addCallback(_check_next_write) |
---|
1133 | + # Add the root hash and the salt hash. This should change the |
---|
1134 | + # checkstring, but not in a way that we'll be able to see right |
---|
1135 | + # now, since the read vectors are applied before the write |
---|
1136 | + # vectors. |
---|
1137 | + d.addCallback(lambda ignored: |
---|
1138 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1139 | + def _check_old_testv_after_new_one_is_written(results): |
---|
1140 | + result, readvs = results |
---|
1141 | + self.failUnless(result) |
---|
1142 | + self.failUnlessIn(0, readvs) |
---|
1143 | + self.failUnlessEqual(self.expected_checkstring, |
---|
1144 | + readvs[0][0]) |
---|
1145 | + new_checkstring = mw.get_checkstring() |
---|
1146 | + self.failIfEqual(new_checkstring, |
---|
1147 | + readvs[0][0]) |
---|
1148 | + d.addCallback(_check_old_testv_after_new_one_is_written) |
---|
1149 | + # Now add the signature. This should succeed, meaning that the |
---|
1150 | + # data gets written and the read vector matches what the writer |
---|
1151 | + # thinks should be there. |
---|
1152 | + d.addCallback(lambda ignored: |
---|
1153 | + mw.put_signature(self.signature)) |
---|
1154 | + d.addCallback(_check_next_write) |
---|
1155 | + # The checkstring remains the same for the rest of the process. |
---|
1156 | + return d |
---|
1157 | + |
---|
1158 | + |
---|
1159 | + def test_blockhashes_after_share_hash_chain(self): |
---|
1160 | + mw = self._make_new_mw("si1", 0) |
---|
1161 | + d = defer.succeed(None) |
---|
1162 | + # Put everything up to and including the share hash chain |
---|
1163 | + for i in xrange(6): |
---|
1164 | + d.addCallback(lambda ignored, i=i: |
---|
1165 | + mw.put_block(self.block, i, self.salt)) |
---|
1166 | + d.addCallback(lambda ignored: |
---|
1167 | + mw.put_encprivkey(self.encprivkey)) |
---|
1168 | + d.addCallback(lambda ignored: |
---|
1169 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1170 | + d.addCallback(lambda ignored: |
---|
1171 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1172 | + # Now try to put a block hash tree after the share hash chain. |
---|
1173 | + # This won't necessarily overwrite the share hash chain, but it |
---|
1174 | + # is a bad idea in general -- if we write one that is anything |
---|
1175 | + # other than the exact size of the initial one, we will either |
---|
1176 | + # overwrite the share hash chain, or give the reader (who uses |
---|
1177 | + # the offset of the share hash chain as an end boundary) a |
---|
1178 | + # shorter tree than they know to read, which will result in them |
---|
1179 | + # reading junk. There is little reason to support it as a use |
---|
1180 | + # case, so we should disallow it altogether. |
---|
1181 | + d.addCallback(lambda ignored: |
---|
1182 | + self.shouldFail(LayoutInvalid, "test same blockhashes", |
---|
1183 | + None, |
---|
1184 | + mw.put_blockhashes, self.block_hash_tree)) |
---|
1185 | + return d |
---|
1186 | + |
---|
1187 | + |
---|
1188 | + def test_encprivkey_after_blockhashes(self): |
---|
1189 | + mw = self._make_new_mw("si1", 0) |
---|
1190 | + d = defer.succeed(None) |
---|
1191 | + # Put everything up to and including the block hash tree |
---|
1192 | + for i in xrange(6): |
---|
1193 | + d.addCallback(lambda ignored, i=i: |
---|
1194 | + mw.put_block(self.block, i, self.salt)) |
---|
1195 | + d.addCallback(lambda ignored: |
---|
1196 | + mw.put_encprivkey(self.encprivkey)) |
---|
1197 | + d.addCallback(lambda ignored: |
---|
1198 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1199 | + d.addCallback(lambda ignored: |
---|
1200 | + self.shouldFail(LayoutInvalid, "out of order private key", |
---|
1201 | + None, |
---|
1202 | + mw.put_encprivkey, self.encprivkey)) |
---|
1203 | + return d |
---|
1204 | + |
---|
1205 | + |
---|
1206 | + def test_share_hash_chain_after_signature(self): |
---|
1207 | + mw = self._make_new_mw("si1", 0) |
---|
1208 | + d = defer.succeed(None) |
---|
1209 | + # Put everything up to and including the signature |
---|
1210 | + for i in xrange(6): |
---|
1211 | + d.addCallback(lambda ignored, i=i: |
---|
1212 | + mw.put_block(self.block, i, self.salt)) |
---|
1213 | + d.addCallback(lambda ignored: |
---|
1214 | + mw.put_encprivkey(self.encprivkey)) |
---|
1215 | + d.addCallback(lambda ignored: |
---|
1216 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1217 | + d.addCallback(lambda ignored: |
---|
1218 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1219 | + d.addCallback(lambda ignored: |
---|
1220 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1221 | + d.addCallback(lambda ignored: |
---|
1222 | + mw.put_signature(self.signature)) |
---|
1223 | + # Now try to put the share hash chain again. This should fail |
---|
1224 | + d.addCallback(lambda ignored: |
---|
1225 | + self.shouldFail(LayoutInvalid, "out of order share hash chain", |
---|
1226 | + None, |
---|
1227 | + mw.put_sharehashes, self.share_hash_chain)) |
---|
1228 | + return d |
---|
1229 | + |
---|
1230 | + |
---|
1231 | + def test_signature_after_verification_key(self): |
---|
1232 | + mw = self._make_new_mw("si1", 0) |
---|
1233 | + d = defer.succeed(None) |
---|
1234 | + # Put everything up to and including the verification key. |
---|
1235 | + for i in xrange(6): |
---|
1236 | + d.addCallback(lambda ignored, i=i: |
---|
1237 | + mw.put_block(self.block, i, self.salt)) |
---|
1238 | + d.addCallback(lambda ignored: |
---|
1239 | + mw.put_encprivkey(self.encprivkey)) |
---|
1240 | + d.addCallback(lambda ignored: |
---|
1241 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1242 | + d.addCallback(lambda ignored: |
---|
1243 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1244 | + d.addCallback(lambda ignored: |
---|
1245 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1246 | + d.addCallback(lambda ignored: |
---|
1247 | + mw.put_signature(self.signature)) |
---|
1248 | + d.addCallback(lambda ignored: |
---|
1249 | + mw.put_verification_key(self.verification_key)) |
---|
1250 | + # Now try to put the signature again. This should fail |
---|
1251 | + d.addCallback(lambda ignored: |
---|
1252 | + self.shouldFail(LayoutInvalid, "signature after verification", |
---|
1253 | + None, |
---|
1254 | + mw.put_signature, self.signature)) |
---|
1255 | + return d |
---|
1256 | + |
---|
1257 | + |
---|
1258 | + def test_uncoordinated_write(self): |
---|
1259 | + # Make two mutable writers, both pointing to the same storage |
---|
1260 | + # server, both at the same storage index, and try writing to the |
---|
1261 | + # same share. |
---|
1262 | + mw1 = self._make_new_mw("si1", 0) |
---|
1263 | + mw2 = self._make_new_mw("si1", 0) |
---|
1264 | + d = defer.succeed(None) |
---|
1265 | + def _check_success(results): |
---|
1266 | + result, readvs = results |
---|
1267 | + self.failUnless(result) |
---|
1268 | + |
---|
1269 | + def _check_failure(results): |
---|
1270 | + result, readvs = results |
---|
1271 | + self.failIf(result) |
---|
1272 | + |
---|
1273 | + d.addCallback(lambda ignored: |
---|
1274 | + mw1.put_block(self.block, 0, self.salt)) |
---|
1275 | + d.addCallback(_check_success) |
---|
1276 | + d.addCallback(lambda ignored: |
---|
1277 | + mw2.put_block(self.block, 0, self.salt)) |
---|
1278 | + d.addCallback(_check_failure) |
---|
1279 | + return d |
---|
1280 | + |
---|
1281 | + |
---|
1282 | + def test_invalid_salt_size(self): |
---|
1283 | + # Salts need to be 16 bytes in size. Writes that attempt to |
---|
1284 | + # write more or less than this should be rejected. |
---|
1285 | + mw = self._make_new_mw("si1", 0) |
---|
1286 | + invalid_salt = "a" * 17 # 17 bytes |
---|
1287 | + another_invalid_salt = "b" * 15 # 15 bytes |
---|
1288 | + d = defer.succeed(None) |
---|
1289 | + d.addCallback(lambda ignored: |
---|
1290 | + self.shouldFail(LayoutInvalid, "salt too big", |
---|
1291 | + None, |
---|
1292 | + mw.put_block, self.block, 0, invalid_salt)) |
---|
1293 | + d.addCallback(lambda ignored: |
---|
1294 | + self.shouldFail(LayoutInvalid, "salt too small", |
---|
1295 | + None, |
---|
1296 | + mw.put_block, self.block, 0, |
---|
1297 | + another_invalid_salt)) |
---|
1298 | + return d |
---|
1299 | + |
---|
1300 | + |
---|
1301 | + def test_write_test_vectors(self): |
---|
1302 | + # If we give the write proxy a bogus test vector at |
---|
1303 | + # any point during the process, it should fail to write. |
---|
1304 | + mw = self._make_new_mw("si1", 0) |
---|
1305 | + mw.set_checkstring("this is a lie") |
---|
1306 | + # The initial write should be expecting to find the improbable |
---|
1307 | + # checkstring above in place; finding nothing, it should fail. |
---|
1308 | + d = defer.succeed(None) |
---|
1309 | + d.addCallback(lambda ignored: |
---|
1310 | + mw.put_block(self.block, 0, self.salt)) |
---|
1311 | + def _check_failure(results): |
---|
1312 | + result, readv = results |
---|
1313 | + self.failIf(result) |
---|
1314 | + d.addCallback(_check_failure) |
---|
1315 | + # Now set the checkstring to the empty string, which |
---|
1316 | + # indicates that no share is there. |
---|
1317 | + d.addCallback(lambda ignored: |
---|
1318 | + mw.set_checkstring("")) |
---|
1319 | + d.addCallback(lambda ignored: |
---|
1320 | + mw.put_block(self.block, 0, self.salt)) |
---|
1321 | + def _check_success(results): |
---|
1322 | + result, readv = results |
---|
1323 | + self.failUnless(result) |
---|
1324 | + d.addCallback(_check_success) |
---|
1325 | + # Now set the checkstring to something wrong |
---|
1326 | + d.addCallback(lambda ignored: |
---|
1327 | + mw.set_checkstring("something wrong")) |
---|
1328 | + # This should fail to do anything |
---|
1329 | + d.addCallback(lambda ignored: |
---|
1330 | + mw.put_block(self.block, 1, self.salt)) |
---|
1331 | + d.addCallback(_check_failure) |
---|
1332 | + # Now set it back to what it should be. |
---|
1333 | + d.addCallback(lambda ignored: |
---|
1334 | + mw.set_checkstring(mw.get_checkstring())) |
---|
1335 | + for i in xrange(1, 6): |
---|
1336 | + d.addCallback(lambda ignored, i=i: |
---|
1337 | + mw.put_block(self.block, i, self.salt)) |
---|
1338 | + d.addCallback(_check_success) |
---|
1339 | + d.addCallback(lambda ignored: |
---|
1340 | + mw.put_encprivkey(self.encprivkey)) |
---|
1341 | + d.addCallback(_check_success) |
---|
1342 | + d.addCallback(lambda ignored: |
---|
1343 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1344 | + d.addCallback(_check_success) |
---|
1345 | + d.addCallback(lambda ignored: |
---|
1346 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1347 | + d.addCallback(_check_success) |
---|
1348 | + def _keep_old_checkstring(ignored): |
---|
1349 | + self.old_checkstring = mw.get_checkstring() |
---|
1350 | + mw.set_checkstring("foobarbaz") |
---|
1351 | + d.addCallback(_keep_old_checkstring) |
---|
1352 | + d.addCallback(lambda ignored: |
---|
1353 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1354 | + d.addCallback(_check_failure) |
---|
1355 | + d.addCallback(lambda ignored: |
---|
1356 | + self.failUnlessEqual(self.old_checkstring, mw.get_checkstring())) |
---|
1357 | + def _restore_old_checkstring(ignored): |
---|
1358 | + mw.set_checkstring(self.old_checkstring) |
---|
1359 | + d.addCallback(_restore_old_checkstring) |
---|
1360 | + d.addCallback(lambda ignored: |
---|
1361 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1362 | + # The checkstring should have been set appropriately for us on |
---|
1363 | + # the last write; if we try to change it to something else, |
---|
1364 | + # that change should cause the verification key step to fail. |
---|
1365 | + d.addCallback(lambda ignored: |
---|
1366 | + mw.set_checkstring("something else")) |
---|
1367 | + d.addCallback(lambda ignored: |
---|
1368 | + mw.put_signature(self.signature)) |
---|
1369 | + d.addCallback(_check_failure) |
---|
1370 | + d.addCallback(lambda ignored: |
---|
1371 | + mw.set_checkstring(mw.get_checkstring())) |
---|
1372 | + d.addCallback(lambda ignored: |
---|
1373 | + mw.put_signature(self.signature)) |
---|
1374 | + d.addCallback(_check_success) |
---|
1375 | + d.addCallback(lambda ignored: |
---|
1376 | + mw.put_verification_key(self.verification_key)) |
---|
1377 | + d.addCallback(_check_success) |
---|
1378 | + return d |
---|
1379 | + |
---|
1380 | + |
---|
1381 | + def test_offset_only_set_on_success(self): |
---|
1382 | + # The write proxy should be smart enough to detect when a write |
---|
1383 | + # has failed, and to temper its definition of progress based on |
---|
1384 | + # that. |
---|
1385 | + mw = self._make_new_mw("si1", 0) |
---|
1386 | + d = defer.succeed(None) |
---|
1387 | + for i in xrange(1, 6): |
---|
1388 | + d.addCallback(lambda ignored, i=i: |
---|
1389 | + mw.put_block(self.block, i, self.salt)) |
---|
1390 | + def _break_checkstring(ignored): |
---|
1391 | + self._old_checkstring = mw.get_checkstring() |
---|
1392 | + mw.set_checkstring("foobarbaz") |
---|
1393 | + |
---|
1394 | + def _fix_checkstring(ignored): |
---|
1395 | + mw.set_checkstring(self._old_checkstring) |
---|
1396 | + |
---|
1397 | + d.addCallback(_break_checkstring) |
---|
1398 | + |
---|
1399 | + # Setting the encrypted private key shouldn't work now, which is |
---|
1400 | + # to be expected and is tested elsewhere. We also want to make |
---|
1401 | + # sure that we can't add the block hash tree after a failed |
---|
1402 | + # write of this sort. |
---|
1403 | + d.addCallback(lambda ignored: |
---|
1404 | + mw.put_encprivkey(self.encprivkey)) |
---|
1405 | + d.addCallback(lambda ignored: |
---|
1406 | + self.shouldFail(LayoutInvalid, "test out-of-order blockhashes", |
---|
1407 | + None, |
---|
1408 | + mw.put_blockhashes, self.block_hash_tree)) |
---|
1409 | + d.addCallback(_fix_checkstring) |
---|
1410 | + d.addCallback(lambda ignored: |
---|
1411 | + mw.put_encprivkey(self.encprivkey)) |
---|
1412 | + d.addCallback(_break_checkstring) |
---|
1413 | + d.addCallback(lambda ignored: |
---|
1414 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1415 | + d.addCallback(lambda ignored: |
---|
1416 | + self.shouldFail(LayoutInvalid, "test out-of-order sharehashes", |
---|
1417 | + None, |
---|
1418 | + mw.put_sharehashes, self.share_hash_chain)) |
---|
1419 | + d.addCallback(_fix_checkstring) |
---|
1420 | + d.addCallback(lambda ignored: |
---|
1421 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1422 | + d.addCallback(_break_checkstring) |
---|
1423 | + d.addCallback(lambda ignored: |
---|
1424 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1425 | + d.addCallback(lambda ignored: |
---|
1426 | + self.shouldFail(LayoutInvalid, "out-of-order root hash", |
---|
1427 | + None, |
---|
1428 | + mw.put_root_and_salt_hashes, |
---|
1429 | + self.root_hash, self.salt_hash)) |
---|
1430 | + d.addCallback(_fix_checkstring) |
---|
1431 | + d.addCallback(lambda ignored: |
---|
1432 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1433 | + d.addCallback(_break_checkstring) |
---|
1434 | + d.addCallback(lambda ignored: |
---|
1435 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1436 | + d.addCallback(lambda ignored: |
---|
1437 | + self.shouldFail(LayoutInvalid, "out-of-order signature", |
---|
1438 | + None, |
---|
1439 | + mw.put_signature, self.signature)) |
---|
1440 | + d.addCallback(_fix_checkstring) |
---|
1441 | + d.addCallback(lambda ignored: |
---|
1442 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1443 | + d.addCallback(_break_checkstring) |
---|
1444 | + d.addCallback(lambda ignored: |
---|
1445 | + mw.put_signature(self.signature)) |
---|
1446 | + d.addCallback(lambda ignored: |
---|
1447 | + self.shouldFail(LayoutInvalid, "out-of-order verification key", |
---|
1448 | + None, |
---|
1449 | + mw.put_verification_key, |
---|
1450 | + self.verification_key)) |
---|
1451 | + d.addCallback(_fix_checkstring) |
---|
1452 | + d.addCallback(lambda ignored: |
---|
1453 | + mw.put_signature(self.signature)) |
---|
1454 | + d.addCallback(_break_checkstring) |
---|
1455 | + d.addCallback(lambda ignored: |
---|
1456 | + mw.put_verification_key(self.verification_key)) |
---|
1457 | + d.addCallback(lambda ignored: |
---|
1458 | + self.shouldFail(LayoutInvalid, "out-of-order finish", |
---|
1459 | + None, |
---|
1460 | + mw.finish_publishing)) |
---|
1461 | + return d |
---|
1462 | + |
---|
1463 | + |
---|
1464 | + def serialize_blockhashes(self, blockhashes): |
---|
1465 | + return "".join(blockhashes) |
---|
1466 | + |
---|
1467 | + |
---|
1468 | + def serialize_sharehashes(self, sharehashes): |
---|
1469 | + ret = "".join([struct.pack(">H32s", i, sharehashes[i]) |
---|
1470 | + for i in sorted(sharehashes.keys())]) |
---|
1471 | + return ret |
---|
1472 | + |
---|
1473 | + |
---|
1474 | + def test_write(self): |
---|
1475 | + # This translates to a file with 6 6-byte segments, and with 2-byte |
---|
1476 | + # blocks. |
---|
1477 | + mw = self._make_new_mw("si1", 0) |
---|
1478 | + mw2 = self._make_new_mw("si1", 1) |
---|
1479 | + # Test writing some blocks. |
---|
1480 | + read = self.ss.remote_slot_readv |
---|
1481 | + def _check_block_write(i, share): |
---|
1482 | + self.failUnlessEqual(read("si1", [share], [(239 + (i * 2), 2)]), |
---|
1483 | + {share: [self.block]}) |
---|
1484 | + self.failUnlessEqual(read("si1", [share], [(143 + (i * 16), 16)]), |
---|
1485 | + {share: [self.salt]}) |
---|
1486 | + d = defer.succeed(None) |
---|
1487 | + for i in xrange(6): |
---|
1488 | + d.addCallback(lambda ignored, i=i: |
---|
1489 | + mw.put_block(self.block, i, self.salt)) |
---|
1490 | + d.addCallback(lambda ignored, i=i: |
---|
1491 | + _check_block_write(i, 0)) |
---|
1492 | + # Now try the same thing, but with share 1 instead of share 0. |
---|
1493 | + for i in xrange(6): |
---|
1494 | + d.addCallback(lambda ignored, i=i: |
---|
1495 | + mw2.put_block(self.block, i, self.salt)) |
---|
1496 | + d.addCallback(lambda ignored, i=i: |
---|
1497 | + _check_block_write(i, 1)) |
---|
1498 | + |
---|
1499 | + def _spy_on_results(results): |
---|
1500 | + print read("si1", [], [(0, 40000000)]) |
---|
1501 | + return results |
---|
1502 | + |
---|
1503 | + # Next, we make a fake encrypted private key, and put it onto the |
---|
1504 | + # storage server. |
---|
1505 | + d.addCallback(lambda ignored: |
---|
1506 | + mw.put_encprivkey(self.encprivkey)) |
---|
1507 | + # So far, we have: |
---|
1508 | + # header: 143 bytes |
---|
1509 | + # salts: 16 * 6 = 96 bytes |
---|
1510 | + # blocks: 2 * 6 = 12 bytes |
---|
1511 | + # = 251 bytes |
---|
1512 | + expected_private_key_offset = 251 |
---|
1513 | + self.failUnlessEqual(len(self.encprivkey), 7) |
---|
1514 | + d.addCallback(lambda ignored: |
---|
1515 | + self.failUnlessEqual(read("si1", [0], [(251, 7)]), |
---|
1516 | + {0: [self.encprivkey]})) |
---|
1517 | + |
---|
1518 | + # Next, we put a fake block hash tree. |
---|
1519 | + d.addCallback(lambda ignored: |
---|
1520 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1521 | + # The block hash tree got inserted at: |
---|
1522 | + # header + salts + blocks: 251 bytes |
---|
1523 | + # encrypted private key: 7 bytes |
---|
1524 | + # = 258 bytes |
---|
1525 | + expected_block_hash_offset = 258 |
---|
1526 | + self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6) |
---|
1527 | + d.addCallback(lambda ignored: |
---|
1528 | + self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]), |
---|
1529 | + {0: [self.block_hash_tree_s]})) |
---|
1530 | + |
---|
1531 | + # Next, put a fake share hash chain |
---|
1532 | + d.addCallback(lambda ignored: |
---|
1533 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1534 | + # The share hash chain got inserted at: |
---|
1535 | + # header + salts + blocks + private key = 258 bytes |
---|
1536 | + # block hash tree: 32 * 6 = 192 bytes |
---|
1537 | + # = 450 bytes |
---|
1538 | + expected_share_hash_offset = 450 |
---|
1539 | + d.addCallback(lambda ignored: |
---|
1540 | + self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]), |
---|
1541 | + {0: [self.share_hash_chain_s]})) |
---|
1542 | + |
---|
1543 | + # Next, we put what is supposed to be the root hash of |
---|
1544 | + # our share hash tree but isn't, along with the flat hash |
---|
1545 | + # of all the salts. |
---|
1546 | + d.addCallback(lambda ignored: |
---|
1547 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1548 | + # The root hash gets inserted at byte 9 (its position is in the header, |
---|
1549 | + # and is fixed). The salt is right after it. |
---|
1550 | + def _check(ignored): |
---|
1551 | + self.failUnlessEqual(read("si1", [0], [(9, 32)]), |
---|
1552 | + {0: [self.root_hash]}) |
---|
1553 | + self.failUnlessEqual(read("si1", [0], [(41, 32)]), |
---|
1554 | + {0: [self.salt_hash]}) |
---|
1555 | + d.addCallback(_check) |
---|
1556 | + |
---|
1557 | + # Next, we put a signature of the header block. |
---|
1558 | + d.addCallback(lambda ignored: |
---|
1559 | + mw.put_signature(self.signature)) |
---|
1560 | + # The signature gets written to: |
---|
1561 | + # header + salts + blocks + block and share hash tree = 654 |
---|
1562 | + expected_signature_offset = 654 |
---|
1563 | + self.failUnlessEqual(len(self.signature), 9) |
---|
1564 | + d.addCallback(lambda ignored: |
---|
1565 | + self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]), |
---|
1566 | + {0: [self.signature]})) |
---|
1567 | + |
---|
1568 | + # Next, we put the verification key |
---|
1569 | + d.addCallback(lambda ignored: |
---|
1570 | + mw.put_verification_key(self.verification_key)) |
---|
1571 | + # The verification key gets written to: |
---|
1572 | + # 654 + 9 = 663 bytes |
---|
1573 | + expected_verification_key_offset = 663 |
---|
1574 | + self.failUnlessEqual(len(self.verification_key), 6) |
---|
1575 | + d.addCallback(lambda ignored: |
---|
1576 | + self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]), |
---|
1577 | + {0: [self.verification_key]})) |
---|
1578 | + |
---|
1579 | + def _check_signable(ignored): |
---|
1580 | + # Make sure that the signable is what we think it should be. |
---|
1581 | + signable = mw.get_signable() |
---|
1582 | + verno, seq, roothash, salthash, k, n, segsize, datalen = \ |
---|
1583 | + struct.unpack(">BQ32s32sBBQQ", |
---|
1584 | + signable) |
---|
1585 | + self.failUnlessEqual(verno, 1) |
---|
1586 | + self.failUnlessEqual(seq, 0) |
---|
1587 | + self.failUnlessEqual(roothash, self.root_hash) |
---|
1588 | + self.failUnlessEqual(salthash, self.salt_hash) |
---|
1589 | + self.failUnlessEqual(k, 3) |
---|
1590 | + self.failUnlessEqual(n, 10) |
---|
1591 | + self.failUnlessEqual(segsize, 6) |
---|
1592 | + self.failUnlessEqual(datalen, 36) |
---|
1593 | + d.addCallback(_check_signable) |
---|
1594 | + # Next, we cause the offset table to be published. |
---|
1595 | + d.addCallback(lambda ignored: |
---|
1596 | + mw.finish_publishing()) |
---|
1597 | + expected_eof_offset = 669 |
---|
1598 | + |
---|
1599 | + # The offset table starts at byte 91. Happily, we have already |
---|
1600 | + # worked out most of these offsets above, but we want to make |
---|
1601 | + # sure that the representation on disk agrees what what we've |
---|
1602 | + # calculated. |
---|
1603 | + # |
---|
1604 | + # (we don't have an explicit offset for the AES salts, because |
---|
1605 | + # we know that they start right after the header) |
---|
1606 | + def _check_offsets(ignored): |
---|
1607 | + # Check the version number to make sure that it is correct. |
---|
1608 | + expected_version_number = struct.pack(">B", 1) |
---|
1609 | + self.failUnlessEqual(read("si1", [0], [(0, 1)]), |
---|
1610 | + {0: [expected_version_number]}) |
---|
1611 | + # Check the sequence number to make sure that it is correct |
---|
1612 | + expected_sequence_number = struct.pack(">Q", 0) |
---|
1613 | + self.failUnlessEqual(read("si1", [0], [(1, 8)]), |
---|
1614 | + {0: [expected_sequence_number]}) |
---|
1615 | + # Check that the encoding parameters (k, N, segement size, data |
---|
1616 | + # length) are what they should be. These are 3, 10, 6, 36 |
---|
1617 | + expected_k = struct.pack(">B", 3) |
---|
1618 | + self.failUnlessEqual(read("si1", [0], [(73, 1)]), |
---|
1619 | + {0: [expected_k]}) |
---|
1620 | + expected_n = struct.pack(">B", 10) |
---|
1621 | + self.failUnlessEqual(read("si1", [0], [(74, 1)]), |
---|
1622 | + {0: [expected_n]}) |
---|
1623 | + expected_segment_size = struct.pack(">Q", 6) |
---|
1624 | + self.failUnlessEqual(read("si1", [0], [(75, 8)]), |
---|
1625 | + {0: [expected_segment_size]}) |
---|
1626 | + expected_data_length = struct.pack(">Q", 36) |
---|
1627 | + self.failUnlessEqual(read("si1", [0], [(83, 8)]), |
---|
1628 | + {0: [expected_data_length]}) |
---|
1629 | + # 91 4 The offset of the share data |
---|
1630 | + expected_offset = struct.pack(">L", 239) |
---|
1631 | + self.failUnlessEqual(read("si1", [0], [(91, 4)]), |
---|
1632 | + {0: [expected_offset]}) |
---|
1633 | + # 95 8 The offset of the encrypted private key |
---|
1634 | + expected_offset = struct.pack(">Q", expected_private_key_offset) |
---|
1635 | + self.failUnlessEqual(read("si1", [0], [(95, 8)]), |
---|
1636 | + {0: [expected_offset]}) |
---|
1637 | + # 103 8 The offset of the block hash tree |
---|
1638 | + expected_offset = struct.pack(">Q", expected_block_hash_offset) |
---|
1639 | + self.failUnlessEqual(read("si1", [0], [(103, 8)]), |
---|
1640 | + {0: [expected_offset]}) |
---|
1641 | + # 111 8 The offset of the share hash chain |
---|
1642 | + expected_offset = struct.pack(">Q", expected_share_hash_offset) |
---|
1643 | + self.failUnlessEqual(read("si1", [0], [(111, 8)]), |
---|
1644 | + {0: [expected_offset]}) |
---|
1645 | + # 119 8 The offset of the signature |
---|
1646 | + expected_offset = struct.pack(">Q", expected_signature_offset) |
---|
1647 | + self.failUnlessEqual(read("si1", [0], [(119, 8)]), |
---|
1648 | + {0: [expected_offset]}) |
---|
1649 | + # 127 8 The offset of the verification key |
---|
1650 | + expected_offset = struct.pack(">Q", expected_verification_key_offset) |
---|
1651 | + self.failUnlessEqual(read("si1", [0], [(127, 8)]), |
---|
1652 | + {0: [expected_offset]}) |
---|
1653 | + # 135 8 offset of the EOF |
---|
1654 | + expected_offset = struct.pack(">Q", expected_eof_offset) |
---|
1655 | + self.failUnlessEqual(read("si1", [0], [(135, 8)]), |
---|
1656 | + {0: [expected_offset]}) |
---|
1657 | + # = 143 bytes in total. |
---|
1658 | + d.addCallback(_check_offsets) |
---|
1659 | + return d |
---|
1660 | + |
---|
1661 | + def _make_new_mw(self, si, share, datalength=36): |
---|
1662 | + # This is a file of size 36 bytes. Since it has a segment |
---|
1663 | + # size of 6, we know that it has 6 byte segments, which will |
---|
1664 | + # be split into blocks of 2 bytes because our FEC k |
---|
1665 | + # parameter is 3. |
---|
1666 | + mw = MDMFSlotWriteProxy(share, self.rref, si, self.secrets, 0, 3, 10, |
---|
1667 | + 6, datalength) |
---|
1668 | + return mw |
---|
1669 | + |
---|
1670 | + |
---|
1671 | + def test_write_rejected_with_too_many_blocks(self): |
---|
1672 | + mw = self._make_new_mw("si0", 0) |
---|
1673 | + |
---|
1674 | + # Try writing too many blocks. We should not be able to write |
---|
1675 | + # more than 6 |
---|
1676 | + # blocks into each share. |
---|
1677 | + d = defer.succeed(None) |
---|
1678 | + for i in xrange(6): |
---|
1679 | + d.addCallback(lambda ignored, i=i: |
---|
1680 | + mw.put_block(self.block, i, self.salt)) |
---|
1681 | + d.addCallback(lambda ignored: |
---|
1682 | + self.shouldFail(LayoutInvalid, "too many blocks", |
---|
1683 | + None, |
---|
1684 | + mw.put_block, self.block, 7, self.salt)) |
---|
1685 | + return d |
---|
1686 | + |
---|
1687 | + |
---|
1688 | + def test_write_rejected_with_invalid_salt(self): |
---|
1689 | + # Try writing an invalid salt. Salts are 16 bytes -- any more or |
---|
1690 | + # less should cause an error. |
---|
1691 | + mw = self._make_new_mw("si1", 0) |
---|
1692 | + bad_salt = "a" * 17 # 17 bytes |
---|
1693 | + d = defer.succeed(None) |
---|
1694 | + d.addCallback(lambda ignored: |
---|
1695 | + self.shouldFail(LayoutInvalid, "test_invalid_salt", |
---|
1696 | + None, mw.put_block, self.block, 7, bad_salt)) |
---|
1697 | + return d |
---|
1698 | + |
---|
1699 | + |
---|
1700 | + def test_write_rejected_with_invalid_salt_hash(self): |
---|
1701 | + # Try writing an invalid salt hash. These should be SHA256d, and |
---|
1702 | + # 32 bytes long as a result. |
---|
1703 | + mw = self._make_new_mw("si2", 0) |
---|
1704 | + invalid_salt_hash = "b" * 31 |
---|
1705 | + d = defer.succeed(None) |
---|
1706 | + # Before this test can work, we need to put some blocks + salts, |
---|
1707 | + # a block hash tree, and a share hash tree. Otherwise, we'll see |
---|
1708 | + # failures that match what we are looking for, but are caused by |
---|
1709 | + # the constraints imposed on operation ordering. |
---|
1710 | + for i in xrange(6): |
---|
1711 | + d.addCallback(lambda ignored, i=i: |
---|
1712 | + mw.put_block(self.block, i, self.salt)) |
---|
1713 | + d.addCallback(lambda ignored: |
---|
1714 | + mw.put_encprivkey(self.encprivkey)) |
---|
1715 | + d.addCallback(lambda ignored: |
---|
1716 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1717 | + d.addCallback(lambda ignored: |
---|
1718 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1719 | + d.addCallback(lambda ignored: |
---|
1720 | + self.shouldFail(LayoutInvalid, "invalid root hash", |
---|
1721 | + None, mw.put_root_and_salt_hashes, |
---|
1722 | + self.root_hash, invalid_salt_hash)) |
---|
1723 | + return d |
---|
1724 | + |
---|
1725 | + |
---|
1726 | + def test_write_rejected_with_invalid_root_hash(self): |
---|
1727 | + # Try writing an invalid root hash. This should be SHA256d, and |
---|
1728 | + # 32 bytes long as a result. |
---|
1729 | + mw = self._make_new_mw("si2", 0) |
---|
1730 | + # 17 bytes != 32 bytes |
---|
1731 | + invalid_root_hash = "a" * 17 |
---|
1732 | + d = defer.succeed(None) |
---|
1733 | + # Before this test can work, we need to put some blocks + salts, |
---|
1734 | + # a block hash tree, and a share hash tree. Otherwise, we'll see |
---|
1735 | + # failures that match what we are looking for, but are caused by |
---|
1736 | + # the constraints imposed on operation ordering. |
---|
1737 | + for i in xrange(6): |
---|
1738 | + d.addCallback(lambda ignored, i=i: |
---|
1739 | + mw.put_block(self.block, i, self.salt)) |
---|
1740 | + d.addCallback(lambda ignored: |
---|
1741 | + mw.put_encprivkey(self.encprivkey)) |
---|
1742 | + d.addCallback(lambda ignored: |
---|
1743 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1744 | + d.addCallback(lambda ignored: |
---|
1745 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1746 | + d.addCallback(lambda ignored: |
---|
1747 | + self.shouldFail(LayoutInvalid, "invalid root hash", |
---|
1748 | + None, mw.put_root_and_salt_hashes, |
---|
1749 | + invalid_root_hash, self.salt_hash)) |
---|
1750 | + return d |
---|
1751 | + |
---|
1752 | + |
---|
1753 | + def test_write_rejected_with_invalid_blocksize(self): |
---|
1754 | + # The blocksize implied by the writer that we get from |
---|
1755 | + # _make_new_mw is 2bytes -- any more or any less than this |
---|
1756 | + # should be cause for failure, unless it is the tail segment, in |
---|
1757 | + # which case it may not be failure. |
---|
1758 | + invalid_block = "a" |
---|
1759 | + mw = self._make_new_mw("si3", 0, 33) # implies a tail segment with |
---|
1760 | + # one byte blocks |
---|
1761 | + # 1 bytes != 2 bytes |
---|
1762 | + d = defer.succeed(None) |
---|
1763 | + d.addCallback(lambda ignored, invalid_block=invalid_block: |
---|
1764 | + self.shouldFail(LayoutInvalid, "test blocksize too small", |
---|
1765 | + None, mw.put_block, invalid_block, 0, |
---|
1766 | + self.salt)) |
---|
1767 | + invalid_block = invalid_block * 3 |
---|
1768 | + # 3 bytes != 2 bytes |
---|
1769 | + d.addCallback(lambda ignored: |
---|
1770 | + self.shouldFail(LayoutInvalid, "test blocksize too large", |
---|
1771 | + None, |
---|
1772 | + mw.put_block, invalid_block, 0, self.salt)) |
---|
1773 | + for i in xrange(5): |
---|
1774 | + d.addCallback(lambda ignored, i=i: |
---|
1775 | + mw.put_block(self.block, i, self.salt)) |
---|
1776 | + # Try to put an invalid tail segment |
---|
1777 | + d.addCallback(lambda ignored: |
---|
1778 | + self.shouldFail(LayoutInvalid, "test invalid tail segment", |
---|
1779 | + None, |
---|
1780 | + mw.put_block, self.block, 5, self.salt)) |
---|
1781 | + valid_block = "a" |
---|
1782 | + d.addCallback(lambda ignored: |
---|
1783 | + mw.put_block(valid_block, 5, self.salt)) |
---|
1784 | + return d |
---|
1785 | + |
---|
1786 | + |
---|
1787 | + def test_write_enforces_order_constraints(self): |
---|
1788 | + # We require that the MDMFSlotWriteProxy be interacted with in a |
---|
1789 | + # specific way. |
---|
1790 | + # That way is: |
---|
1791 | + # 0: __init__ |
---|
1792 | + # 1: write blocks and salts |
---|
1793 | + # 2: Write the encrypted private key |
---|
1794 | + # 3: Write the block hashes |
---|
1795 | + # 4: Write the share hashes |
---|
1796 | + # 5: Write the root hash and salt hash |
---|
1797 | + # 6: Write the signature and verification key |
---|
1798 | + # 7: Write the file. |
---|
1799 | + # |
---|
1800 | + # Some of these can be performed out-of-order, and some can't. |
---|
1801 | + # The dependencies that I want to test here are: |
---|
1802 | + # - Private key before block hashes |
---|
1803 | + # - share hashes and block hashes before root hash |
---|
1804 | + # - root hash before signature |
---|
1805 | + # - signature before verification key |
---|
1806 | + mw0 = self._make_new_mw("si0", 0) |
---|
1807 | + # Write some shares |
---|
1808 | + d = defer.succeed(None) |
---|
1809 | + for i in xrange(6): |
---|
1810 | + d.addCallback(lambda ignored, i=i: |
---|
1811 | + mw0.put_block(self.block, i, self.salt)) |
---|
1812 | + # Try to write the block hashes before writing the encrypted |
---|
1813 | + # private key |
---|
1814 | + d.addCallback(lambda ignored: |
---|
1815 | + self.shouldFail(LayoutInvalid, "block hashes before key", |
---|
1816 | + None, mw0.put_blockhashes, |
---|
1817 | + self.block_hash_tree)) |
---|
1818 | + |
---|
1819 | + # Write the private key. |
---|
1820 | + d.addCallback(lambda ignored: |
---|
1821 | + mw0.put_encprivkey(self.encprivkey)) |
---|
1822 | + |
---|
1823 | + |
---|
1824 | + # Try to write the share hash chain without writing the block |
---|
1825 | + # hash tree |
---|
1826 | + d.addCallback(lambda ignored: |
---|
1827 | + self.shouldFail(LayoutInvalid, "share hash chain before " |
---|
1828 | + "block hash tree", |
---|
1829 | + None, |
---|
1830 | + mw0.put_sharehashes, self.share_hash_chain)) |
---|
1831 | + |
---|
1832 | + # Try to write the root hash and salt hash without writing either the |
---|
1833 | + # block hashes or the share hashes |
---|
1834 | + d.addCallback(lambda ignored: |
---|
1835 | + self.shouldFail(LayoutInvalid, "root hash before share hashes", |
---|
1836 | + None, |
---|
1837 | + mw0.put_root_and_salt_hashes, |
---|
1838 | + self.root_hash, self.salt_hash)) |
---|
1839 | + |
---|
1840 | + # Now write the block hashes and try again |
---|
1841 | + d.addCallback(lambda ignored: |
---|
1842 | + mw0.put_blockhashes(self.block_hash_tree)) |
---|
1843 | + d.addCallback(lambda ignored: |
---|
1844 | + self.shouldFail(LayoutInvalid, "root hash before share hashes", |
---|
1845 | + None, mw0.put_root_and_salt_hashes, |
---|
1846 | + self.root_hash, self.salt_hash)) |
---|
1847 | + |
---|
1848 | + # We haven't yet put the root hash on the share, so we shouldn't |
---|
1849 | + # be able to sign it. |
---|
1850 | + d.addCallback(lambda ignored: |
---|
1851 | + self.shouldFail(LayoutInvalid, "signature before root hash", |
---|
1852 | + None, mw0.put_signature, self.signature)) |
---|
1853 | + |
---|
1854 | + d.addCallback(lambda ignored: |
---|
1855 | + self.failUnlessRaises(LayoutInvalid, mw0.get_signable)) |
---|
1856 | + |
---|
1857 | + # ..and, since that fails, we also shouldn't be able to put the |
---|
1858 | + # verification key. |
---|
1859 | + d.addCallback(lambda ignored: |
---|
1860 | + self.shouldFail(LayoutInvalid, "key before signature", |
---|
1861 | + None, mw0.put_verification_key, |
---|
1862 | + self.verification_key)) |
---|
1863 | + |
---|
1864 | + # Now write the share hashes and verify that it works. |
---|
1865 | + d.addCallback(lambda ignored: |
---|
1866 | + mw0.put_sharehashes(self.share_hash_chain)) |
---|
1867 | + |
---|
1868 | + # We should still be unable to sign the header |
---|
1869 | + d.addCallback(lambda ignored: |
---|
1870 | + self.shouldFail(LayoutInvalid, "signature before hashes", |
---|
1871 | + None, |
---|
1872 | + mw0.put_signature, self.signature)) |
---|
1873 | + |
---|
1874 | + # We should be able to write the root hash now too |
---|
1875 | + d.addCallback(lambda ignored: |
---|
1876 | + mw0.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1877 | + |
---|
1878 | + # We should still be unable to put the verification key |
---|
1879 | + d.addCallback(lambda ignored: |
---|
1880 | + self.shouldFail(LayoutInvalid, "key before signature", |
---|
1881 | + None, mw0.put_verification_key, |
---|
1882 | + self.verification_key)) |
---|
1883 | + |
---|
1884 | + d.addCallback(lambda ignored: |
---|
1885 | + mw0.put_signature(self.signature)) |
---|
1886 | + |
---|
1887 | + # We shouldn't be able to write the offsets to the remote server |
---|
1888 | + # until the offset table is finished; IOW, until we have written |
---|
1889 | + # the verification key. |
---|
1890 | + d.addCallback(lambda ignored: |
---|
1891 | + self.shouldFail(LayoutInvalid, "offsets before verification key", |
---|
1892 | + None, |
---|
1893 | + mw0.finish_publishing)) |
---|
1894 | + |
---|
1895 | + d.addCallback(lambda ignored: |
---|
1896 | + mw0.put_verification_key(self.verification_key)) |
---|
1897 | + return d |
---|
1898 | + |
---|
1899 | + |
---|
1900 | + def test_end_to_end(self): |
---|
1901 | + mw = self._make_new_mw("si1", 0) |
---|
1902 | + # Write a share using the mutable writer, and make sure that the |
---|
1903 | + # reader knows how to read everything back to us. |
---|
1904 | + d = defer.succeed(None) |
---|
1905 | + for i in xrange(6): |
---|
1906 | + d.addCallback(lambda ignored, i=i: |
---|
1907 | + mw.put_block(self.block, i, self.salt)) |
---|
1908 | + d.addCallback(lambda ignored: |
---|
1909 | + mw.put_encprivkey(self.encprivkey)) |
---|
1910 | + d.addCallback(lambda ignored: |
---|
1911 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
1912 | + d.addCallback(lambda ignored: |
---|
1913 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
1914 | + d.addCallback(lambda ignored: |
---|
1915 | + mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
1916 | + d.addCallback(lambda ignored: |
---|
1917 | + mw.put_signature(self.signature)) |
---|
1918 | + d.addCallback(lambda ignored: |
---|
1919 | + mw.put_verification_key(self.verification_key)) |
---|
1920 | + d.addCallback(lambda ignored: |
---|
1921 | + mw.finish_publishing()) |
---|
1922 | + |
---|
1923 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1924 | + def _check_block_and_salt((block, salt)): |
---|
1925 | + self.failUnlessEqual(block, self.block) |
---|
1926 | + self.failUnlessEqual(salt, self.salt) |
---|
1927 | + |
---|
1928 | + for i in xrange(6): |
---|
1929 | + d.addCallback(lambda ignored, i=i: |
---|
1930 | + mr.get_block_and_salt(i)) |
---|
1931 | + d.addCallback(_check_block_and_salt) |
---|
1932 | + |
---|
1933 | + d.addCallback(lambda ignored: |
---|
1934 | + mr.get_encprivkey()) |
---|
1935 | + d.addCallback(lambda encprivkey: |
---|
1936 | + self.failUnlessEqual(self.encprivkey, encprivkey)) |
---|
1937 | + |
---|
1938 | + d.addCallback(lambda ignored: |
---|
1939 | + mr.get_blockhashes()) |
---|
1940 | + d.addCallback(lambda blockhashes: |
---|
1941 | + self.failUnlessEqual(self.block_hash_tree, blockhashes)) |
---|
1942 | + |
---|
1943 | + d.addCallback(lambda ignored: |
---|
1944 | + mr.get_sharehashes()) |
---|
1945 | + d.addCallback(lambda sharehashes: |
---|
1946 | + self.failUnlessEqual(self.share_hash_chain, sharehashes)) |
---|
1947 | + |
---|
1948 | + d.addCallback(lambda ignored: |
---|
1949 | + mr.get_signature()) |
---|
1950 | + d.addCallback(lambda signature: |
---|
1951 | + self.failUnlessEqual(signature, self.signature)) |
---|
1952 | + |
---|
1953 | + d.addCallback(lambda ignored: |
---|
1954 | + mr.get_verification_key()) |
---|
1955 | + d.addCallback(lambda verification_key: |
---|
1956 | + self.failUnlessEqual(verification_key, self.verification_key)) |
---|
1957 | + |
---|
1958 | + d.addCallback(lambda ignored: |
---|
1959 | + mr.get_seqnum()) |
---|
1960 | + d.addCallback(lambda seqnum: |
---|
1961 | + self.failUnlessEqual(seqnum, 0)) |
---|
1962 | + |
---|
1963 | + d.addCallback(lambda ignored: |
---|
1964 | + mr.get_root_hash()) |
---|
1965 | + d.addCallback(lambda root_hash: |
---|
1966 | + self.failUnlessEqual(self.root_hash, root_hash)) |
---|
1967 | + |
---|
1968 | + d.addCallback(lambda ignored: |
---|
1969 | + mr.get_salt_hash()) |
---|
1970 | + d.addCallback(lambda salt_hash: |
---|
1971 | + self.failUnlessEqual(self.salt_hash, salt_hash)) |
---|
1972 | + |
---|
1973 | + d.addCallback(lambda ignored: |
---|
1974 | + mr.get_encoding_parameters()) |
---|
1975 | + def _check_encoding_parameters((k, n, segsize, datalen)): |
---|
1976 | + self.failUnlessEqual(k, 3) |
---|
1977 | + self.failUnlessEqual(n, 10) |
---|
1978 | + self.failUnlessEqual(segsize, 6) |
---|
1979 | + self.failUnlessEqual(datalen, 36) |
---|
1980 | + d.addCallback(_check_encoding_parameters) |
---|
1981 | + |
---|
1982 | + d.addCallback(lambda ignored: |
---|
1983 | + mr.get_checkstring()) |
---|
1984 | + d.addCallback(lambda checkstring: |
---|
1985 | + self.failUnlessEqual(checkstring, mw.get_checkstring())) |
---|
1986 | + return d |
---|
1987 | + |
---|
1988 | + |
---|
1989 | + def test_is_sdmf(self): |
---|
1990 | + # The MDMFSlotReadProxy should also know how to read SDMF files, |
---|
1991 | + # since it will encounter them on the grid. Callers use the |
---|
1992 | + # is_sdmf method to test this. |
---|
1993 | + self.write_sdmf_share_to_server("si1") |
---|
1994 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
1995 | + d = mr.is_sdmf() |
---|
1996 | + d.addCallback(lambda issdmf: |
---|
1997 | + self.failUnless(issdmf)) |
---|
1998 | + return d |
---|
1999 | + |
---|
2000 | + |
---|
2001 | + def test_reads_sdmf(self): |
---|
2002 | + # The slot read proxy should, naturally, know how to tell us |
---|
2003 | + # about data in the SDMF format |
---|
2004 | + self.write_sdmf_share_to_server("si1") |
---|
2005 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
2006 | + d = defer.succeed(None) |
---|
2007 | + d.addCallback(lambda ignored: |
---|
2008 | + mr.is_sdmf()) |
---|
2009 | + d.addCallback(lambda issdmf: |
---|
2010 | + self.failUnless(issdmf)) |
---|
2011 | + |
---|
2012 | + # What do we need to read? |
---|
2013 | + # - The sharedata |
---|
2014 | + # - The salt |
---|
2015 | + d.addCallback(lambda ignored: |
---|
2016 | + mr.get_block_and_salt(0)) |
---|
2017 | + def _check_block_and_salt(results): |
---|
2018 | + block, salt = results |
---|
2019 | + self.failUnlessEqual(block, self.block * 6) |
---|
2020 | + self.failUnlessEqual(salt, self.salt) |
---|
2021 | + d.addCallback(_check_block_and_salt) |
---|
2022 | + |
---|
2023 | + # - The blockhashes |
---|
2024 | + d.addCallback(lambda ignored: |
---|
2025 | + mr.get_blockhashes()) |
---|
2026 | + d.addCallback(lambda blockhashes: |
---|
2027 | + self.failUnlessEqual(self.block_hash_tree, |
---|
2028 | + blockhashes, |
---|
2029 | + blockhashes)) |
---|
2030 | + # - The sharehashes |
---|
2031 | + d.addCallback(lambda ignored: |
---|
2032 | + mr.get_sharehashes()) |
---|
2033 | + d.addCallback(lambda sharehashes: |
---|
2034 | + self.failUnlessEqual(self.share_hash_chain, |
---|
2035 | + sharehashes)) |
---|
2036 | + # - The keys |
---|
2037 | + d.addCallback(lambda ignored: |
---|
2038 | + mr.get_encprivkey()) |
---|
2039 | + d.addCallback(lambda encprivkey: |
---|
2040 | + self.failUnlessEqual(encprivkey, self.encprivkey, encprivkey)) |
---|
2041 | + d.addCallback(lambda ignored: |
---|
2042 | + mr.get_verification_key()) |
---|
2043 | + d.addCallback(lambda verification_key: |
---|
2044 | + self.failUnlessEqual(verification_key, |
---|
2045 | + self.verification_key, |
---|
2046 | + verification_key)) |
---|
2047 | + # - The signature |
---|
2048 | + d.addCallback(lambda ignored: |
---|
2049 | + mr.get_signature()) |
---|
2050 | + d.addCallback(lambda signature: |
---|
2051 | + self.failUnlessEqual(signature, self.signature, signature)) |
---|
2052 | + |
---|
2053 | + # - The sequence number |
---|
2054 | + d.addCallback(lambda ignored: |
---|
2055 | + mr.get_seqnum()) |
---|
2056 | + d.addCallback(lambda seqnum: |
---|
2057 | + self.failUnlessEqual(seqnum, 0, seqnum)) |
---|
2058 | + |
---|
2059 | + # - The root hash |
---|
2060 | + # - The salt hash (to verify that it is None) |
---|
2061 | + d.addCallback(lambda ignored: |
---|
2062 | + mr.get_root_hash()) |
---|
2063 | + d.addCallback(lambda root_hash: |
---|
2064 | + self.failUnlessEqual(root_hash, self.root_hash, root_hash)) |
---|
2065 | + d.addCallback(lambda ignored: |
---|
2066 | + mr.get_salt_hash()) |
---|
2067 | + d.addCallback(lambda salt_hash: |
---|
2068 | + self.failIf(salt_hash)) |
---|
2069 | + return d |
---|
2070 | + |
---|
2071 | + |
---|
2072 | + def test_only_reads_one_segment_sdmf(self): |
---|
2073 | + # SDMF shares have only one segment, so it doesn't make sense to |
---|
2074 | + # read more segments than that. The reader should know this and |
---|
2075 | + # complain if we try to do that. |
---|
2076 | + self.write_sdmf_share_to_server("si1") |
---|
2077 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
2078 | + d = defer.succeed(None) |
---|
2079 | + d.addCallback(lambda ignored: |
---|
2080 | + mr.is_sdmf()) |
---|
2081 | + d.addCallback(lambda issdmf: |
---|
2082 | + self.failUnless(issdmf)) |
---|
2083 | + d.addCallback(lambda ignored: |
---|
2084 | + self.shouldFail(LayoutInvalid, "test bad segment", |
---|
2085 | + None, |
---|
2086 | + mr.get_block_and_salt, 1)) |
---|
2087 | + return d |
---|
2088 | + |
---|
2089 | + |
---|
2090 | + def test_read_with_prefetched_mdmf_data(self): |
---|
2091 | + # The MDMFSlotReadProxy will prefill certain fields if you pass |
---|
2092 | + # it data that you have already fetched. This is useful for |
---|
2093 | + # cases like the Servermap, which prefetches ~2kb of data while |
---|
2094 | + # finding out which shares are on the remote peer so that it |
---|
2095 | + # doesn't waste round trips. |
---|
2096 | + mdmf_data = self.build_test_mdmf_share() |
---|
2097 | + # We're telling it enough to figure out whether it is SDMF or |
---|
2098 | + # MDMF. |
---|
2099 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:1]) |
---|
2100 | + self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2101 | + |
---|
2102 | + # Now we're telling it more, but still not enough to flesh out |
---|
2103 | + # the rest of the encoding parameter, so none of them should be |
---|
2104 | + # set. |
---|
2105 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:10]) |
---|
2106 | + self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2107 | + self.failIf(mr._sequence_number) |
---|
2108 | + |
---|
2109 | + # This should be enough to flesh out the encoding parameters of |
---|
2110 | + # an MDMF file. |
---|
2111 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:91]) |
---|
2112 | + self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2113 | + self.failUnlessEqual(mr._root_hash, self.root_hash), |
---|
2114 | + self.failUnlessEqual(mr._sequence_number, 0) |
---|
2115 | + self.failUnlessEqual(mr._required_shares, 3) |
---|
2116 | + self.failUnlessEqual(mr._total_shares, 10) |
---|
2117 | + |
---|
2118 | + # This should be enough to fill in the encoding parameters and |
---|
2119 | + # a little more, but not enough to complete the offset table. |
---|
2120 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:100]) |
---|
2121 | + self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2122 | + self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2123 | + self.failUnlessEqual(mr._sequence_number, 0) |
---|
2124 | + self.failUnlessEqual(mr._required_shares, 3) |
---|
2125 | + self.failUnlessEqual(mr._total_shares, 10) |
---|
2126 | + self.failIf(mr._offsets) |
---|
2127 | + |
---|
2128 | + # This should be enough to fill in both the encoding parameters |
---|
2129 | + # and the table of offsets |
---|
2130 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:143]) |
---|
2131 | + self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2132 | + self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2133 | + self.failUnlessEqual(mr._sequence_number, 0) |
---|
2134 | + self.failUnlessEqual(mr._required_shares, 3) |
---|
2135 | + self.failUnlessEqual(mr._total_shares, 10) |
---|
2136 | + self.failUnless(mr._offsets) |
---|
2137 | + |
---|
2138 | + |
---|
2139 | + def test_read_with_prefetched_sdmf_data(self): |
---|
2140 | + sdmf_data = self.build_test_sdmf_share() |
---|
2141 | + # Feed it just enough data to check the share type |
---|
2142 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:1]) |
---|
2143 | + self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2144 | + self.failIf(mr._sequence_number) |
---|
2145 | + |
---|
2146 | + # Now feed it more data, but not enough data to populate the |
---|
2147 | + # encoding parameters. The results should be exactly the same as |
---|
2148 | + # before. |
---|
2149 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:10]) |
---|
2150 | + self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2151 | + self.failIf(mr._sequence_number) |
---|
2152 | + |
---|
2153 | + # Now feed it enough data to populate the encoding parameters |
---|
2154 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:75]) |
---|
2155 | + self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2156 | + self.failUnlessEqual(mr._sequence_number, 0) |
---|
2157 | + self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2158 | + self.failUnlessEqual(mr._required_shares, 3) |
---|
2159 | + self.failUnlessEqual(mr._total_shares, 10) |
---|
2160 | + |
---|
2161 | + # Now feed it enough data to populate the encoding parameters |
---|
2162 | + # and then some, but not enough to fill in the offset table. |
---|
2163 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:100]) |
---|
2164 | + self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2165 | + self.failUnlessEqual(mr._sequence_number, 0) |
---|
2166 | + self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2167 | + self.failUnlessEqual(mr._required_shares, 3) |
---|
2168 | + self.failUnlessEqual(mr._total_shares, 10) |
---|
2169 | + self.failIf(mr._offsets) |
---|
2170 | + |
---|
2171 | + # Now fill in the offset table. |
---|
2172 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:107]) |
---|
2173 | + self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2174 | + self.failUnlessEqual(mr._sequence_number, 0) |
---|
2175 | + self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2176 | + self.failUnlessEqual(mr._required_shares, 3) |
---|
2177 | + self.failUnlessEqual(mr._total_shares, 10) |
---|
2178 | + self.failUnless(mr._offsets) |
---|
2179 | + |
---|
2180 | + |
---|
2181 | + def test_read_with_prefetched_bogus_data(self): |
---|
2182 | + bogus_data = "kjkasdlkjsjkdjksajdjsadjsajdskaj" |
---|
2183 | + # This shouldn't do anything. |
---|
2184 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, bogus_data) |
---|
2185 | + self.failIf(mr._version_number) |
---|
2186 | + |
---|
2187 | + |
---|
2188 | + def test_read_with_empty_mdmf_file(self): |
---|
2189 | + # Some tests upload a file with no contents to test things |
---|
2190 | + # unrelated to the actual handling of the content of the file. |
---|
2191 | + # The reader should behave intelligently in these cases. |
---|
2192 | + self.write_test_share_to_server("si1", empty=True) |
---|
2193 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
2194 | + # We should be able to get the encoding parameters, and they |
---|
2195 | + # should be correct. |
---|
2196 | + d = defer.succeed(None) |
---|
2197 | + d.addCallback(lambda ignored: |
---|
2198 | + mr.get_encoding_parameters()) |
---|
2199 | + def _check_encoding_parameters(params): |
---|
2200 | + self.failUnlessEqual(len(params), 4) |
---|
2201 | + k, n, segsize, datalen = params |
---|
2202 | + self.failUnlessEqual(k, 3) |
---|
2203 | + self.failUnlessEqual(n, 10) |
---|
2204 | + self.failUnlessEqual(segsize, 0) |
---|
2205 | + self.failUnlessEqual(datalen, 0) |
---|
2206 | + d.addCallback(_check_encoding_parameters) |
---|
2207 | + |
---|
2208 | + # We should not be able to fetch a block, since there are no |
---|
2209 | + # blocks to fetch |
---|
2210 | + d.addCallback(lambda ignored: |
---|
2211 | + self.shouldFail(LayoutInvalid, "get block on empty file", |
---|
2212 | + None, |
---|
2213 | + mr.get_block_and_salt, 0)) |
---|
2214 | + return d |
---|
2215 | + |
---|
2216 | + |
---|
2217 | + def test_read_with_empty_sdmf_file(self): |
---|
2218 | + self.write_sdmf_share_to_server("si1", empty=True) |
---|
2219 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
2220 | + # We should be able to get the encoding parameters, and they |
---|
2221 | + # should be correct |
---|
2222 | + d = defer.succeed(None) |
---|
2223 | + d.addCallback(lambda ignored: |
---|
2224 | + mr.get_encoding_parameters()) |
---|
2225 | + def _check_encoding_parameters(params): |
---|
2226 | + self.failUnlessEqual(len(params), 4) |
---|
2227 | + k, n, segsize, datalen = params |
---|
2228 | + self.failUnlessEqual(k, 3) |
---|
2229 | + self.failUnlessEqual(n, 10) |
---|
2230 | + self.failUnlessEqual(segsize, 0) |
---|
2231 | + self.failUnlessEqual(datalen, 0) |
---|
2232 | + d.addCallback(_check_encoding_parameters) |
---|
2233 | + |
---|
2234 | + # It does not make sense to get a block in this format, so we |
---|
2235 | + # should not be able to. |
---|
2236 | + d.addCallback(lambda ignored: |
---|
2237 | + self.shouldFail(LayoutInvalid, "get block on an empty file", |
---|
2238 | + None, |
---|
2239 | + mr.get_block_and_salt, 0)) |
---|
2240 | + return d |
---|
2241 | + |
---|
2242 | + |
---|
2243 | + def test_verinfo_with_sdmf_file(self): |
---|
2244 | + self.write_sdmf_share_to_server("si1") |
---|
2245 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
2246 | + # We should be able to get the version information. |
---|
2247 | + d = defer.succeed(None) |
---|
2248 | + d.addCallback(lambda ignored: |
---|
2249 | + mr.get_verinfo()) |
---|
2250 | + def _check_verinfo(verinfo): |
---|
2251 | + self.failUnless(verinfo) |
---|
2252 | + self.failUnlessEqual(len(verinfo), 9) |
---|
2253 | + (seqnum, |
---|
2254 | + root_hash, |
---|
2255 | + salt, |
---|
2256 | + segsize, |
---|
2257 | + datalen, |
---|
2258 | + k, |
---|
2259 | + n, |
---|
2260 | + prefix, |
---|
2261 | + offsets) = verinfo |
---|
2262 | + self.failUnlessEqual(seqnum, 0) |
---|
2263 | + self.failUnlessEqual(root_hash, self.root_hash) |
---|
2264 | + self.failUnlessEqual(salt, self.salt) |
---|
2265 | + self.failUnlessEqual(segsize, 36) |
---|
2266 | + self.failUnlessEqual(datalen, 36) |
---|
2267 | + self.failUnlessEqual(k, 3) |
---|
2268 | + self.failUnlessEqual(n, 10) |
---|
2269 | + expected_prefix = struct.pack(">BQ32s16s BBQQ", |
---|
2270 | + 0, |
---|
2271 | + seqnum, |
---|
2272 | + root_hash, |
---|
2273 | + salt, |
---|
2274 | + k, |
---|
2275 | + n, |
---|
2276 | + segsize, |
---|
2277 | + datalen) |
---|
2278 | + self.failUnlessEqual(prefix, expected_prefix) |
---|
2279 | + self.failUnlessEqual(offsets, self.offsets) |
---|
2280 | + d.addCallback(_check_verinfo) |
---|
2281 | + return d |
---|
2282 | + |
---|
2283 | + |
---|
2284 | + def test_verinfo_with_mdmf_file(self): |
---|
2285 | + self.write_test_share_to_server("si1") |
---|
2286 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
2287 | + d = defer.succeed(None) |
---|
2288 | + d.addCallback(lambda ignored: |
---|
2289 | + mr.get_verinfo()) |
---|
2290 | + def _check_verinfo(verinfo): |
---|
2291 | + self.failUnless(verinfo) |
---|
2292 | + self.failUnlessEqual(len(verinfo), 9) |
---|
2293 | + (seqnum, |
---|
2294 | + root_hash, |
---|
2295 | + salt_hash, |
---|
2296 | + segsize, |
---|
2297 | + datalen, |
---|
2298 | + k, |
---|
2299 | + n, |
---|
2300 | + prefix, |
---|
2301 | + offsets) = verinfo |
---|
2302 | + self.failUnlessEqual(seqnum, 0) |
---|
2303 | + self.failUnlessEqual(root_hash, self.root_hash) |
---|
2304 | + self.failUnlessEqual(salt_hash, self.salt_hash) |
---|
2305 | + self.failUnlessEqual(segsize, 6) |
---|
2306 | + self.failUnlessEqual(datalen, 36) |
---|
2307 | + self.failUnlessEqual(k, 3) |
---|
2308 | + self.failUnlessEqual(n, 10) |
---|
2309 | + expected_prefix = struct.pack(">BQ32s32s BBQQ", |
---|
2310 | + 1, |
---|
2311 | + seqnum, |
---|
2312 | + root_hash, |
---|
2313 | + salt_hash, |
---|
2314 | + k, |
---|
2315 | + n, |
---|
2316 | + segsize, |
---|
2317 | + datalen) |
---|
2318 | + self.failUnlessEqual(prefix, expected_prefix) |
---|
2319 | + self.failUnlessEqual(offsets, self.offsets) |
---|
2320 | + d.addCallback(_check_verinfo) |
---|
2321 | + return d |
---|
2322 | + |
---|
2323 | + |
---|
2324 | class Stats(unittest.TestCase): |
---|
2325 | |
---|
2326 | def setUp(self): |
---|
2327 | } |
---|
2328 | [Alter MDMF proxy tests to reflect the new form of caching |
---|
2329 | Kevan Carstensen <kevan@isnotajoke.com>**20100614213459 |
---|
2330 | Ignore-this: 3e84dbd1b6ea103be36e0e98babe79d4 |
---|
2331 | ] { |
---|
2332 | hunk ./src/allmydata/test/test_storage.py 23 |
---|
2333 | from allmydata.immutable.layout import WriteBucketProxy, WriteBucketProxy_v2, \ |
---|
2334 | ReadBucketProxy |
---|
2335 | from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \ |
---|
2336 | - LayoutInvalid |
---|
2337 | + LayoutInvalid, MDMFSIGNABLEHEADER, \ |
---|
2338 | + SIGNED_PREFIX |
---|
2339 | from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \ |
---|
2340 | SDMF_VERSION |
---|
2341 | from allmydata.test.common import LoggingServiceParent, ShouldFailMixin |
---|
2342 | hunk ./src/allmydata/test/test_storage.py 105 |
---|
2343 | |
---|
2344 | class RemoteBucket: |
---|
2345 | |
---|
2346 | + def __init__(self): |
---|
2347 | + self.read_count = 0 |
---|
2348 | + self.write_count = 0 |
---|
2349 | + |
---|
2350 | def callRemote(self, methname, *args, **kwargs): |
---|
2351 | def _call(): |
---|
2352 | meth = getattr(self.target, "remote_" + methname) |
---|
2353 | hunk ./src/allmydata/test/test_storage.py 113 |
---|
2354 | return meth(*args, **kwargs) |
---|
2355 | + |
---|
2356 | + if methname == "slot_readv": |
---|
2357 | + self.read_count += 1 |
---|
2358 | + if methname == "slot_writev": |
---|
2359 | + self.write_count += 1 |
---|
2360 | + |
---|
2361 | return defer.maybeDeferred(_call) |
---|
2362 | |
---|
2363 | hunk ./src/allmydata/test/test_storage.py 121 |
---|
2364 | + |
---|
2365 | class BucketProxy(unittest.TestCase): |
---|
2366 | def make_bucket(self, name, size): |
---|
2367 | basedir = os.path.join("storage", "BucketProxy", name) |
---|
2368 | hunk ./src/allmydata/test/test_storage.py 2605 |
---|
2369 | mr.get_block_and_salt(0)) |
---|
2370 | def _check_block_and_salt(results): |
---|
2371 | block, salt = results |
---|
2372 | + # Our original file is 36 bytes long. Then each share is 12 |
---|
2373 | + # bytes in size. The share is composed entirely of the |
---|
2374 | + # letter a. self.block contains 2 as, so 6 * self.block is |
---|
2375 | + # what we are looking for. |
---|
2376 | self.failUnlessEqual(block, self.block * 6) |
---|
2377 | self.failUnlessEqual(salt, self.salt) |
---|
2378 | d.addCallback(_check_block_and_salt) |
---|
2379 | hunk ./src/allmydata/test/test_storage.py 2687 |
---|
2380 | # finding out which shares are on the remote peer so that it |
---|
2381 | # doesn't waste round trips. |
---|
2382 | mdmf_data = self.build_test_mdmf_share() |
---|
2383 | - # We're telling it enough to figure out whether it is SDMF or |
---|
2384 | - # MDMF. |
---|
2385 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:1]) |
---|
2386 | - self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2387 | - |
---|
2388 | - # Now we're telling it more, but still not enough to flesh out |
---|
2389 | - # the rest of the encoding parameter, so none of them should be |
---|
2390 | - # set. |
---|
2391 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:10]) |
---|
2392 | - self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2393 | - self.failIf(mr._sequence_number) |
---|
2394 | - |
---|
2395 | - # This should be enough to flesh out the encoding parameters of |
---|
2396 | - # an MDMF file. |
---|
2397 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:91]) |
---|
2398 | - self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2399 | - self.failUnlessEqual(mr._root_hash, self.root_hash), |
---|
2400 | - self.failUnlessEqual(mr._sequence_number, 0) |
---|
2401 | - self.failUnlessEqual(mr._required_shares, 3) |
---|
2402 | - self.failUnlessEqual(mr._total_shares, 10) |
---|
2403 | - |
---|
2404 | - # This should be enough to fill in the encoding parameters and |
---|
2405 | - # a little more, but not enough to complete the offset table. |
---|
2406 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:100]) |
---|
2407 | - self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2408 | - self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2409 | - self.failUnlessEqual(mr._sequence_number, 0) |
---|
2410 | - self.failUnlessEqual(mr._required_shares, 3) |
---|
2411 | - self.failUnlessEqual(mr._total_shares, 10) |
---|
2412 | - self.failIf(mr._offsets) |
---|
2413 | + self.write_test_share_to_server("si1") |
---|
2414 | + def _make_mr(ignored, length): |
---|
2415 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:length]) |
---|
2416 | + return mr |
---|
2417 | |
---|
2418 | hunk ./src/allmydata/test/test_storage.py 2692 |
---|
2419 | + d = defer.succeed(None) |
---|
2420 | # This should be enough to fill in both the encoding parameters |
---|
2421 | hunk ./src/allmydata/test/test_storage.py 2694 |
---|
2422 | - # and the table of offsets |
---|
2423 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, mdmf_data[:143]) |
---|
2424 | - self.failUnlessEqual(mr._version_number, MDMF_VERSION) |
---|
2425 | - self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2426 | - self.failUnlessEqual(mr._sequence_number, 0) |
---|
2427 | - self.failUnlessEqual(mr._required_shares, 3) |
---|
2428 | - self.failUnlessEqual(mr._total_shares, 10) |
---|
2429 | - self.failUnless(mr._offsets) |
---|
2430 | + # and the table of offsets, which will complete the version |
---|
2431 | + # information tuple. |
---|
2432 | + d.addCallback(_make_mr, 143) |
---|
2433 | + d.addCallback(lambda mr: |
---|
2434 | + mr.get_verinfo()) |
---|
2435 | + def _check_verinfo(verinfo): |
---|
2436 | + self.failUnless(verinfo) |
---|
2437 | + self.failUnlessEqual(len(verinfo), 9) |
---|
2438 | + (seqnum, |
---|
2439 | + root_hash, |
---|
2440 | + salt_hash, |
---|
2441 | + segsize, |
---|
2442 | + datalen, |
---|
2443 | + k, |
---|
2444 | + n, |
---|
2445 | + prefix, |
---|
2446 | + offsets) = verinfo |
---|
2447 | + self.failUnlessEqual(seqnum, 0) |
---|
2448 | + self.failUnlessEqual(root_hash, self.root_hash) |
---|
2449 | + self.failUnlessEqual(salt_hash, self.salt_hash) |
---|
2450 | + self.failUnlessEqual(segsize, 6) |
---|
2451 | + self.failUnlessEqual(datalen, 36) |
---|
2452 | + self.failUnlessEqual(k, 3) |
---|
2453 | + self.failUnlessEqual(n, 10) |
---|
2454 | + expected_prefix = struct.pack(MDMFSIGNABLEHEADER, |
---|
2455 | + 1, |
---|
2456 | + seqnum, |
---|
2457 | + root_hash, |
---|
2458 | + salt_hash, |
---|
2459 | + k, |
---|
2460 | + n, |
---|
2461 | + segsize, |
---|
2462 | + datalen) |
---|
2463 | + self.failUnlessEqual(expected_prefix, prefix) |
---|
2464 | + self.failUnlessEqual(self.rref.read_count, 0) |
---|
2465 | + d.addCallback(_check_verinfo) |
---|
2466 | + # This is not enough data to read a block and a share, so the |
---|
2467 | + # wrapper should attempt to read this from the remote server. |
---|
2468 | + d.addCallback(_make_mr, 143) |
---|
2469 | + d.addCallback(lambda mr: |
---|
2470 | + mr.get_block_and_salt(0)) |
---|
2471 | + def _check_block_and_salt((block, salt)): |
---|
2472 | + self.failUnlessEqual(block, self.block) |
---|
2473 | + self.failUnlessEqual(salt, self.salt) |
---|
2474 | + self.failUnlessEqual(self.rref.read_count, 1) |
---|
2475 | + # The file that we're playing with has 6 segments. Then there |
---|
2476 | + # are 6 * 16 = 96 bytes of salts before we can write shares. |
---|
2477 | + # Each block has two bytes, so 143 + 96 + 2 = 241 bytes should |
---|
2478 | + # be enough to read one block. |
---|
2479 | + d.addCallback(_make_mr, 241) |
---|
2480 | + d.addCallback(lambda mr: |
---|
2481 | + mr.get_block_and_salt(0)) |
---|
2482 | + d.addCallback(_check_block_and_salt) |
---|
2483 | + return d |
---|
2484 | |
---|
2485 | |
---|
2486 | def test_read_with_prefetched_sdmf_data(self): |
---|
2487 | hunk ./src/allmydata/test/test_storage.py 2752 |
---|
2488 | sdmf_data = self.build_test_sdmf_share() |
---|
2489 | - # Feed it just enough data to check the share type |
---|
2490 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:1]) |
---|
2491 | - self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2492 | - self.failIf(mr._sequence_number) |
---|
2493 | - |
---|
2494 | - # Now feed it more data, but not enough data to populate the |
---|
2495 | - # encoding parameters. The results should be exactly the same as |
---|
2496 | - # before. |
---|
2497 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:10]) |
---|
2498 | - self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2499 | - self.failIf(mr._sequence_number) |
---|
2500 | - |
---|
2501 | - # Now feed it enough data to populate the encoding parameters |
---|
2502 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:75]) |
---|
2503 | - self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2504 | - self.failUnlessEqual(mr._sequence_number, 0) |
---|
2505 | - self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2506 | - self.failUnlessEqual(mr._required_shares, 3) |
---|
2507 | - self.failUnlessEqual(mr._total_shares, 10) |
---|
2508 | - |
---|
2509 | - # Now feed it enough data to populate the encoding parameters |
---|
2510 | - # and then some, but not enough to fill in the offset table. |
---|
2511 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:100]) |
---|
2512 | - self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2513 | - self.failUnlessEqual(mr._sequence_number, 0) |
---|
2514 | - self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2515 | - self.failUnlessEqual(mr._required_shares, 3) |
---|
2516 | - self.failUnlessEqual(mr._total_shares, 10) |
---|
2517 | - self.failIf(mr._offsets) |
---|
2518 | - |
---|
2519 | - # Now fill in the offset table. |
---|
2520 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:107]) |
---|
2521 | - self.failUnlessEqual(mr._version_number, SDMF_VERSION) |
---|
2522 | - self.failUnlessEqual(mr._sequence_number, 0) |
---|
2523 | - self.failUnlessEqual(mr._root_hash, self.root_hash) |
---|
2524 | - self.failUnlessEqual(mr._required_shares, 3) |
---|
2525 | - self.failUnlessEqual(mr._total_shares, 10) |
---|
2526 | - self.failUnless(mr._offsets) |
---|
2527 | + self.write_sdmf_share_to_server("si1") |
---|
2528 | + def _make_mr(ignored, length): |
---|
2529 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0, sdmf_data[:length]) |
---|
2530 | + return mr |
---|
2531 | |
---|
2532 | hunk ./src/allmydata/test/test_storage.py 2757 |
---|
2533 | + d = defer.succeed(None) |
---|
2534 | + # This should be enough to get us the encoding parameters, |
---|
2535 | + # offset table, and everything else we need to build a verinfo |
---|
2536 | + # string. |
---|
2537 | + d.addCallback(_make_mr, 107) |
---|
2538 | + d.addCallback(lambda mr: |
---|
2539 | + mr.get_verinfo()) |
---|
2540 | + def _check_verinfo(verinfo): |
---|
2541 | + self.failUnless(verinfo) |
---|
2542 | + self.failUnlessEqual(len(verinfo), 9) |
---|
2543 | + (seqnum, |
---|
2544 | + root_hash, |
---|
2545 | + salt, |
---|
2546 | + segsize, |
---|
2547 | + datalen, |
---|
2548 | + k, |
---|
2549 | + n, |
---|
2550 | + prefix, |
---|
2551 | + offsets) = verinfo |
---|
2552 | + self.failUnlessEqual(seqnum, 0) |
---|
2553 | + self.failUnlessEqual(root_hash, self.root_hash) |
---|
2554 | + self.failUnlessEqual(salt, self.salt) |
---|
2555 | + self.failUnlessEqual(segsize, 36) |
---|
2556 | + self.failUnlessEqual(datalen, 36) |
---|
2557 | + self.failUnlessEqual(k, 3) |
---|
2558 | + self.failUnlessEqual(n, 10) |
---|
2559 | + expected_prefix = struct.pack(SIGNED_PREFIX, |
---|
2560 | + 0, |
---|
2561 | + seqnum, |
---|
2562 | + root_hash, |
---|
2563 | + salt, |
---|
2564 | + k, |
---|
2565 | + n, |
---|
2566 | + segsize, |
---|
2567 | + datalen) |
---|
2568 | + self.failUnlessEqual(expected_prefix, prefix) |
---|
2569 | + self.failUnlessEqual(self.rref.read_count, 0) |
---|
2570 | + d.addCallback(_check_verinfo) |
---|
2571 | + # This shouldn't be enough to read any share data. |
---|
2572 | + d.addCallback(_make_mr, 107) |
---|
2573 | + d.addCallback(lambda mr: |
---|
2574 | + mr.get_block_and_salt(0)) |
---|
2575 | + def _check_block_and_salt((block, salt)): |
---|
2576 | + self.failUnlessEqual(block, self.block * 6) |
---|
2577 | + self.failUnlessEqual(salt, self.salt) |
---|
2578 | + # TODO: Fix the read routine so that it reads only the data |
---|
2579 | + # that it has cached if it can't read all of it. |
---|
2580 | + self.failUnlessEqual(self.rref.read_count, 2) |
---|
2581 | |
---|
2582 | hunk ./src/allmydata/test/test_storage.py 2806 |
---|
2583 | - def test_read_with_prefetched_bogus_data(self): |
---|
2584 | - bogus_data = "kjkasdlkjsjkdjksajdjsadjsajdskaj" |
---|
2585 | - # This shouldn't do anything. |
---|
2586 | - mr = MDMFSlotReadProxy(self.rref, "si1", 0, bogus_data) |
---|
2587 | - self.failIf(mr._version_number) |
---|
2588 | + # This should be enough to read share data. |
---|
2589 | + d.addCallback(_make_mr, self.offsets['share_data']) |
---|
2590 | + d.addCallback(lambda mr: |
---|
2591 | + mr.get_block_and_salt(0)) |
---|
2592 | + d.addCallback(_check_block_and_salt) |
---|
2593 | + return d |
---|
2594 | |
---|
2595 | |
---|
2596 | def test_read_with_empty_mdmf_file(self): |
---|
2597 | } |
---|
2598 | [Add tests and support functions for servermap tests |
---|
2599 | Kevan Carstensen <kevan@isnotajoke.com>**20100614213721 |
---|
2600 | Ignore-this: 583734d2f728fc80637b5c0c0f4c0fc |
---|
2601 | ] { |
---|
2602 | hunk ./src/allmydata/test/test_mutable.py 103 |
---|
2603 | d = fireEventually() |
---|
2604 | d.addCallback(lambda res: _call()) |
---|
2605 | return d |
---|
2606 | + |
---|
2607 | def callRemoteOnly(self, methname, *args, **kwargs): |
---|
2608 | d = self.callRemote(methname, *args, **kwargs) |
---|
2609 | d.addBoth(lambda ignore: None) |
---|
2610 | hunk ./src/allmydata/test/test_mutable.py 152 |
---|
2611 | chr(ord(original[byte_offset]) ^ 0x01) + |
---|
2612 | original[byte_offset+1:]) |
---|
2613 | |
---|
2614 | +def add_two(original, byte_offset): |
---|
2615 | + # It isn't enough to simply flip the bit for the version number, |
---|
2616 | + # because 1 is a valid version number. So we add two instead. |
---|
2617 | + return (original[:byte_offset] + |
---|
2618 | + chr(ord(original[byte_offset]) ^ 0x02) + |
---|
2619 | + original[byte_offset+1:]) |
---|
2620 | + |
---|
2621 | def corrupt(res, s, offset, shnums_to_corrupt=None, offset_offset=0): |
---|
2622 | # if shnums_to_corrupt is None, corrupt all shares. Otherwise it is a |
---|
2623 | # list of shnums to corrupt. |
---|
2624 | hunk ./src/allmydata/test/test_mutable.py 188 |
---|
2625 | real_offset = offset1 |
---|
2626 | real_offset = int(real_offset) + offset2 + offset_offset |
---|
2627 | assert isinstance(real_offset, int), offset |
---|
2628 | - shares[shnum] = flip_bit(data, real_offset) |
---|
2629 | + if offset1 == 0: # verbyte |
---|
2630 | + f = add_two |
---|
2631 | + else: |
---|
2632 | + f = flip_bit |
---|
2633 | + shares[shnum] = f(data, real_offset) |
---|
2634 | return res |
---|
2635 | |
---|
2636 | def make_storagebroker(s=None, num_peers=10): |
---|
2637 | hunk ./src/allmydata/test/test_mutable.py 625 |
---|
2638 | d.addCallback(_created) |
---|
2639 | return d |
---|
2640 | |
---|
2641 | - def publish_multiple(self): |
---|
2642 | + def publish_mdmf(self): |
---|
2643 | + # like publish_one, except that the result is guaranteed to be |
---|
2644 | + # an MDMF file. |
---|
2645 | + # self.CONTENTS should have more than one segment. |
---|
2646 | + self.CONTENTS = "This is an MDMF file" * 100000 |
---|
2647 | + self._storage = FakeStorage() |
---|
2648 | + self._nodemaker = make_nodemaker(self._storage) |
---|
2649 | + self._storage_broker = self._nodemaker.storage_broker |
---|
2650 | + d = self._nodemaker.create_mutable_file(self.CONTENTS, version=1) |
---|
2651 | + def _created(node): |
---|
2652 | + self._fn = node |
---|
2653 | + self._fn2 = self._nodemaker.create_from_cap(node.get_uri()) |
---|
2654 | + d.addCallback(_created) |
---|
2655 | + return d |
---|
2656 | + |
---|
2657 | + |
---|
2658 | + def publish_sdmf(self): |
---|
2659 | + # like publish_one, except that the result is guaranteed to be |
---|
2660 | + # an SDMF file |
---|
2661 | + self.CONTENTS = "This is an SDMF file" * 1000 |
---|
2662 | + self._storage = FakeStorage() |
---|
2663 | + self._nodemaker = make_nodemaker(self._storage) |
---|
2664 | + self._storage_broker = self._nodemaker.storage_broker |
---|
2665 | + d = self._nodemaker.create_mutable_file(self.CONTENTS, version=0) |
---|
2666 | + def _created(node): |
---|
2667 | + self._fn = node |
---|
2668 | + self._fn2 = self._nodemaker.create_from_cap(node.get_uri()) |
---|
2669 | + d.addCallback(_created) |
---|
2670 | + return d |
---|
2671 | + |
---|
2672 | + |
---|
2673 | + def publish_multiple(self, version=0): |
---|
2674 | self.CONTENTS = ["Contents 0", |
---|
2675 | "Contents 1", |
---|
2676 | "Contents 2", |
---|
2677 | hunk ./src/allmydata/test/test_mutable.py 665 |
---|
2678 | self._copied_shares = {} |
---|
2679 | self._storage = FakeStorage() |
---|
2680 | self._nodemaker = make_nodemaker(self._storage) |
---|
2681 | - d = self._nodemaker.create_mutable_file(self.CONTENTS[0]) # seqnum=1 |
---|
2682 | + d = self._nodemaker.create_mutable_file(self.CONTENTS[0], version=version) # seqnum=1 |
---|
2683 | def _created(node): |
---|
2684 | self._fn = node |
---|
2685 | # now create multiple versions of the same file, and accumulate |
---|
2686 | hunk ./src/allmydata/test/test_mutable.py 689 |
---|
2687 | d.addCallback(_created) |
---|
2688 | return d |
---|
2689 | |
---|
2690 | + |
---|
2691 | def _copy_shares(self, ignored, index): |
---|
2692 | shares = self._storage._peers |
---|
2693 | # we need a deep copy |
---|
2694 | hunk ./src/allmydata/test/test_mutable.py 842 |
---|
2695 | self._storage._peers = {} # delete all shares |
---|
2696 | ms = self.make_servermap |
---|
2697 | d = defer.succeed(None) |
---|
2698 | - |
---|
2699 | +# |
---|
2700 | d.addCallback(lambda res: ms(mode=MODE_CHECK)) |
---|
2701 | d.addCallback(lambda sm: self.failUnlessNoneRecoverable(sm)) |
---|
2702 | |
---|
2703 | hunk ./src/allmydata/test/test_mutable.py 894 |
---|
2704 | return d |
---|
2705 | |
---|
2706 | |
---|
2707 | + def test_servermapupdater_finds_mdmf_files(self): |
---|
2708 | + # setUp already published an MDMF file for us. We just need to |
---|
2709 | + # make sure that when we run the ServermapUpdater, the file is |
---|
2710 | + # reported to have one recoverable version. |
---|
2711 | + d = defer.succeed(None) |
---|
2712 | + d.addCallback(lambda ignored: |
---|
2713 | + self.publish_mdmf()) |
---|
2714 | + d.addCallback(lambda ignored: |
---|
2715 | + self.make_servermap(mode=MODE_CHECK)) |
---|
2716 | + # Calling make_servermap also updates the servermap in the mode |
---|
2717 | + # that we specify, so we just need to see what it says. |
---|
2718 | + def _check_servermap(sm): |
---|
2719 | + self.failUnlessEqual(len(sm.recoverable_versions()), 1) |
---|
2720 | + d.addCallback(_check_servermap) |
---|
2721 | + # Now, we upload more versions |
---|
2722 | + d.addCallback(lambda ignored: |
---|
2723 | + self.publish_multiple(version=1)) |
---|
2724 | + d.addCallback(lambda ignored: |
---|
2725 | + self.make_servermap(mode=MODE_CHECK)) |
---|
2726 | + def _check_servermap_multiple(sm): |
---|
2727 | + v = sm.recoverable_versions() |
---|
2728 | + i = sm.unrecoverable_versions() |
---|
2729 | + d.addCallback(_check_servermap_multiple) |
---|
2730 | + return d |
---|
2731 | + test_servermapupdater_finds_mdmf_files.todo = ("I don't know how to " |
---|
2732 | + "write this yet") |
---|
2733 | + |
---|
2734 | + |
---|
2735 | + def test_servermapupdater_finds_sdmf_files(self): |
---|
2736 | + d = defer.succeed(None) |
---|
2737 | + d.addCallback(lambda ignored: |
---|
2738 | + self.publish_sdmf()) |
---|
2739 | + d.addCallback(lambda ignored: |
---|
2740 | + self.make_servermap(mode=MODE_CHECK)) |
---|
2741 | + d.addCallback(lambda servermap: |
---|
2742 | + self.failUnlessEqual(len(servermap.recoverable_versions()), 1)) |
---|
2743 | + return d |
---|
2744 | + |
---|
2745 | |
---|
2746 | class Roundtrip(unittest.TestCase, testutil.ShouldFailMixin, PublishMixin): |
---|
2747 | def setUp(self): |
---|
2748 | hunk ./src/allmydata/test/test_mutable.py 1084 |
---|
2749 | return d |
---|
2750 | |
---|
2751 | def test_corrupt_all_verbyte(self): |
---|
2752 | - # when the version byte is not 0, we hit an UnknownVersionError error |
---|
2753 | - # in unpack_share(). |
---|
2754 | + # when the version byte is not 0 or 1, we hit an UnknownVersionError |
---|
2755 | + # error in unpack_share(). |
---|
2756 | d = self._test_corrupt_all(0, "UnknownVersionError") |
---|
2757 | def _check_servermap(servermap): |
---|
2758 | # and the dump should mention the problems |
---|
2759 | hunk ./src/allmydata/test/test_mutable.py 1091 |
---|
2760 | s = StringIO() |
---|
2761 | dump = servermap.dump(s).getvalue() |
---|
2762 | - self.failUnless("10 PROBLEMS" in dump, dump) |
---|
2763 | + self.failUnless("30 PROBLEMS" in dump, dump) |
---|
2764 | d.addCallback(_check_servermap) |
---|
2765 | return d |
---|
2766 | |
---|
2767 | hunk ./src/allmydata/test/test_mutable.py 2153 |
---|
2768 | self.basedir = "mutable/Problems/test_privkey_query_missing" |
---|
2769 | self.set_up_grid(num_servers=20) |
---|
2770 | nm = self.g.clients[0].nodemaker |
---|
2771 | - LARGE = "These are Larger contents" * 2000 # about 50KB |
---|
2772 | + LARGE = "These are Larger contents" * 2000 # about 50KiB |
---|
2773 | nm._node_cache = DevNullDictionary() # disable the nodecache |
---|
2774 | |
---|
2775 | d = nm.create_mutable_file(LARGE) |
---|
2776 | } |
---|
2777 | [Make a segmented downloader |
---|
2778 | Kevan Carstensen <kevan@isnotajoke.com>**20100623001332 |
---|
2779 | Ignore-this: f3543532a5d573cc884c17a4ebbf451e |
---|
2780 | |
---|
2781 | Rework the current mutable file Retrieve class to download segmented |
---|
2782 | files. The rewrite preserves the semantics and basic conceptual state |
---|
2783 | machine of the old Retrieve class, but adapts them to work with |
---|
2784 | files with more than one segment, which involves a fairly substantial |
---|
2785 | rewrite. |
---|
2786 | |
---|
2787 | I've also adapted some existing SDMF tests to work with the new |
---|
2788 | downloader, as necessary. |
---|
2789 | |
---|
2790 | TODO: |
---|
2791 | - Write tests for MDMF functionality. |
---|
2792 | - Finish writing and testing salt functionality |
---|
2793 | ] { |
---|
2794 | hunk ./src/allmydata/mutable/retrieve.py 9 |
---|
2795 | from twisted.python import failure |
---|
2796 | from foolscap.api import DeadReferenceError, eventually, fireEventually |
---|
2797 | from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError |
---|
2798 | -from allmydata.util import hashutil, idlib, log |
---|
2799 | +from allmydata.util import hashutil, idlib, log, mathutil |
---|
2800 | from allmydata import hashtree, codec |
---|
2801 | from allmydata.storage.server import si_b2a |
---|
2802 | from pycryptopp.cipher.aes import AES |
---|
2803 | hunk ./src/allmydata/mutable/retrieve.py 16 |
---|
2804 | from pycryptopp.publickey import rsa |
---|
2805 | |
---|
2806 | from allmydata.mutable.common import DictOfSets, CorruptShareError, UncoordinatedWriteError |
---|
2807 | -from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data |
---|
2808 | +from allmydata.mutable.layout import SIGNED_PREFIX, unpack_share_data, \ |
---|
2809 | + MDMFSlotReadProxy |
---|
2810 | |
---|
2811 | class RetrieveStatus: |
---|
2812 | implements(IRetrieveStatus) |
---|
2813 | hunk ./src/allmydata/mutable/retrieve.py 103 |
---|
2814 | self.verinfo = verinfo |
---|
2815 | # during repair, we may be called upon to grab the private key, since |
---|
2816 | # it wasn't picked up during a verify=False checker run, and we'll |
---|
2817 | - # need it for repair to generate the a new version. |
---|
2818 | + # need it for repair to generate a new version. |
---|
2819 | self._need_privkey = fetch_privkey |
---|
2820 | if self._node.get_privkey(): |
---|
2821 | self._need_privkey = False |
---|
2822 | hunk ./src/allmydata/mutable/retrieve.py 108 |
---|
2823 | |
---|
2824 | + if self._need_privkey: |
---|
2825 | + # TODO: Evaluate the need for this. We'll use it if we want |
---|
2826 | + # to limit how many queries are on the wire for the privkey |
---|
2827 | + # at once. |
---|
2828 | + self._privkey_query_markers = [] # one Marker for each time we've |
---|
2829 | + # tried to get the privkey. |
---|
2830 | + |
---|
2831 | self._status = RetrieveStatus() |
---|
2832 | self._status.set_storage_index(self._storage_index) |
---|
2833 | self._status.set_helper(False) |
---|
2834 | hunk ./src/allmydata/mutable/retrieve.py 124 |
---|
2835 | offsets_tuple) = self.verinfo |
---|
2836 | self._status.set_size(datalength) |
---|
2837 | self._status.set_encoding(k, N) |
---|
2838 | + self.readers = {} |
---|
2839 | |
---|
2840 | def get_status(self): |
---|
2841 | return self._status |
---|
2842 | hunk ./src/allmydata/mutable/retrieve.py 148 |
---|
2843 | self.remaining_sharemap = DictOfSets() |
---|
2844 | for (shnum, peerid, timestamp) in shares: |
---|
2845 | self.remaining_sharemap.add(shnum, peerid) |
---|
2846 | + # If the servermap update fetched anything, it fetched at least 1 |
---|
2847 | + # KiB, so we ask for that much. |
---|
2848 | + # TODO: Change the cache methods to allow us to fetch all of the |
---|
2849 | + # data that they have, then change this method to do that. |
---|
2850 | + any_cache, timestamp = self._node._read_from_cache(self.verinfo, |
---|
2851 | + shnum, |
---|
2852 | + 0, |
---|
2853 | + 1000) |
---|
2854 | + ss = self.servermap.connections[peerid] |
---|
2855 | + reader = MDMFSlotReadProxy(ss, |
---|
2856 | + self._storage_index, |
---|
2857 | + shnum, |
---|
2858 | + any_cache) |
---|
2859 | + reader.peerid = peerid |
---|
2860 | + self.readers[shnum] = reader |
---|
2861 | + |
---|
2862 | |
---|
2863 | self.shares = {} # maps shnum to validated blocks |
---|
2864 | hunk ./src/allmydata/mutable/retrieve.py 166 |
---|
2865 | + self._active_readers = [] # list of active readers for this dl. |
---|
2866 | + self._validated_readers = set() # set of readers that we have |
---|
2867 | + # validated the prefix of |
---|
2868 | + self._block_hash_trees = {} # shnum => hashtree |
---|
2869 | + # TODO: Make this into a file-backed consumer or something to |
---|
2870 | + # conserve memory. |
---|
2871 | + self._plaintext = "" |
---|
2872 | |
---|
2873 | # how many shares do we need? |
---|
2874 | hunk ./src/allmydata/mutable/retrieve.py 175 |
---|
2875 | - (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
2876 | + (seqnum, |
---|
2877 | + root_hash, |
---|
2878 | + IV, |
---|
2879 | + segsize, |
---|
2880 | + datalength, |
---|
2881 | + k, |
---|
2882 | + N, |
---|
2883 | + prefix, |
---|
2884 | offsets_tuple) = self.verinfo |
---|
2885 | hunk ./src/allmydata/mutable/retrieve.py 184 |
---|
2886 | - assert len(self.remaining_sharemap) >= k |
---|
2887 | - # we start with the lowest shnums we have available, since FEC is |
---|
2888 | - # faster if we're using "primary shares" |
---|
2889 | - self.active_shnums = set(sorted(self.remaining_sharemap.keys())[:k]) |
---|
2890 | - for shnum in self.active_shnums: |
---|
2891 | - # we use an arbitrary peer who has the share. If shares are |
---|
2892 | - # doubled up (more than one share per peer), we could make this |
---|
2893 | - # run faster by spreading the load among multiple peers. But the |
---|
2894 | - # algorithm to do that is more complicated than I want to write |
---|
2895 | - # right now, and a well-provisioned grid shouldn't have multiple |
---|
2896 | - # shares per peer. |
---|
2897 | - peerid = list(self.remaining_sharemap[shnum])[0] |
---|
2898 | - self.get_data(shnum, peerid) |
---|
2899 | |
---|
2900 | hunk ./src/allmydata/mutable/retrieve.py 185 |
---|
2901 | - # control flow beyond this point: state machine. Receiving responses |
---|
2902 | - # from queries is the input. We might send out more queries, or we |
---|
2903 | - # might produce a result. |
---|
2904 | |
---|
2905 | hunk ./src/allmydata/mutable/retrieve.py 186 |
---|
2906 | + # We need one share hash tree for the entire file; its leaves |
---|
2907 | + # are the roots of the block hash trees for the shares that |
---|
2908 | + # comprise it, and its root is in the verinfo. |
---|
2909 | + self.share_hash_tree = hashtree.IncompleteHashTree(N) |
---|
2910 | + self.share_hash_tree.set_hashes({0: root_hash}) |
---|
2911 | + |
---|
2912 | + # This will set up both the segment decoder and the tail segment |
---|
2913 | + # decoder, as well as a variety of other instance variables that |
---|
2914 | + # the download process will use. |
---|
2915 | + self._setup_encoding_parameters() |
---|
2916 | + assert len(self.remaining_sharemap) >= k |
---|
2917 | + |
---|
2918 | + self.log("starting download") |
---|
2919 | + self._add_active_peers() |
---|
2920 | + # The download process beyond this is a state machine. |
---|
2921 | + # _add_active_peers will select the peers that we want to use |
---|
2922 | + # for the download, and then attempt to start downloading. After |
---|
2923 | + # each segment, it will check for doneness, reacting to broken |
---|
2924 | + # peers and corrupt shares as necessary. If it runs out of good |
---|
2925 | + # peers before downloading all of the segments, _done_deferred |
---|
2926 | + # will errback. Otherwise, it will eventually callback with the |
---|
2927 | + # contents of the mutable file. |
---|
2928 | return self._done_deferred |
---|
2929 | |
---|
2930 | hunk ./src/allmydata/mutable/retrieve.py 210 |
---|
2931 | - def get_data(self, shnum, peerid): |
---|
2932 | - self.log(format="sending sh#%(shnum)d request to [%(peerid)s]", |
---|
2933 | - shnum=shnum, |
---|
2934 | - peerid=idlib.shortnodeid_b2a(peerid), |
---|
2935 | - level=log.NOISY) |
---|
2936 | - ss = self.servermap.connections[peerid] |
---|
2937 | - started = time.time() |
---|
2938 | - (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
2939 | + |
---|
2940 | + def _setup_encoding_parameters(self): |
---|
2941 | + """ |
---|
2942 | + I set up the encoding parameters, including k, n, the number |
---|
2943 | + of segments associated with this file, and the segment decoder. |
---|
2944 | + I do not set the tail segment decoder, which is set in the |
---|
2945 | + method that decodes the tail segment, as it is single-use. |
---|
2946 | + """ |
---|
2947 | + # XXX: Or is it? What if servers fail in the last step? |
---|
2948 | + (seqnum, |
---|
2949 | + root_hash, |
---|
2950 | + IV, |
---|
2951 | + segsize, |
---|
2952 | + datalength, |
---|
2953 | + k, |
---|
2954 | + n, |
---|
2955 | + known_prefix, |
---|
2956 | offsets_tuple) = self.verinfo |
---|
2957 | hunk ./src/allmydata/mutable/retrieve.py 228 |
---|
2958 | - offsets = dict(offsets_tuple) |
---|
2959 | + self._required_shares = k |
---|
2960 | + self._total_shares = n |
---|
2961 | + self._segment_size = segsize |
---|
2962 | + self._data_length = datalength |
---|
2963 | + if datalength and segsize: |
---|
2964 | + self._num_segments = mathutil.div_ceil(datalength, segsize) |
---|
2965 | + self._tail_data_size = datalength % segsize |
---|
2966 | + else: |
---|
2967 | + self._num_segments = 0 |
---|
2968 | + self._tail_data_size = 0 |
---|
2969 | + |
---|
2970 | + self._segment_decoder = codec.CRSDecoder() |
---|
2971 | + self._segment_decoder.set_params(segsize, k, n) |
---|
2972 | + self._current_segment = 0 |
---|
2973 | + |
---|
2974 | + if not self._tail_data_size: |
---|
2975 | + self._tail_data_size = segsize |
---|
2976 | |
---|
2977 | hunk ./src/allmydata/mutable/retrieve.py 246 |
---|
2978 | - # we read the checkstring, to make sure that the data we grab is from |
---|
2979 | - # the right version. |
---|
2980 | - readv = [ (0, struct.calcsize(SIGNED_PREFIX)) ] |
---|
2981 | + self._tail_segment_size = mathutil.next_multiple(self._tail_data_size, |
---|
2982 | + self._required_shares) |
---|
2983 | + if self._tail_segment_size == self._segment_size: |
---|
2984 | + self._tail_decoder = self._segment_decoder |
---|
2985 | + else: |
---|
2986 | + self._tail_decoder = codec.CRSDecoder() |
---|
2987 | + self._tail_decoder.set_params(self._tail_segment_size, |
---|
2988 | + self._required_shares, |
---|
2989 | + self._total_shares) |
---|
2990 | + |
---|
2991 | + self.log("got encoding parameters: " |
---|
2992 | + "k: %d " |
---|
2993 | + "n: %d " |
---|
2994 | + "%d segments of %d bytes each (%d byte tail segment)" % \ |
---|
2995 | + (k, n, self._num_segments, self._segment_size, |
---|
2996 | + self._tail_segment_size)) |
---|
2997 | + |
---|
2998 | + for i in xrange(self._total_shares): |
---|
2999 | + # So we don't have to do this later. |
---|
3000 | + self._block_hash_trees[i] = hashtree.IncompleteHashTree(self._num_segments) |
---|
3001 | |
---|
3002 | hunk ./src/allmydata/mutable/retrieve.py 267 |
---|
3003 | - # We also read the data, and the hashes necessary to validate them |
---|
3004 | - # (share_hash_chain, block_hash_tree, share_data). We don't read the |
---|
3005 | - # signature or the pubkey, since that was handled during the |
---|
3006 | - # servermap phase, and we'll be comparing the share hash chain |
---|
3007 | - # against the roothash that was validated back then. |
---|
3008 | + # If we have more than one segment, we are an SDMF file, which |
---|
3009 | + # means that we need to validate the salts as we receive them. |
---|
3010 | + self._salt_hash_tree = hashtree.IncompleteHashTree(self._num_segments) |
---|
3011 | + self._salt_hash_tree[0] = IV # from the prefix. |
---|
3012 | + |
---|
3013 | |
---|
3014 | hunk ./src/allmydata/mutable/retrieve.py 273 |
---|
3015 | - readv.append( (offsets['share_hash_chain'], |
---|
3016 | - offsets['enc_privkey'] - offsets['share_hash_chain'] ) ) |
---|
3017 | + def _add_active_peers(self): |
---|
3018 | + """ |
---|
3019 | + I populate self._active_readers with enough active readers to |
---|
3020 | + retrieve the contents of this mutable file. I am called before |
---|
3021 | + downloading starts, and (eventually) after each validation |
---|
3022 | + error, connection error, or other problem in the download. |
---|
3023 | + """ |
---|
3024 | + # TODO: It would be cool to investigate other heuristics for |
---|
3025 | + # reader selection. For instance, the cost (in time the user |
---|
3026 | + # spends waiting for their file) of selecting a really slow peer |
---|
3027 | + # that happens to have a primary share is probably more than |
---|
3028 | + # selecting a really fast peer that doesn't have a primary |
---|
3029 | + # share. Maybe the servermap could be extended to provide this |
---|
3030 | + # information; it could keep track of latency information while |
---|
3031 | + # it gathers more important data, and then this routine could |
---|
3032 | + # use that to select active readers. |
---|
3033 | + # |
---|
3034 | + # (these and other questions would be easier to answer with a |
---|
3035 | + # robust, configurable tahoe-lafs simulator, which modeled node |
---|
3036 | + # failures, differences in node speed, and other characteristics |
---|
3037 | + # that we expect storage servers to have. You could have |
---|
3038 | + # presets for really stable grids (like allmydata.com), |
---|
3039 | + # friendnets, make it easy to configure your own settings, and |
---|
3040 | + # then simulate the effect of big changes on these use cases |
---|
3041 | + # instead of just reasoning about what the effect might be. Out |
---|
3042 | + # of scope for MDMF, though.) |
---|
3043 | |
---|
3044 | hunk ./src/allmydata/mutable/retrieve.py 300 |
---|
3045 | - # if we need the private key (for repair), we also fetch that |
---|
3046 | - if self._need_privkey: |
---|
3047 | - readv.append( (offsets['enc_privkey'], |
---|
3048 | - offsets['EOF'] - offsets['enc_privkey']) ) |
---|
3049 | + # We need at least self._required_shares readers to download a |
---|
3050 | + # segment. |
---|
3051 | + needed = self._required_shares - len(self._active_readers) |
---|
3052 | + # XXX: Why don't format= log messages work here? |
---|
3053 | + self.log("adding %d peers to the active peers list" % needed) |
---|
3054 | |
---|
3055 | hunk ./src/allmydata/mutable/retrieve.py 306 |
---|
3056 | - m = Marker() |
---|
3057 | - self._outstanding_queries[m] = (peerid, shnum, started) |
---|
3058 | + # We favor lower numbered shares, since FEC is faster with |
---|
3059 | + # primary shares than with other shares, and lower-numbered |
---|
3060 | + # shares are more likely to be primary than higher numbered |
---|
3061 | + # shares. |
---|
3062 | + active_shnums = set(sorted(self.remaining_sharemap.keys())) |
---|
3063 | + active_shnums = list(active_shnums)[:needed] |
---|
3064 | + if len(active_shnums) < needed: |
---|
3065 | + # We don't have enough readers to retrieve the file; fail. |
---|
3066 | + return self._failed() |
---|
3067 | |
---|
3068 | hunk ./src/allmydata/mutable/retrieve.py 316 |
---|
3069 | - # ask the cache first |
---|
3070 | - got_from_cache = False |
---|
3071 | - datavs = [] |
---|
3072 | - for (offset, length) in readv: |
---|
3073 | - (data, timestamp) = self._node._read_from_cache(self.verinfo, shnum, |
---|
3074 | - offset, length) |
---|
3075 | - if data is not None: |
---|
3076 | - datavs.append(data) |
---|
3077 | - if len(datavs) == len(readv): |
---|
3078 | - self.log("got data from cache") |
---|
3079 | - got_from_cache = True |
---|
3080 | - d = fireEventually({shnum: datavs}) |
---|
3081 | - # datavs is a dict mapping shnum to a pair of strings |
---|
3082 | - else: |
---|
3083 | - d = self._do_read(ss, peerid, self._storage_index, [shnum], readv) |
---|
3084 | - self.remaining_sharemap.discard(shnum, peerid) |
---|
3085 | + for shnum in active_shnums: |
---|
3086 | + self._active_readers.append(self.readers[shnum]) |
---|
3087 | + self.log("added reader for share %d" % shnum) |
---|
3088 | + assert len(self._active_readers) == self._required_shares |
---|
3089 | + # Conceptually, this is part of the _add_active_peers step. It |
---|
3090 | + # validates the prefixes of newly added readers to make sure |
---|
3091 | + # that they match what we are expecting for self.verinfo. If |
---|
3092 | + # validation is successful, _validate_active_prefixes will call |
---|
3093 | + # _download_current_segment for us. If validation is |
---|
3094 | + # unsuccessful, then _validate_prefixes will remove the peer and |
---|
3095 | + # call _add_active_peers again, where we will attempt to rectify |
---|
3096 | + # the problem by choosing another peer. |
---|
3097 | + return self._validate_active_prefixes() |
---|
3098 | |
---|
3099 | hunk ./src/allmydata/mutable/retrieve.py 330 |
---|
3100 | - d.addCallback(self._got_results, m, peerid, started, got_from_cache) |
---|
3101 | - d.addErrback(self._query_failed, m, peerid) |
---|
3102 | - # errors that aren't handled by _query_failed (and errors caused by |
---|
3103 | - # _query_failed) get logged, but we still want to check for doneness. |
---|
3104 | - def _oops(f): |
---|
3105 | - self.log(format="problem in _query_failed for sh#%(shnum)d to %(peerid)s", |
---|
3106 | - shnum=shnum, |
---|
3107 | - peerid=idlib.shortnodeid_b2a(peerid), |
---|
3108 | - failure=f, |
---|
3109 | - level=log.WEIRD, umid="W0xnQA") |
---|
3110 | - d.addErrback(_oops) |
---|
3111 | - d.addBoth(self._check_for_done) |
---|
3112 | - # any error during _check_for_done means the download fails. If the |
---|
3113 | - # download is successful, _check_for_done will fire _done by itself. |
---|
3114 | - d.addErrback(self._done) |
---|
3115 | - d.addErrback(log.err) |
---|
3116 | - return d # purely for testing convenience |
---|
3117 | |
---|
3118 | hunk ./src/allmydata/mutable/retrieve.py 331 |
---|
3119 | - def _do_read(self, ss, peerid, storage_index, shnums, readv): |
---|
3120 | - # isolate the callRemote to a separate method, so tests can subclass |
---|
3121 | - # Publish and override it |
---|
3122 | - d = ss.callRemote("slot_readv", storage_index, shnums, readv) |
---|
3123 | - return d |
---|
3124 | + def _validate_active_prefixes(self): |
---|
3125 | + """ |
---|
3126 | + I check to make sure that the prefixes on the peers that I am |
---|
3127 | + currently reading from match the prefix that we want to see, as |
---|
3128 | + said in self.verinfo. |
---|
3129 | + |
---|
3130 | + If I find that all of the active peers have acceptable prefixes, |
---|
3131 | + I pass control to _download_current_segment, which will use |
---|
3132 | + those peers to do cool things. If I find that some of the active |
---|
3133 | + peers have unacceptable prefixes, I will remove them from active |
---|
3134 | + peers (and from further consideration) and call |
---|
3135 | + _add_active_peers to attempt to rectify the situation. I keep |
---|
3136 | + track of which peers I have already validated so that I don't |
---|
3137 | + need to do so again. |
---|
3138 | + """ |
---|
3139 | + assert self._active_readers, "No more active readers" |
---|
3140 | |
---|
3141 | hunk ./src/allmydata/mutable/retrieve.py 348 |
---|
3142 | - def remove_peer(self, peerid): |
---|
3143 | + ds = [] |
---|
3144 | + new_readers = set(self._active_readers) - self._validated_readers |
---|
3145 | + self.log('validating %d newly-added active readers' % len(new_readers)) |
---|
3146 | + |
---|
3147 | + for reader in new_readers: |
---|
3148 | + # We force a remote read here -- otherwise, we are relying |
---|
3149 | + # on cached data that we already verified as valid, and we |
---|
3150 | + # won't detect an uncoordinated write that has occurred |
---|
3151 | + # since the last servermap update. |
---|
3152 | + d = reader.get_prefix(force_remote=True) |
---|
3153 | + d.addCallback(self._try_to_validate_prefix, reader) |
---|
3154 | + ds.append(d) |
---|
3155 | + dl = defer.DeferredList(ds, consumeErrors=True) |
---|
3156 | + def _check_results(results): |
---|
3157 | + # Each result in results will be of the form (success, msg). |
---|
3158 | + # We don't care about msg, but success will tell us whether |
---|
3159 | + # or not the checkstring validated. If it didn't, we need to |
---|
3160 | + # remove the offending (peer,share) from our active readers, |
---|
3161 | + # and ensure that active readers is again populated. |
---|
3162 | + bad_readers = [] |
---|
3163 | + for i, result in enumerate(results): |
---|
3164 | + if not result[0]: |
---|
3165 | + reader = self._active_readers[i] |
---|
3166 | + f = result[1] |
---|
3167 | + assert isinstance(f, failure.Failure) |
---|
3168 | + |
---|
3169 | + self.log("The reader %s failed to " |
---|
3170 | + "properly validate: %s" % \ |
---|
3171 | + (reader, str(f.value))) |
---|
3172 | + bad_readers.append((reader, f)) |
---|
3173 | + else: |
---|
3174 | + reader = self._active_readers[i] |
---|
3175 | + self.log("the reader %s checks out, so we'll use it" % \ |
---|
3176 | + reader) |
---|
3177 | + self._validated_readers.add(reader) |
---|
3178 | + # Each time we validate a reader, we check to see if |
---|
3179 | + # we need the private key. If we do, we politely ask |
---|
3180 | + # for it and then continue computing. If we find |
---|
3181 | + # that we haven't gotten it at the end of |
---|
3182 | + # segment decoding, then we'll take more drastic |
---|
3183 | + # measures. |
---|
3184 | + if self._need_privkey: |
---|
3185 | + d = reader.get_encprivkey() |
---|
3186 | + d.addCallback(self._try_to_validate_privkey, reader) |
---|
3187 | + if bad_readers: |
---|
3188 | + # We do them all at once, or else we screw up list indexing. |
---|
3189 | + for (reader, f) in bad_readers: |
---|
3190 | + self._mark_bad_share(reader, f) |
---|
3191 | + return self._add_active_peers() |
---|
3192 | + else: |
---|
3193 | + return self._download_current_segment() |
---|
3194 | + # The next step will assert that it has enough active |
---|
3195 | + # readers to fetch shares; we just need to remove it. |
---|
3196 | + dl.addCallback(_check_results) |
---|
3197 | + return dl |
---|
3198 | + |
---|
3199 | + |
---|
3200 | + def _try_to_validate_prefix(self, prefix, reader): |
---|
3201 | + """ |
---|
3202 | + I check that the prefix returned by a candidate server for |
---|
3203 | + retrieval matches the prefix that the servermap knows about |
---|
3204 | + (and, hence, the prefix that was validated earlier). If it does, |
---|
3205 | + I return True, which means that I approve of the use of the |
---|
3206 | + candidate server for segment retrieval. If it doesn't, I return |
---|
3207 | + False, which means that another server must be chosen. |
---|
3208 | + """ |
---|
3209 | + (seqnum, |
---|
3210 | + root_hash, |
---|
3211 | + IV, |
---|
3212 | + segsize, |
---|
3213 | + datalength, |
---|
3214 | + k, |
---|
3215 | + N, |
---|
3216 | + known_prefix, |
---|
3217 | + offsets_tuple) = self.verinfo |
---|
3218 | + if known_prefix != prefix: |
---|
3219 | + self.log("prefix from share %d doesn't match" % reader.shnum) |
---|
3220 | + raise UncoordinatedWriteError("Mismatched prefix -- this could " |
---|
3221 | + "indicate an uncoordinated write") |
---|
3222 | + # Otherwise, we're okay -- no issues. |
---|
3223 | + |
---|
3224 | + |
---|
3225 | + def _remove_reader(self, reader): |
---|
3226 | + """ |
---|
3227 | + At various points, we will wish to remove a peer from |
---|
3228 | + consideration and/or use. These include, but are not necessarily |
---|
3229 | + limited to: |
---|
3230 | + |
---|
3231 | + - A connection error. |
---|
3232 | + - A mismatched prefix (that is, a prefix that does not match |
---|
3233 | + our conception of the version information string). |
---|
3234 | + - A failing block hash, salt hash, or share hash, which can |
---|
3235 | + indicate disk failure/bit flips, or network trouble. |
---|
3236 | + |
---|
3237 | + This method will do that. I will make sure that the |
---|
3238 | + (shnum,reader) combination represented by my reader argument is |
---|
3239 | + not used for anything else during this download. I will not |
---|
3240 | + advise the reader of any corruption, something that my callers |
---|
3241 | + may wish to do on their own. |
---|
3242 | + """ |
---|
3243 | + # TODO: When you're done writing this, see if this is ever |
---|
3244 | + # actually used for something that _mark_bad_share isn't. I have |
---|
3245 | + # a feeling that they will be used for very similar things, and |
---|
3246 | + # that having them both here is just going to be an epic amount |
---|
3247 | + # of code duplication. |
---|
3248 | + # |
---|
3249 | + # (well, okay, not epic, but meaningful) |
---|
3250 | + self.log("removing reader %s" % reader) |
---|
3251 | + # Remove the reader from _active_readers |
---|
3252 | + self._active_readers.remove(reader) |
---|
3253 | + # TODO: self.readers.remove(reader)? |
---|
3254 | for shnum in list(self.remaining_sharemap.keys()): |
---|
3255 | hunk ./src/allmydata/mutable/retrieve.py 460 |
---|
3256 | - self.remaining_sharemap.discard(shnum, peerid) |
---|
3257 | + # TODO: Make sure that we set reader.peerid somewhere. |
---|
3258 | + self.remaining_sharemap.discard(shnum, reader.peerid) |
---|
3259 | |
---|
3260 | hunk ./src/allmydata/mutable/retrieve.py 463 |
---|
3261 | - def _got_results(self, datavs, marker, peerid, started, got_from_cache): |
---|
3262 | - now = time.time() |
---|
3263 | - elapsed = now - started |
---|
3264 | - if not got_from_cache: |
---|
3265 | - self._status.add_fetch_timing(peerid, elapsed) |
---|
3266 | - self.log(format="got results (%(shares)d shares) from [%(peerid)s]", |
---|
3267 | - shares=len(datavs), |
---|
3268 | - peerid=idlib.shortnodeid_b2a(peerid), |
---|
3269 | - level=log.NOISY) |
---|
3270 | - self._outstanding_queries.pop(marker, None) |
---|
3271 | - if not self._running: |
---|
3272 | - return |
---|
3273 | |
---|
3274 | hunk ./src/allmydata/mutable/retrieve.py 464 |
---|
3275 | - # note that we only ask for a single share per query, so we only |
---|
3276 | - # expect a single share back. On the other hand, we use the extra |
---|
3277 | - # shares if we get them.. seems better than an assert(). |
---|
3278 | + def _mark_bad_share(self, reader, f): |
---|
3279 | + """ |
---|
3280 | + I mark the (peerid, shnum) encapsulated by my reader argument as |
---|
3281 | + a bad share, which means that it will not be used anywhere else. |
---|
3282 | + |
---|
3283 | + There are several reasons to want to mark something as a bad |
---|
3284 | + share. These include: |
---|
3285 | + |
---|
3286 | + - A connection error to the peer. |
---|
3287 | + - A mismatched prefix (that is, a prefix that does not match |
---|
3288 | + our local conception of the version information string). |
---|
3289 | + - A failing block hash, salt hash, share hash, or other |
---|
3290 | + integrity check. |
---|
3291 | |
---|
3292 | hunk ./src/allmydata/mutable/retrieve.py 478 |
---|
3293 | - for shnum,datav in datavs.items(): |
---|
3294 | - (prefix, hash_and_data) = datav[:2] |
---|
3295 | + This method will ensure that readers that we wish to mark bad |
---|
3296 | + (for these reasons or other reasons) are not used for the rest |
---|
3297 | + of the download. Additionally, it will attempt to tell the |
---|
3298 | + remote peer (with no guarantee of success) that its share is |
---|
3299 | + corrupt. |
---|
3300 | + """ |
---|
3301 | + self.log("marking share %d on server %s as bad" % \ |
---|
3302 | + (reader.shnum, reader)) |
---|
3303 | + self._remove_reader(reader) |
---|
3304 | + self._bad_shares.add((reader.peerid, reader.shnum)) |
---|
3305 | + self._status.problems[reader.peerid] = f |
---|
3306 | + self._last_failure = f |
---|
3307 | + self.notify_server_corruption(reader.peerid, reader.shnum, f.value) |
---|
3308 | + |
---|
3309 | + |
---|
3310 | + def _download_current_segment(self): |
---|
3311 | + """ |
---|
3312 | + I download, validate, decode, decrypt, and assemble the segment |
---|
3313 | + that this Retrieve is currently responsible for downloading. |
---|
3314 | + """ |
---|
3315 | + assert len(self._active_readers) >= self._required_shares |
---|
3316 | + if self._current_segment < self._num_segments: |
---|
3317 | + d = self._process_segment(self._current_segment) |
---|
3318 | + else: |
---|
3319 | + d = defer.succeed(None) |
---|
3320 | + d.addCallback(self._check_for_done) |
---|
3321 | + return d |
---|
3322 | + |
---|
3323 | + |
---|
3324 | + def _process_segment(self, segnum): |
---|
3325 | + """ |
---|
3326 | + I download, validate, decode, and decrypt one segment of the |
---|
3327 | + file that this Retrieve is retrieving. This means coordinating |
---|
3328 | + the process of getting k blocks of that file, validating them, |
---|
3329 | + assembling them into one segment with the decoder, and then |
---|
3330 | + decrypting them. |
---|
3331 | + """ |
---|
3332 | + self.log("processing segment %d" % segnum) |
---|
3333 | + |
---|
3334 | + # TODO: The old code uses a marker. Should this code do that |
---|
3335 | + # too? What did the Marker do? |
---|
3336 | + assert len(self._active_readers) >= self._required_shares |
---|
3337 | + |
---|
3338 | + # We need to ask each of our active readers for its block and |
---|
3339 | + # salt. We will then validate those. If validation is |
---|
3340 | + # successful, we will assemble the results into plaintext. |
---|
3341 | + ds = [] |
---|
3342 | + for reader in self._active_readers: |
---|
3343 | + d = reader.get_block_and_salt(segnum) |
---|
3344 | + d.addCallback(self._validate_block, segnum, reader) |
---|
3345 | + d.addErrback(self._validation_failed, reader) |
---|
3346 | + ds.append(d) |
---|
3347 | + dl = defer.DeferredList(ds) |
---|
3348 | + dl.addCallback(self._maybe_decode_and_decrypt_segment, segnum) |
---|
3349 | + return dl |
---|
3350 | + |
---|
3351 | + |
---|
3352 | + def _maybe_decode_and_decrypt_segment(self, blocks_and_salts, segnum): |
---|
3353 | + """ |
---|
3354 | + I take the results of fetching and validating the blocks from a |
---|
3355 | + callback chain in another method. If the results are such that |
---|
3356 | + they tell me that validation and fetching succeeded without |
---|
3357 | + incident, I will proceed with decoding and decryption. |
---|
3358 | + Otherwise, I will do nothing. |
---|
3359 | + """ |
---|
3360 | + self.log("trying to decode and decrypt segment %d" % segnum) |
---|
3361 | + failures = False |
---|
3362 | + for block_and_salt in blocks_and_salts: |
---|
3363 | + if not block_and_salt[0] or block_and_salt[1] == None: |
---|
3364 | + self.log("some validation operations failed; not proceeding") |
---|
3365 | + failures = True |
---|
3366 | + break |
---|
3367 | + if not failures: |
---|
3368 | + self.log("everything looks ok, building segment %d" % segnum) |
---|
3369 | + d = self._decode_blocks(blocks_and_salts, segnum) |
---|
3370 | + d.addCallback(self._decrypt_segment) |
---|
3371 | + d.addErrback(self._decoding_or_decrypting_failed) |
---|
3372 | + d.addCallback(self._set_segment) |
---|
3373 | + return d |
---|
3374 | + else: |
---|
3375 | + return defer.succeed(None) |
---|
3376 | + |
---|
3377 | + |
---|
3378 | + def _set_segment(self, segment): |
---|
3379 | + """ |
---|
3380 | + Given a plaintext segment, I register that segment with the |
---|
3381 | + target that is handling the file download. |
---|
3382 | + """ |
---|
3383 | + self.log("got plaintext for segment %d" % self._current_segment) |
---|
3384 | + self._plaintext += segment |
---|
3385 | + self._current_segment += 1 |
---|
3386 | + |
---|
3387 | + |
---|
3388 | + def _validation_failed(self, f, reader): |
---|
3389 | + """ |
---|
3390 | + I am called when a block or a salt fails to correctly validate. |
---|
3391 | + I react to this failure by notifying the remote server of |
---|
3392 | + corruption, and then removing the remote peer from further |
---|
3393 | + activity. |
---|
3394 | + """ |
---|
3395 | + self.log("validation failed on share %d, peer %s, segment %d: %s" % \ |
---|
3396 | + (reader.shnum, reader, self._current_segment, str(f))) |
---|
3397 | + self._mark_bad_share(reader, f) |
---|
3398 | + return |
---|
3399 | + |
---|
3400 | + |
---|
3401 | + def _decoding_or_decrypting_failed(self, f): |
---|
3402 | + """ |
---|
3403 | + I am called when a list of blocks fails to decode into a segment |
---|
3404 | + of crypttext, or fails to decrypt (for whatever reason) into a |
---|
3405 | + segment of plaintext. I exist to make a log message about this |
---|
3406 | + failure: my other job is to mark a share as corrupt, which is |
---|
3407 | + not hard. |
---|
3408 | + """ |
---|
3409 | + # XXX: Is this correct? When we're dealing with validation |
---|
3410 | + # failures, it's easy to say that one share or one server was |
---|
3411 | + # responsible for the failure. Is it so easy when decoding or |
---|
3412 | + # decrypting fails? Maybe we should just log here, and try |
---|
3413 | + # again? Of course, that could lead to infinite loops if |
---|
3414 | + # something *is* wrong, because the state machine will just keep |
---|
3415 | + # trying to download the broken segment over and over and |
---|
3416 | + # over... |
---|
3417 | + self.log("decoding or decrypting failed on segment %d: %s" % \ |
---|
3418 | + (self._current_segment, str(f.value))) |
---|
3419 | + for reader in self._active_readers: |
---|
3420 | + self._mark_bad_share(reader, f) |
---|
3421 | + |
---|
3422 | + assert len(self._active_readers) == 0 |
---|
3423 | + return |
---|
3424 | + |
---|
3425 | + |
---|
3426 | + def _validate_block(self, (block, salt), segnum, reader): |
---|
3427 | + """ |
---|
3428 | + I validate a block from one share on a remote server. |
---|
3429 | + """ |
---|
3430 | + # Grab the part of the block hash tree that is necessary to |
---|
3431 | + # validate this block, then generate the block hash root. |
---|
3432 | + d = self._get_needed_hashes(reader, segnum) |
---|
3433 | + def _handle_validation(block_and_sharehashes): |
---|
3434 | + self.log("validating share %d for segment %d" % (reader.shnum, |
---|
3435 | + segnum)) |
---|
3436 | + blockhashes, sharehashes = block_and_sharehashes |
---|
3437 | + blockhashes = dict(enumerate(blockhashes[1])) |
---|
3438 | + bht = self._block_hash_trees[reader.shnum] |
---|
3439 | + # If we needed sharehashes in the last step, we'll want to |
---|
3440 | + # get those dealt with before we start processing the |
---|
3441 | + # blockhashes. |
---|
3442 | + if self.share_hash_tree.needed_hashes(reader.shnum): |
---|
3443 | + try: |
---|
3444 | + self.share_hash_tree.set_hashes(hashes=sharehashes[1]) |
---|
3445 | + except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \ |
---|
3446 | + IndexError), e: |
---|
3447 | + # XXX: This is a stupid message -- make it more |
---|
3448 | + # informative. |
---|
3449 | + raise CorruptShareError(reader.peerid, |
---|
3450 | + reader.shnum, |
---|
3451 | + "corrupt hashes: %s" % e) |
---|
3452 | + |
---|
3453 | + if not bht[0]: |
---|
3454 | + share_hash = self.share_hash_tree.get_leaf(reader.shnum) |
---|
3455 | + if not share_hash: |
---|
3456 | + raise CorruptShareError(reader.peerid, |
---|
3457 | + reader.shnum, |
---|
3458 | + "missing the root hash") |
---|
3459 | + bht.set_hashes({0: share_hash}) |
---|
3460 | + |
---|
3461 | + if bht.needed_hashes(segnum, include_leaf=True): |
---|
3462 | + try: |
---|
3463 | + bht.set_hashes(blockhashes) |
---|
3464 | + except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \ |
---|
3465 | + IndexError), e: |
---|
3466 | + raise CorruptShareError(reader.peerid, |
---|
3467 | + reader.shnum, |
---|
3468 | + "block hash tree failure: %s" % e) |
---|
3469 | + |
---|
3470 | + blockhash = hashutil.block_hash(block) |
---|
3471 | + self.log("got blockhash %s" % [blockhash]) |
---|
3472 | + self.log("comparing to tree %s" % bht) |
---|
3473 | + # If this works without an error, then validation is |
---|
3474 | + # successful. |
---|
3475 | try: |
---|
3476 | hunk ./src/allmydata/mutable/retrieve.py 659 |
---|
3477 | - self._got_results_one_share(shnum, peerid, |
---|
3478 | - prefix, hash_and_data) |
---|
3479 | - except CorruptShareError, e: |
---|
3480 | - # log it and give the other shares a chance to be processed |
---|
3481 | - f = failure.Failure() |
---|
3482 | - self.log(format="bad share: %(f_value)s", |
---|
3483 | - f_value=str(f.value), failure=f, |
---|
3484 | - level=log.WEIRD, umid="7fzWZw") |
---|
3485 | - self.notify_server_corruption(peerid, shnum, str(e)) |
---|
3486 | - self.remove_peer(peerid) |
---|
3487 | - self.servermap.mark_bad_share(peerid, shnum, prefix) |
---|
3488 | - self._bad_shares.add( (peerid, shnum) ) |
---|
3489 | - self._status.problems[peerid] = f |
---|
3490 | - self._last_failure = f |
---|
3491 | - pass |
---|
3492 | - if self._need_privkey and len(datav) > 2: |
---|
3493 | - lp = None |
---|
3494 | - self._try_to_validate_privkey(datav[2], peerid, shnum, lp) |
---|
3495 | - # all done! |
---|
3496 | + bht.set_hashes(leaves={segnum: blockhash}) |
---|
3497 | + except (hashtree.BadHashError, hashtree.NotEnoughHashesError, \ |
---|
3498 | + IndexError), e: |
---|
3499 | + raise CorruptShareError(reader.peerid, |
---|
3500 | + reader.shnum, |
---|
3501 | + "block hash tree failure: %s" % e) |
---|
3502 | + |
---|
3503 | + # TODO: Validate the salt, too. |
---|
3504 | + self.log('share %d is valid for segment %d' % (reader.shnum, |
---|
3505 | + segnum)) |
---|
3506 | + return {reader.shnum: (block, salt)} |
---|
3507 | + d.addCallback(_handle_validation) |
---|
3508 | + return d |
---|
3509 | + |
---|
3510 | + |
---|
3511 | + def _get_needed_hashes(self, reader, segnum): |
---|
3512 | + """ |
---|
3513 | + I get the hashes needed to validate segnum from the reader, then return |
---|
3514 | + to my caller when this is done. |
---|
3515 | + """ |
---|
3516 | + bht = self._block_hash_trees[reader.shnum] |
---|
3517 | + needed = bht.needed_hashes(segnum, include_leaf=True) |
---|
3518 | + # The root of the block hash tree is also a leaf in the share |
---|
3519 | + # hash tree. So we don't need to fetch it from the remote |
---|
3520 | + # server. In the case of files with one segment, this means that |
---|
3521 | + # we won't fetch any block hash tree from the remote server, |
---|
3522 | + # since the hash of each share of the file is the entire block |
---|
3523 | + # hash tree, and is a leaf in the share hash tree. This is fine, |
---|
3524 | + # since any share corruption will be detected in the share hash |
---|
3525 | + # tree. |
---|
3526 | + needed.discard(0) |
---|
3527 | + # XXX: not now, causes test failures. |
---|
3528 | + self.log("getting blockhashes for segment %d, share %d: %s" % \ |
---|
3529 | + (segnum, reader.shnum, str(needed))) |
---|
3530 | + d1 = reader.get_blockhashes(needed) |
---|
3531 | + if self.share_hash_tree.needed_hashes(reader.shnum): |
---|
3532 | + need = self.share_hash_tree.needed_hashes(reader.shnum) |
---|
3533 | + self.log("also need sharehashes for share %d: %s" % (reader.shnum, |
---|
3534 | + str(need))) |
---|
3535 | + d2 = reader.get_sharehashes(need) |
---|
3536 | + else: |
---|
3537 | + d2 = defer.succeed(None) |
---|
3538 | + dl = defer.DeferredList([d1, d2]) |
---|
3539 | + return dl |
---|
3540 | + |
---|
3541 | + |
---|
3542 | + def _decode_blocks(self, blocks_and_salts, segnum): |
---|
3543 | + """ |
---|
3544 | + I take a list of k blocks and salts, and decode that into a |
---|
3545 | + single encrypted segment. |
---|
3546 | + """ |
---|
3547 | + d = {} |
---|
3548 | + # We want to merge our dictionaries to the form |
---|
3549 | + # {shnum: blocks_and_salts} |
---|
3550 | + # |
---|
3551 | + # The dictionaries come from validate block that way, so we just |
---|
3552 | + # need to merge them. |
---|
3553 | + for block_and_salt in blocks_and_salts: |
---|
3554 | + d.update(block_and_salt[1]) |
---|
3555 | + |
---|
3556 | + # All of these blocks should have the same salt; in SDMF, it is |
---|
3557 | + # the file-wide IV, while in MDMF it is the per-segment salt. In |
---|
3558 | + # either case, we just need to get one of them and use it. |
---|
3559 | + # |
---|
3560 | + # d.items()[0] is like (shnum, (block, salt)) |
---|
3561 | + # d.items()[0][1] is like (block, salt) |
---|
3562 | + # d.items()[0][1][1] is the salt. |
---|
3563 | + salt = d.items()[0][1][1] |
---|
3564 | + # Next, extract just the blocks from the dict. We'll use the |
---|
3565 | + # salt in the next step. |
---|
3566 | + share_and_shareids = [(k, v[0]) for k, v in d.items()] |
---|
3567 | + d2 = dict(share_and_shareids) |
---|
3568 | + shareids = [] |
---|
3569 | + shares = [] |
---|
3570 | + for shareid, share in d2.items(): |
---|
3571 | + shareids.append(shareid) |
---|
3572 | + shares.append(share) |
---|
3573 | + |
---|
3574 | + assert len(shareids) >= self._required_shares, len(shareids) |
---|
3575 | + # zfec really doesn't want extra shares |
---|
3576 | + shareids = shareids[:self._required_shares] |
---|
3577 | + shares = shares[:self._required_shares] |
---|
3578 | + self.log("decoding segment %d" % segnum) |
---|
3579 | + if segnum == self._num_segments - 1: |
---|
3580 | + d = defer.maybeDeferred(self._tail_decoder.decode, shares, shareids) |
---|
3581 | + else: |
---|
3582 | + d = defer.maybeDeferred(self._segment_decoder.decode, shares, shareids) |
---|
3583 | + def _process(buffers): |
---|
3584 | + segment = "".join(buffers) |
---|
3585 | + self.log(format="now decoding segment %(segnum)s of %(numsegs)s", |
---|
3586 | + segnum=segnum, |
---|
3587 | + numsegs=self._num_segments, |
---|
3588 | + level=log.NOISY) |
---|
3589 | + self.log(" joined length %d, datalength %d" % |
---|
3590 | + (len(segment), self._data_length)) |
---|
3591 | + if segnum == self._num_segments - 1: |
---|
3592 | + size_to_use = self._tail_data_size |
---|
3593 | + else: |
---|
3594 | + size_to_use = self._segment_size |
---|
3595 | + segment = segment[:size_to_use] |
---|
3596 | + self.log(" segment len=%d" % len(segment)) |
---|
3597 | + return segment, salt |
---|
3598 | + d.addCallback(_process) |
---|
3599 | + return d |
---|
3600 | + |
---|
3601 | + |
---|
3602 | + def _decrypt_segment(self, segment_and_salt): |
---|
3603 | + """ |
---|
3604 | + I take a single segment and its salt, and decrypt it. I return |
---|
3605 | + the plaintext of the segment that is in my argument. |
---|
3606 | + """ |
---|
3607 | + segment, salt = segment_and_salt |
---|
3608 | + self._status.set_status("decrypting") |
---|
3609 | + self.log("decrypting segment %d" % self._current_segment) |
---|
3610 | + started = time.time() |
---|
3611 | + key = hashutil.ssk_readkey_data_hash(salt, self._node.get_readkey()) |
---|
3612 | + decryptor = AES(key) |
---|
3613 | + plaintext = decryptor.process(segment) |
---|
3614 | + self._status.timings["decrypt"] = time.time() - started |
---|
3615 | + return plaintext |
---|
3616 | + |
---|
3617 | |
---|
3618 | def notify_server_corruption(self, peerid, shnum, reason): |
---|
3619 | ss = self.servermap.connections[peerid] |
---|
3620 | hunk ./src/allmydata/mutable/retrieve.py 786 |
---|
3621 | ss.callRemoteOnly("advise_corrupt_share", |
---|
3622 | "mutable", self._storage_index, shnum, reason) |
---|
3623 | |
---|
3624 | - def _got_results_one_share(self, shnum, peerid, |
---|
3625 | - got_prefix, got_hash_and_data): |
---|
3626 | - self.log("_got_results: got shnum #%d from peerid %s" |
---|
3627 | - % (shnum, idlib.shortnodeid_b2a(peerid))) |
---|
3628 | - (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
3629 | - offsets_tuple) = self.verinfo |
---|
3630 | - assert len(got_prefix) == len(prefix), (len(got_prefix), len(prefix)) |
---|
3631 | - if got_prefix != prefix: |
---|
3632 | - msg = "someone wrote to the data since we read the servermap: prefix changed" |
---|
3633 | - raise UncoordinatedWriteError(msg) |
---|
3634 | - (share_hash_chain, block_hash_tree, |
---|
3635 | - share_data) = unpack_share_data(self.verinfo, got_hash_and_data) |
---|
3636 | - |
---|
3637 | - assert isinstance(share_data, str) |
---|
3638 | - # build the block hash tree. SDMF has only one leaf. |
---|
3639 | - leaves = [hashutil.block_hash(share_data)] |
---|
3640 | - t = hashtree.HashTree(leaves) |
---|
3641 | - if list(t) != block_hash_tree: |
---|
3642 | - raise CorruptShareError(peerid, shnum, "block hash tree failure") |
---|
3643 | - share_hash_leaf = t[0] |
---|
3644 | - t2 = hashtree.IncompleteHashTree(N) |
---|
3645 | - # root_hash was checked by the signature |
---|
3646 | - t2.set_hashes({0: root_hash}) |
---|
3647 | - try: |
---|
3648 | - t2.set_hashes(hashes=share_hash_chain, |
---|
3649 | - leaves={shnum: share_hash_leaf}) |
---|
3650 | - except (hashtree.BadHashError, hashtree.NotEnoughHashesError, |
---|
3651 | - IndexError), e: |
---|
3652 | - msg = "corrupt hashes: %s" % (e,) |
---|
3653 | - raise CorruptShareError(peerid, shnum, msg) |
---|
3654 | - self.log(" data valid! len=%d" % len(share_data)) |
---|
3655 | - # each query comes down to this: placing validated share data into |
---|
3656 | - # self.shares |
---|
3657 | - self.shares[shnum] = share_data |
---|
3658 | |
---|
3659 | hunk ./src/allmydata/mutable/retrieve.py 787 |
---|
3660 | - def _try_to_validate_privkey(self, enc_privkey, peerid, shnum, lp): |
---|
3661 | + def _try_to_validate_privkey(self, enc_privkey, reader): |
---|
3662 | |
---|
3663 | alleged_privkey_s = self._node._decrypt_privkey(enc_privkey) |
---|
3664 | alleged_writekey = hashutil.ssk_writekey_hash(alleged_privkey_s) |
---|
3665 | hunk ./src/allmydata/mutable/retrieve.py 793 |
---|
3666 | if alleged_writekey != self._node.get_writekey(): |
---|
3667 | self.log("invalid privkey from %s shnum %d" % |
---|
3668 | - (idlib.nodeid_b2a(peerid)[:8], shnum), |
---|
3669 | - parent=lp, level=log.WEIRD, umid="YIw4tA") |
---|
3670 | + (reader, reader.shnum), |
---|
3671 | + level=log.WEIRD, umid="YIw4tA") |
---|
3672 | return |
---|
3673 | |
---|
3674 | # it's good |
---|
3675 | hunk ./src/allmydata/mutable/retrieve.py 798 |
---|
3676 | - self.log("got valid privkey from shnum %d on peerid %s" % |
---|
3677 | - (shnum, idlib.shortnodeid_b2a(peerid)), |
---|
3678 | - parent=lp) |
---|
3679 | + self.log("got valid privkey from shnum %d on reader %s" % |
---|
3680 | + (reader.shnum, reader)) |
---|
3681 | privkey = rsa.create_signing_key_from_string(alleged_privkey_s) |
---|
3682 | self._node._populate_encprivkey(enc_privkey) |
---|
3683 | self._node._populate_privkey(privkey) |
---|
3684 | hunk ./src/allmydata/mutable/retrieve.py 805 |
---|
3685 | self._need_privkey = False |
---|
3686 | |
---|
3687 | + |
---|
3688 | def _query_failed(self, f, marker, peerid): |
---|
3689 | self.log(format="query to [%(peerid)s] failed", |
---|
3690 | peerid=idlib.shortnodeid_b2a(peerid), |
---|
3691 | hunk ./src/allmydata/mutable/retrieve.py 822 |
---|
3692 | self.log(format="error during query: %(f_value)s", |
---|
3693 | f_value=str(f.value), failure=f, level=level, umid="gOJB5g") |
---|
3694 | |
---|
3695 | - def _check_for_done(self, res): |
---|
3696 | - # exit paths: |
---|
3697 | - # return : keep waiting, no new queries |
---|
3698 | - # return self._send_more_queries(outstanding) : send some more queries |
---|
3699 | - # fire self._done(plaintext) : download successful |
---|
3700 | - # raise exception : download fails |
---|
3701 | - |
---|
3702 | - self.log(format="_check_for_done: running=%(running)s, decoding=%(decoding)s", |
---|
3703 | - running=self._running, decoding=self._decoding, |
---|
3704 | - level=log.NOISY) |
---|
3705 | - if not self._running: |
---|
3706 | - return |
---|
3707 | - if self._decoding: |
---|
3708 | - return |
---|
3709 | - (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
3710 | - offsets_tuple) = self.verinfo |
---|
3711 | - |
---|
3712 | - if len(self.shares) < k: |
---|
3713 | - # we don't have enough shares yet |
---|
3714 | - return self._maybe_send_more_queries(k) |
---|
3715 | - if self._need_privkey: |
---|
3716 | - # we got k shares, but none of them had a valid privkey. TODO: |
---|
3717 | - # look further. Adding code to do this is a bit complicated, and |
---|
3718 | - # I want to avoid that complication, and this should be pretty |
---|
3719 | - # rare (k shares with bitflips in the enc_privkey but not in the |
---|
3720 | - # data blocks). If we actually do get here, the subsequent repair |
---|
3721 | - # will fail for lack of a privkey. |
---|
3722 | - self.log("got k shares but still need_privkey, bummer", |
---|
3723 | - level=log.WEIRD, umid="MdRHPA") |
---|
3724 | - |
---|
3725 | - # we have enough to finish. All the shares have had their hashes |
---|
3726 | - # checked, so if something fails at this point, we don't know how |
---|
3727 | - # to fix it, so the download will fail. |
---|
3728 | |
---|
3729 | hunk ./src/allmydata/mutable/retrieve.py 823 |
---|
3730 | - self._decoding = True # avoid reentrancy |
---|
3731 | - self._status.set_status("decoding") |
---|
3732 | - now = time.time() |
---|
3733 | - elapsed = now - self._started |
---|
3734 | - self._status.timings["fetch"] = elapsed |
---|
3735 | - |
---|
3736 | - d = defer.maybeDeferred(self._decode) |
---|
3737 | - d.addCallback(self._decrypt, IV, self._node.get_readkey()) |
---|
3738 | - d.addBoth(self._done) |
---|
3739 | - return d # purely for test convenience |
---|
3740 | - |
---|
3741 | - def _maybe_send_more_queries(self, k): |
---|
3742 | - # we don't have enough shares yet. Should we send out more queries? |
---|
3743 | - # There are some number of queries outstanding, each for a single |
---|
3744 | - # share. If we can generate 'needed_shares' additional queries, we do |
---|
3745 | - # so. If we can't, then we know this file is a goner, and we raise |
---|
3746 | - # NotEnoughSharesError. |
---|
3747 | - self.log(format=("_maybe_send_more_queries, have=%(have)d, k=%(k)d, " |
---|
3748 | - "outstanding=%(outstanding)d"), |
---|
3749 | - have=len(self.shares), k=k, |
---|
3750 | - outstanding=len(self._outstanding_queries), |
---|
3751 | - level=log.NOISY) |
---|
3752 | - |
---|
3753 | - remaining_shares = k - len(self.shares) |
---|
3754 | - needed = remaining_shares - len(self._outstanding_queries) |
---|
3755 | - if not needed: |
---|
3756 | - # we have enough queries in flight already |
---|
3757 | - |
---|
3758 | - # TODO: but if they've been in flight for a long time, and we |
---|
3759 | - # have reason to believe that new queries might respond faster |
---|
3760 | - # (i.e. we've seen other queries come back faster, then consider |
---|
3761 | - # sending out new queries. This could help with peers which have |
---|
3762 | - # silently gone away since the servermap was updated, for which |
---|
3763 | - # we're still waiting for the 15-minute TCP disconnect to happen. |
---|
3764 | - self.log("enough queries are in flight, no more are needed", |
---|
3765 | - level=log.NOISY) |
---|
3766 | - return |
---|
3767 | - |
---|
3768 | - outstanding_shnums = set([shnum |
---|
3769 | - for (peerid, shnum, started) |
---|
3770 | - in self._outstanding_queries.values()]) |
---|
3771 | - # prefer low-numbered shares, they are more likely to be primary |
---|
3772 | - available_shnums = sorted(self.remaining_sharemap.keys()) |
---|
3773 | - for shnum in available_shnums: |
---|
3774 | - if shnum in outstanding_shnums: |
---|
3775 | - # skip ones that are already in transit |
---|
3776 | - continue |
---|
3777 | - if shnum not in self.remaining_sharemap: |
---|
3778 | - # no servers for that shnum. note that DictOfSets removes |
---|
3779 | - # empty sets from the dict for us. |
---|
3780 | - continue |
---|
3781 | - peerid = list(self.remaining_sharemap[shnum])[0] |
---|
3782 | - # get_data will remove that peerid from the sharemap, and add the |
---|
3783 | - # query to self._outstanding_queries |
---|
3784 | - self._status.set_status("Retrieving More Shares") |
---|
3785 | - self.get_data(shnum, peerid) |
---|
3786 | - needed -= 1 |
---|
3787 | - if not needed: |
---|
3788 | - break |
---|
3789 | - |
---|
3790 | - # at this point, we have as many outstanding queries as we can. If |
---|
3791 | - # needed!=0 then we might not have enough to recover the file. |
---|
3792 | - if needed: |
---|
3793 | - format = ("ran out of peers: " |
---|
3794 | - "have %(have)d shares (k=%(k)d), " |
---|
3795 | - "%(outstanding)d queries in flight, " |
---|
3796 | - "need %(need)d more, " |
---|
3797 | - "found %(bad)d bad shares") |
---|
3798 | - args = {"have": len(self.shares), |
---|
3799 | - "k": k, |
---|
3800 | - "outstanding": len(self._outstanding_queries), |
---|
3801 | - "need": needed, |
---|
3802 | - "bad": len(self._bad_shares), |
---|
3803 | - } |
---|
3804 | - self.log(format=format, |
---|
3805 | - level=log.WEIRD, umid="ezTfjw", **args) |
---|
3806 | - err = NotEnoughSharesError("%s, last failure: %s" % |
---|
3807 | - (format % args, self._last_failure)) |
---|
3808 | - if self._bad_shares: |
---|
3809 | - self.log("We found some bad shares this pass. You should " |
---|
3810 | - "update the servermap and try again to check " |
---|
3811 | - "more peers", |
---|
3812 | - level=log.WEIRD, umid="EFkOlA") |
---|
3813 | - err.servermap = self.servermap |
---|
3814 | - raise err |
---|
3815 | - |
---|
3816 | - return |
---|
3817 | - |
---|
3818 | - def _decode(self): |
---|
3819 | - started = time.time() |
---|
3820 | - (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
3821 | - offsets_tuple) = self.verinfo |
---|
3822 | + def _check_for_done(self, res): |
---|
3823 | + """ |
---|
3824 | + I check to see if this Retrieve object has successfully finished |
---|
3825 | + its work. |
---|
3826 | |
---|
3827 | hunk ./src/allmydata/mutable/retrieve.py 828 |
---|
3828 | - # shares_dict is a dict mapping shnum to share data, but the codec |
---|
3829 | - # wants two lists. |
---|
3830 | - shareids = []; shares = [] |
---|
3831 | - for shareid, share in self.shares.items(): |
---|
3832 | - shareids.append(shareid) |
---|
3833 | - shares.append(share) |
---|
3834 | + I can exit in the following ways: |
---|
3835 | + - If there are no more segments to download, then I exit by |
---|
3836 | + causing self._done_deferred to fire with the plaintext |
---|
3837 | + content requested by the caller. |
---|
3838 | + - If there are still segments to be downloaded, and there |
---|
3839 | + are enough active readers (readers which have not broken |
---|
3840 | + and have not given us corrupt data) to continue |
---|
3841 | + downloading, I send control back to |
---|
3842 | + _download_current_segment. |
---|
3843 | + - If there are still segments to be downloaded but there are |
---|
3844 | + not enough active peers to download them, I ask |
---|
3845 | + _add_active_peers to add more peers. If it is successful, |
---|
3846 | + it will call _download_current_segment. If there are not |
---|
3847 | + enough peers to retrieve the file, then that will cause |
---|
3848 | + _done_deferred to errback. |
---|
3849 | + """ |
---|
3850 | + self.log("checking for doneness") |
---|
3851 | + if self._current_segment == self._num_segments: |
---|
3852 | + # No more segments to download, we're done. |
---|
3853 | + self.log("got plaintext, done") |
---|
3854 | + return self._done() |
---|
3855 | |
---|
3856 | hunk ./src/allmydata/mutable/retrieve.py 850 |
---|
3857 | - assert len(shareids) >= k, len(shareids) |
---|
3858 | - # zfec really doesn't want extra shares |
---|
3859 | - shareids = shareids[:k] |
---|
3860 | - shares = shares[:k] |
---|
3861 | + if len(self._active_readers) >= self._required_shares: |
---|
3862 | + # More segments to download, but we have enough good peers |
---|
3863 | + # in self._active_readers that we can do that without issue, |
---|
3864 | + # so go nab the next segment. |
---|
3865 | + self.log("not done yet: on segment %d of %d" % \ |
---|
3866 | + (self._current_segment + 1, self._num_segments)) |
---|
3867 | + return self._download_current_segment() |
---|
3868 | |
---|
3869 | hunk ./src/allmydata/mutable/retrieve.py 858 |
---|
3870 | - fec = codec.CRSDecoder() |
---|
3871 | - fec.set_params(segsize, k, N) |
---|
3872 | + self.log("not done yet: on segment %d of %d, need to add peers" % \ |
---|
3873 | + (self._current_segment + 1, self._num_segments)) |
---|
3874 | + return self._add_active_peers() |
---|
3875 | |
---|
3876 | hunk ./src/allmydata/mutable/retrieve.py 862 |
---|
3877 | - self.log("params %s, we have %d shares" % ((segsize, k, N), len(shares))) |
---|
3878 | - self.log("about to decode, shareids=%s" % (shareids,)) |
---|
3879 | - d = defer.maybeDeferred(fec.decode, shares, shareids) |
---|
3880 | - def _done(buffers): |
---|
3881 | - self._status.timings["decode"] = time.time() - started |
---|
3882 | - self.log(" decode done, %d buffers" % len(buffers)) |
---|
3883 | - segment = "".join(buffers) |
---|
3884 | - self.log(" joined length %d, datalength %d" % |
---|
3885 | - (len(segment), datalength)) |
---|
3886 | - segment = segment[:datalength] |
---|
3887 | - self.log(" segment len=%d" % len(segment)) |
---|
3888 | - return segment |
---|
3889 | - def _err(f): |
---|
3890 | - self.log(" decode failed: %s" % f) |
---|
3891 | - return f |
---|
3892 | - d.addCallback(_done) |
---|
3893 | - d.addErrback(_err) |
---|
3894 | - return d |
---|
3895 | |
---|
3896 | hunk ./src/allmydata/mutable/retrieve.py 863 |
---|
3897 | - def _decrypt(self, crypttext, IV, readkey): |
---|
3898 | - self._status.set_status("decrypting") |
---|
3899 | - started = time.time() |
---|
3900 | - key = hashutil.ssk_readkey_data_hash(IV, readkey) |
---|
3901 | - decryptor = AES(key) |
---|
3902 | - plaintext = decryptor.process(crypttext) |
---|
3903 | - self._status.timings["decrypt"] = time.time() - started |
---|
3904 | - return plaintext |
---|
3905 | + def _done(self): |
---|
3906 | + """ |
---|
3907 | + I am called by _check_for_done when the download process has |
---|
3908 | + finished successfully. After making some useful logging |
---|
3909 | + statements, I return the decrypted contents to the owner of this |
---|
3910 | + Retrieve object through self._done_deferred. |
---|
3911 | + """ |
---|
3912 | + eventually(self._done_deferred.callback, self._plaintext) |
---|
3913 | |
---|
3914 | hunk ./src/allmydata/mutable/retrieve.py 872 |
---|
3915 | - def _done(self, res): |
---|
3916 | - if not self._running: |
---|
3917 | - return |
---|
3918 | - self._running = False |
---|
3919 | - self._status.set_active(False) |
---|
3920 | - self._status.timings["total"] = time.time() - self._started |
---|
3921 | - # res is either the new contents, or a Failure |
---|
3922 | - if isinstance(res, failure.Failure): |
---|
3923 | - self.log("Retrieve done, with failure", failure=res, |
---|
3924 | - level=log.UNUSUAL) |
---|
3925 | - self._status.set_status("Failed") |
---|
3926 | - else: |
---|
3927 | - self.log("Retrieve done, success!") |
---|
3928 | - self._status.set_status("Finished") |
---|
3929 | - self._status.set_progress(1.0) |
---|
3930 | - # remember the encoding parameters, use them again next time |
---|
3931 | - (seqnum, root_hash, IV, segsize, datalength, k, N, prefix, |
---|
3932 | - offsets_tuple) = self.verinfo |
---|
3933 | - self._node._populate_required_shares(k) |
---|
3934 | - self._node._populate_total_shares(N) |
---|
3935 | - eventually(self._done_deferred.callback, res) |
---|
3936 | |
---|
3937 | hunk ./src/allmydata/mutable/retrieve.py 873 |
---|
3938 | + def _failed(self): |
---|
3939 | + """ |
---|
3940 | + I am called by _add_active_peers when there are not enough |
---|
3941 | + active peers left to complete the download. After making some |
---|
3942 | + useful logging statements, I return an exception to that effect |
---|
3943 | + to the caller of this Retrieve object through |
---|
3944 | + self._done_deferred. |
---|
3945 | + """ |
---|
3946 | + format = ("ran out of peers: " |
---|
3947 | + "have %(have)d of %(total)d segments " |
---|
3948 | + "found %(bad)d bad shares " |
---|
3949 | + "encoding %(k)d-of-%(n)d") |
---|
3950 | + args = {"have": self._current_segment, |
---|
3951 | + "total": self._num_segments, |
---|
3952 | + "k": self._required_shares, |
---|
3953 | + "n": self._total_shares, |
---|
3954 | + "bad": len(self._bad_shares)} |
---|
3955 | + e = NotEnoughSharesError("%s, last failure: %s" % (format % args, |
---|
3956 | + str(self._last_failure))) |
---|
3957 | + f = failure.Failure(e) |
---|
3958 | + eventually(self._done_deferred.callback, f) |
---|
3959 | hunk ./src/allmydata/test/test_mutable.py 309 |
---|
3960 | d.addCallback(_created) |
---|
3961 | return d |
---|
3962 | |
---|
3963 | + |
---|
3964 | def test_create_with_initial_contents_function(self): |
---|
3965 | data = "initial contents" |
---|
3966 | def _make_contents(n): |
---|
3967 | hunk ./src/allmydata/test/test_mutable.py 594 |
---|
3968 | self.failUnless(p._pubkey.verify(sig_material, signature)) |
---|
3969 | #self.failUnlessEqual(signature, p._privkey.sign(sig_material)) |
---|
3970 | self.failUnless(isinstance(share_hash_chain, dict)) |
---|
3971 | - self.failUnlessEqual(len(share_hash_chain), 4) # ln2(10)++ |
---|
3972 | + # TODO: Revisit this to make sure that the additional |
---|
3973 | + # share hashes are really necessary. |
---|
3974 | + # |
---|
3975 | + # (just because they magically make the tests pass does |
---|
3976 | + # not mean that they are necessary) |
---|
3977 | + # ln2(10)++ + 1 for leaves. |
---|
3978 | + self.failUnlessEqual(len(share_hash_chain), 5) |
---|
3979 | for shnum,share_hash in share_hash_chain.items(): |
---|
3980 | self.failUnless(isinstance(shnum, int)) |
---|
3981 | self.failUnless(isinstance(share_hash, str)) |
---|
3982 | hunk ./src/allmydata/test/test_mutable.py 915 |
---|
3983 | def _check_servermap(sm): |
---|
3984 | self.failUnlessEqual(len(sm.recoverable_versions()), 1) |
---|
3985 | d.addCallback(_check_servermap) |
---|
3986 | - # Now, we upload more versions |
---|
3987 | - d.addCallback(lambda ignored: |
---|
3988 | - self.publish_multiple(version=1)) |
---|
3989 | - d.addCallback(lambda ignored: |
---|
3990 | - self.make_servermap(mode=MODE_CHECK)) |
---|
3991 | - def _check_servermap_multiple(sm): |
---|
3992 | - v = sm.recoverable_versions() |
---|
3993 | - i = sm.unrecoverable_versions() |
---|
3994 | - d.addCallback(_check_servermap_multiple) |
---|
3995 | return d |
---|
3996 | hunk ./src/allmydata/test/test_mutable.py 916 |
---|
3997 | - test_servermapupdater_finds_mdmf_files.todo = ("I don't know how to " |
---|
3998 | - "write this yet") |
---|
3999 | |
---|
4000 | |
---|
4001 | def test_servermapupdater_finds_sdmf_files(self): |
---|
4002 | hunk ./src/allmydata/test/test_mutable.py 1163 |
---|
4003 | def _check(res): |
---|
4004 | f = res[0] |
---|
4005 | self.failUnless(f.check(NotEnoughSharesError)) |
---|
4006 | - self.failUnless("someone wrote to the data since we read the servermap" in str(f)) |
---|
4007 | + self.failUnless("uncoordinated write" in str(f)) |
---|
4008 | return self._test_corrupt_all(1, "ran out of peers", |
---|
4009 | corrupt_early=False, |
---|
4010 | failure_checker=_check) |
---|
4011 | hunk ./src/allmydata/test/test_mutable.py 1937 |
---|
4012 | d.addCallback(lambda res: |
---|
4013 | self.shouldFail(NotEnoughSharesError, |
---|
4014 | "test_retrieve_surprise", |
---|
4015 | - "ran out of peers: have 0 shares (k=3)", |
---|
4016 | + "ran out of peers: have 0 of 1", |
---|
4017 | n.download_version, |
---|
4018 | self.old_map, |
---|
4019 | self.old_map.best_recoverable_version(), |
---|
4020 | hunk ./src/allmydata/test/test_mutable.py 1946 |
---|
4021 | d.addCallback(_created) |
---|
4022 | return d |
---|
4023 | |
---|
4024 | + |
---|
4025 | def test_unexpected_shares(self): |
---|
4026 | # upload the file, take a servermap, shut down one of the servers, |
---|
4027 | # upload it again (causing shares to appear on a new server), then |
---|
4028 | } |
---|
4029 | [Tell NodeMaker and MutableFileNode about the distinction between SDMF and MDMF |
---|
4030 | Kevan Carstensen <kevan@isnotajoke.com>**20100623001708 |
---|
4031 | Ignore-this: 92c723fd536264be2eef9e2a919d334f |
---|
4032 | ] { |
---|
4033 | hunk ./src/allmydata/mutable/filenode.py 8 |
---|
4034 | from twisted.internet import defer, reactor |
---|
4035 | from foolscap.api import eventually |
---|
4036 | from allmydata.interfaces import IMutableFileNode, \ |
---|
4037 | - ICheckable, ICheckResults, NotEnoughSharesError |
---|
4038 | + ICheckable, ICheckResults, NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION |
---|
4039 | from allmydata.util import hashutil, log |
---|
4040 | from allmydata.util.assertutil import precondition |
---|
4041 | from allmydata.uri import WriteableSSKFileURI, ReadonlySSKFileURI |
---|
4042 | hunk ./src/allmydata/mutable/filenode.py 67 |
---|
4043 | self._sharemap = {} # known shares, shnum-to-[nodeids] |
---|
4044 | self._cache = ResponseCache() |
---|
4045 | self._most_recent_size = None |
---|
4046 | + # filled in after __init__ if we're being created for the first time; |
---|
4047 | + # filled in by the servermap updater before publishing, otherwise. |
---|
4048 | + # set to this default value in case neither of those things happen, |
---|
4049 | + # or in case the servermap can't find any shares to tell us what |
---|
4050 | + # to publish as. |
---|
4051 | + # TODO: Set this back to None, and find out why the tests fail |
---|
4052 | + # with it set to None. |
---|
4053 | + self._protocol_version = SDMF_VERSION |
---|
4054 | |
---|
4055 | # all users of this MutableFileNode go through the serializer. This |
---|
4056 | # takes advantage of the fact that Deferreds discard the callbacks |
---|
4057 | hunk ./src/allmydata/mutable/filenode.py 472 |
---|
4058 | def _did_upload(self, res, size): |
---|
4059 | self._most_recent_size = size |
---|
4060 | return res |
---|
4061 | + |
---|
4062 | + |
---|
4063 | + def set_version(self, version): |
---|
4064 | + # I can be set in two ways: |
---|
4065 | + # 1. When the node is created. |
---|
4066 | + # 2. (for an existing share) when the Servermap is updated |
---|
4067 | + # before I am read. |
---|
4068 | + assert version in (MDMF_VERSION, SDMF_VERSION) |
---|
4069 | + self._protocol_version = version |
---|
4070 | + |
---|
4071 | + |
---|
4072 | + def get_version(self): |
---|
4073 | + return self._protocol_version |
---|
4074 | hunk ./src/allmydata/nodemaker.py 3 |
---|
4075 | import weakref |
---|
4076 | from zope.interface import implements |
---|
4077 | -from allmydata.interfaces import INodeMaker |
---|
4078 | +from allmydata.util.assertutil import precondition |
---|
4079 | +from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError, \ |
---|
4080 | + SDMF_VERSION, MDMF_VERSION |
---|
4081 | from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode |
---|
4082 | from allmydata.immutable.upload import Data |
---|
4083 | from allmydata.mutable.filenode import MutableFileNode |
---|
4084 | hunk ./src/allmydata/nodemaker.py 92 |
---|
4085 | return self._create_dirnode(filenode) |
---|
4086 | return None |
---|
4087 | |
---|
4088 | - def create_mutable_file(self, contents=None, keysize=None): |
---|
4089 | + def create_mutable_file(self, contents=None, keysize=None, |
---|
4090 | + version=SDMF_VERSION): |
---|
4091 | n = MutableFileNode(self.storage_broker, self.secret_holder, |
---|
4092 | self.default_encoding_parameters, self.history) |
---|
4093 | hunk ./src/allmydata/nodemaker.py 96 |
---|
4094 | + n.set_version(version) |
---|
4095 | d = self.key_generator.generate(keysize) |
---|
4096 | d.addCallback(n.create_with_keys, contents) |
---|
4097 | d.addCallback(lambda res: n) |
---|
4098 | hunk ./src/allmydata/nodemaker.py 102 |
---|
4099 | return d |
---|
4100 | |
---|
4101 | - def create_new_mutable_directory(self, initial_children={}): |
---|
4102 | + def create_new_mutable_directory(self, initial_children={}, |
---|
4103 | + version=SDMF_VERSION): |
---|
4104 | + # initial_children must have metadata (i.e. {} instead of None) |
---|
4105 | + for (name, (node, metadata)) in initial_children.iteritems(): |
---|
4106 | + precondition(isinstance(metadata, dict), |
---|
4107 | + "create_new_mutable_directory requires metadata to be a dict, not None", metadata) |
---|
4108 | + node.raise_error() |
---|
4109 | d = self.create_mutable_file(lambda n: |
---|
4110 | hunk ./src/allmydata/nodemaker.py 110 |
---|
4111 | - pack_children(n, initial_children)) |
---|
4112 | + pack_children(n, initial_children), |
---|
4113 | + version) |
---|
4114 | d.addCallback(self._create_dirnode) |
---|
4115 | return d |
---|
4116 | |
---|
4117 | } |
---|
4118 | [Assorted servermap fixes |
---|
4119 | Kevan Carstensen <kevan@isnotajoke.com>**20100623001732 |
---|
4120 | Ignore-this: d54c4b5de327960ea4ffe096664b5a65 |
---|
4121 | |
---|
4122 | - Check for failure when setting the private key |
---|
4123 | - Check for failure when setting other things |
---|
4124 | - Check for doneness in a way that is resilient to hung servers |
---|
4125 | - Remove dead code |
---|
4126 | - Reorganize error and success handling methods, and make sure they get |
---|
4127 | used. |
---|
4128 | |
---|
4129 | ] { |
---|
4130 | hunk ./src/allmydata/mutable/servermap.py 485 |
---|
4131 | # set as we get responses. |
---|
4132 | self._must_query = must_query |
---|
4133 | |
---|
4134 | - # This tells the done check whether requests are still being |
---|
4135 | - # processed. We should wait before returning until at least |
---|
4136 | - # updated correctly (and dealing with connection errors. |
---|
4137 | - self._processing = 0 |
---|
4138 | - |
---|
4139 | # now initial_peers_to_query contains the peers that we should ask, |
---|
4140 | # self.must_query contains the peers that we must have heard from |
---|
4141 | # before we can consider ourselves finished, and self.extra_peers |
---|
4142 | hunk ./src/allmydata/mutable/servermap.py 550 |
---|
4143 | # _query_failed) get logged, but we still want to check for doneness. |
---|
4144 | d.addErrback(log.err) |
---|
4145 | d.addErrback(self._fatal_error) |
---|
4146 | + d.addCallback(self._check_for_done) |
---|
4147 | return d |
---|
4148 | |
---|
4149 | def _do_read(self, ss, peerid, storage_index, shnums, readv): |
---|
4150 | hunk ./src/allmydata/mutable/servermap.py 569 |
---|
4151 | d = ss.callRemote("slot_readv", storage_index, shnums, readv) |
---|
4152 | return d |
---|
4153 | |
---|
4154 | + |
---|
4155 | + def _got_corrupt_share(self, e, shnum, peerid, data, lp): |
---|
4156 | + """ |
---|
4157 | + I am called when a remote server returns a corrupt share in |
---|
4158 | + response to one of our queries. By corrupt, I mean a share |
---|
4159 | + without a valid signature. I then record the failure, notify the |
---|
4160 | + server of the corruption, and record the share as bad. |
---|
4161 | + """ |
---|
4162 | + f = failure.Failure(e) |
---|
4163 | + self.log(format="bad share: %(f_value)s", f_value=str(f.value), |
---|
4164 | + failure=f, parent=lp, level=log.WEIRD, umid="h5llHg") |
---|
4165 | + # Notify the server that its share is corrupt. |
---|
4166 | + self.notify_server_corruption(peerid, shnum, str(e)) |
---|
4167 | + # By flagging this as a bad peer, we won't count any of |
---|
4168 | + # the other shares on that peer as valid, though if we |
---|
4169 | + # happen to find a valid version string amongst those |
---|
4170 | + # shares, we'll keep track of it so that we don't need |
---|
4171 | + # to validate the signature on those again. |
---|
4172 | + self._bad_peers.add(peerid) |
---|
4173 | + self._last_failure = f |
---|
4174 | + # XXX: Use the reader for this? |
---|
4175 | + checkstring = data[:SIGNED_PREFIX_LENGTH] |
---|
4176 | + self._servermap.mark_bad_share(peerid, shnum, checkstring) |
---|
4177 | + self._servermap.problems.append(f) |
---|
4178 | + |
---|
4179 | + |
---|
4180 | + def _cache_good_sharedata(self, verinfo, shnum, now, data): |
---|
4181 | + """ |
---|
4182 | + If one of my queries returns successfully (which means that we |
---|
4183 | + were able to and successfully did validate the signature), I |
---|
4184 | + cache the data that we initially fetched from the storage |
---|
4185 | + server. This will help reduce the number of roundtrips that need |
---|
4186 | + to occur when the file is downloaded, or when the file is |
---|
4187 | + updated. |
---|
4188 | + """ |
---|
4189 | + self._node._add_to_cache(verinfo, shnum, 0, data, now) |
---|
4190 | + |
---|
4191 | + |
---|
4192 | def _got_results(self, datavs, peerid, readsize, stuff, started): |
---|
4193 | lp = self.log(format="got result from [%(peerid)s], %(numshares)d shares", |
---|
4194 | peerid=idlib.shortnodeid_b2a(peerid), |
---|
4195 | hunk ./src/allmydata/mutable/servermap.py 618 |
---|
4196 | self._servermap.reachable_peers.add(peerid) |
---|
4197 | self._must_query.discard(peerid) |
---|
4198 | self._queries_completed += 1 |
---|
4199 | - # self._processing counts the number of queries that have |
---|
4200 | - # completed, but are still processing. We wait until all queries |
---|
4201 | - # are done processing before returning a result to the client. |
---|
4202 | - # TODO: Should we do this? A response to the initial query means |
---|
4203 | - # that we may not have to query the server for anything else, |
---|
4204 | - # but if we're dealing with an MDMF share, we'll probably have |
---|
4205 | - # to ask it for its signature, unless we cache those sometplace, |
---|
4206 | - # and even then. |
---|
4207 | - self._processing += 1 |
---|
4208 | if not self._running: |
---|
4209 | self.log("but we're not running, so we'll ignore it", parent=lp, |
---|
4210 | level=log.NOISY) |
---|
4211 | hunk ./src/allmydata/mutable/servermap.py 633 |
---|
4212 | ss, storage_index = stuff |
---|
4213 | ds = [] |
---|
4214 | |
---|
4215 | - |
---|
4216 | - def _tattle(ignored, status): |
---|
4217 | - print status |
---|
4218 | - print ignored |
---|
4219 | - return ignored |
---|
4220 | - |
---|
4221 | - def _cache(verinfo, shnum, now, data): |
---|
4222 | - self._queries_oustand |
---|
4223 | - self._node._add_to_cache(verinfo, shnum, 0, data, now) |
---|
4224 | - return shnum, verinfo |
---|
4225 | - |
---|
4226 | - def _corrupt(e, shnum, data): |
---|
4227 | - # This gets raised when there was something wrong with |
---|
4228 | - # the remote server. Specifically, when there was an |
---|
4229 | - # error unpacking the remote data from the server, or |
---|
4230 | - # when the signature is invalid. |
---|
4231 | - print e |
---|
4232 | - f = failure.Failure() |
---|
4233 | - self.log(format="bad share: %(f_value)s", f_value=str(f.value), |
---|
4234 | - failure=f, parent=lp, level=log.WEIRD, umid="h5llHg") |
---|
4235 | - # Notify the server that its share is corrupt. |
---|
4236 | - self.notify_server_corruption(peerid, shnum, str(e)) |
---|
4237 | - # By flagging this as a bad peer, we won't count any of |
---|
4238 | - # the other shares on that peer as valid, though if we |
---|
4239 | - # happen to find a valid version string amongst those |
---|
4240 | - # shares, we'll keep track of it so that we don't need |
---|
4241 | - # to validate the signature on those again. |
---|
4242 | - self._bad_peers.add(peerid) |
---|
4243 | - self._last_failure = f |
---|
4244 | - # 393CHANGE: Use the reader for this. |
---|
4245 | - checkstring = data[:SIGNED_PREFIX_LENGTH] |
---|
4246 | - self._servermap.mark_bad_share(peerid, shnum, checkstring) |
---|
4247 | - self._servermap.problems.append(f) |
---|
4248 | - |
---|
4249 | for shnum,datav in datavs.items(): |
---|
4250 | data = datav[0] |
---|
4251 | reader = MDMFSlotReadProxy(ss, |
---|
4252 | hunk ./src/allmydata/mutable/servermap.py 646 |
---|
4253 | # need to do the following: |
---|
4254 | # - If we don't already have the public key, fetch the |
---|
4255 | # public key. We use this to validate the signature. |
---|
4256 | - friendly_peer = idlib.shortnodeid_b2a(peerid) |
---|
4257 | if not self._node.get_pubkey(): |
---|
4258 | # fetch and set the public key. |
---|
4259 | d = reader.get_verification_key() |
---|
4260 | hunk ./src/allmydata/mutable/servermap.py 649 |
---|
4261 | - d.addCallback(self._try_to_set_pubkey) |
---|
4262 | + d.addCallback(lambda results, shnum=shnum, peerid=peerid: |
---|
4263 | + self._try_to_set_pubkey(results, peerid, shnum, lp)) |
---|
4264 | + # XXX: Make self._pubkey_query_failed? |
---|
4265 | + d.addErrback(lambda error, shnum=shnum, peerid=peerid: |
---|
4266 | + self._got_corrupt_share(error, shnum, peerid, data, lp)) |
---|
4267 | else: |
---|
4268 | # we already have the public key. |
---|
4269 | d = defer.succeed(None) |
---|
4270 | hunk ./src/allmydata/mutable/servermap.py 666 |
---|
4271 | # bytes of the share on the storage server, so we |
---|
4272 | # shouldn't need to fetch anything at this step. |
---|
4273 | d2 = reader.get_verinfo() |
---|
4274 | + d2.addErrback(lambda error, shnum=shnum, peerid=peerid: |
---|
4275 | + self._got_corrupt_share(error, shnum, peerid, data, lp)) |
---|
4276 | # - Next, we need the signature. For an SDMF share, it is |
---|
4277 | # likely that we fetched this when doing our initial fetch |
---|
4278 | # to get the version information. In MDMF, this lives at |
---|
4279 | hunk ./src/allmydata/mutable/servermap.py 674 |
---|
4280 | # the end of the share, so unless the file is quite small, |
---|
4281 | # we'll need to do a remote fetch to get it. |
---|
4282 | d3 = reader.get_signature() |
---|
4283 | + d3.addErrback(lambda error, shnum=shnum, peerid=peerid: |
---|
4284 | + self._got_corrupt_share(error, shnum, peerid, data, lp)) |
---|
4285 | # Once we have all three of these responses, we can move on |
---|
4286 | # to validating the signature |
---|
4287 | |
---|
4288 | hunk ./src/allmydata/mutable/servermap.py 681 |
---|
4289 | # Does the node already have a privkey? If not, we'll try to |
---|
4290 | # fetch it here. |
---|
4291 | - if not self._node.get_privkey(): |
---|
4292 | + if self._need_privkey: |
---|
4293 | d4 = reader.get_encprivkey() |
---|
4294 | d4.addCallback(lambda results, shnum=shnum, peerid=peerid: |
---|
4295 | self._try_to_validate_privkey(results, peerid, shnum, lp)) |
---|
4296 | hunk ./src/allmydata/mutable/servermap.py 685 |
---|
4297 | + d4.addErrback(lambda error, shnum=shnum, peerid=peerid: |
---|
4298 | + self._privkey_query_failed(error, shnum, data, lp)) |
---|
4299 | else: |
---|
4300 | d4 = defer.succeed(None) |
---|
4301 | |
---|
4302 | hunk ./src/allmydata/mutable/servermap.py 694 |
---|
4303 | dl.addCallback(lambda results, shnum=shnum, peerid=peerid: |
---|
4304 | self._got_signature_one_share(results, shnum, peerid, lp)) |
---|
4305 | dl.addErrback(lambda error, shnum=shnum, data=data: |
---|
4306 | - _corrupt(error, shnum, data)) |
---|
4307 | + self._got_corrupt_share(error, shnum, peerid, data, lp)) |
---|
4308 | + dl.addCallback(lambda verinfo, shnum=shnum, peerid=peerid, data=data: |
---|
4309 | + self._cache_good_sharedata(verinfo, shnum, now, data)) |
---|
4310 | ds.append(dl) |
---|
4311 | # dl is a deferred list that will fire when all of the shares |
---|
4312 | # that we found on this peer are done processing. When dl fires, |
---|
4313 | hunk ./src/allmydata/mutable/servermap.py 702 |
---|
4314 | # we know that processing is done, so we can decrement the |
---|
4315 | # semaphore-like thing that we incremented earlier. |
---|
4316 | - dl = defer.DeferredList(ds) |
---|
4317 | - def _done_processing(ignored): |
---|
4318 | - self._processing -= 1 |
---|
4319 | - return ignored |
---|
4320 | - dl.addCallback(_done_processing) |
---|
4321 | + dl = defer.DeferredList(ds, fireOnOneErrback=True) |
---|
4322 | # Are we done? Done means that there are no more queries to |
---|
4323 | # send, that there are no outstanding queries, and that we |
---|
4324 | # haven't received any queries that are still processing. If we |
---|
4325 | hunk ./src/allmydata/mutable/servermap.py 710 |
---|
4326 | # that we returned to our caller to fire, which tells them that |
---|
4327 | # they have a complete servermap, and that we won't be touching |
---|
4328 | # the servermap anymore. |
---|
4329 | - dl.addBoth(self._check_for_done) |
---|
4330 | + dl.addCallback(self._check_for_done) |
---|
4331 | dl.addErrback(self._fatal_error) |
---|
4332 | # all done! |
---|
4333 | hunk ./src/allmydata/mutable/servermap.py 713 |
---|
4334 | - return dl |
---|
4335 | self.log("_got_results done", parent=lp, level=log.NOISY) |
---|
4336 | hunk ./src/allmydata/mutable/servermap.py 714 |
---|
4337 | + return dl |
---|
4338 | + |
---|
4339 | |
---|
4340 | hunk ./src/allmydata/mutable/servermap.py 717 |
---|
4341 | - def _try_to_set_pubkey(self, pubkey_s): |
---|
4342 | + def _try_to_set_pubkey(self, pubkey_s, peerid, shnum, lp): |
---|
4343 | if self._node.get_pubkey(): |
---|
4344 | return # don't go through this again if we don't have to |
---|
4345 | fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s) |
---|
4346 | hunk ./src/allmydata/mutable/servermap.py 773 |
---|
4347 | if verinfo not in self._valid_versions: |
---|
4348 | # This is a new version tuple, and we need to validate it |
---|
4349 | # against the public key before keeping track of it. |
---|
4350 | + assert self._node.get_pubkey() |
---|
4351 | valid = self._node.get_pubkey().verify(prefix, signature[1]) |
---|
4352 | if not valid: |
---|
4353 | raise CorruptShareError(peerid, shnum, |
---|
4354 | hunk ./src/allmydata/mutable/servermap.py 892 |
---|
4355 | self._queries_completed += 1 |
---|
4356 | self._last_failure = f |
---|
4357 | |
---|
4358 | - def _got_privkey_results(self, datavs, peerid, shnum, started, lp): |
---|
4359 | - now = time.time() |
---|
4360 | - elapsed = now - started |
---|
4361 | - self._status.add_per_server_time(peerid, "privkey", started, elapsed) |
---|
4362 | - self._queries_outstanding.discard(peerid) |
---|
4363 | - if not self._need_privkey: |
---|
4364 | - return |
---|
4365 | - if shnum not in datavs: |
---|
4366 | - self.log("privkey wasn't there when we asked it", |
---|
4367 | - level=log.WEIRD, umid="VA9uDQ") |
---|
4368 | - return |
---|
4369 | - datav = datavs[shnum] |
---|
4370 | - enc_privkey = datav[0] |
---|
4371 | - self._try_to_validate_privkey(enc_privkey, peerid, shnum, lp) |
---|
4372 | |
---|
4373 | def _privkey_query_failed(self, f, peerid, shnum, lp): |
---|
4374 | self._queries_outstanding.discard(peerid) |
---|
4375 | hunk ./src/allmydata/mutable/servermap.py 906 |
---|
4376 | self._servermap.problems.append(f) |
---|
4377 | self._last_failure = f |
---|
4378 | |
---|
4379 | + |
---|
4380 | def _check_for_done(self, res): |
---|
4381 | # exit paths: |
---|
4382 | # return self._send_more_queries(outstanding) : send some more queries |
---|
4383 | hunk ./src/allmydata/mutable/servermap.py 930 |
---|
4384 | self.log("but we're not running", parent=lp, level=log.NOISY) |
---|
4385 | return |
---|
4386 | |
---|
4387 | - if self._processing > 0: |
---|
4388 | - # wait until more results are done before returning. |
---|
4389 | - return |
---|
4390 | - |
---|
4391 | if self._must_query: |
---|
4392 | # we are still waiting for responses from peers that used to have |
---|
4393 | # a share, so we must continue to wait. No additional queries are |
---|
4394 | } |
---|
4395 | [A first stab at a segmented uploader |
---|
4396 | Kevan Carstensen <kevan@isnotajoke.com>**20100623001833 |
---|
4397 | Ignore-this: 4493a13e6f639cd7378472b872f199d2 |
---|
4398 | |
---|
4399 | This uploader will upload, segment-by-segment, MDMF files. It will only |
---|
4400 | do this if it thinks that the filenode that it is uploading represents |
---|
4401 | an MDMF file; otherwise, it uploads the file as SDMF. |
---|
4402 | |
---|
4403 | My TODO list so far: |
---|
4404 | - More robust peer selection; we'll want to use something like |
---|
4405 | servers of happiness to figure out reliability and unreliability. |
---|
4406 | - Clean up. |
---|
4407 | ] { |
---|
4408 | hunk ./src/allmydata/mutable/publish.py 8 |
---|
4409 | from zope.interface import implements |
---|
4410 | from twisted.internet import defer |
---|
4411 | from twisted.python import failure |
---|
4412 | -from allmydata.interfaces import IPublishStatus |
---|
4413 | +from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION |
---|
4414 | from allmydata.util import base32, hashutil, mathutil, idlib, log |
---|
4415 | from allmydata import hashtree, codec |
---|
4416 | from allmydata.storage.server import si_b2a |
---|
4417 | hunk ./src/allmydata/mutable/publish.py 19 |
---|
4418 | UncoordinatedWriteError, NotEnoughServersError |
---|
4419 | from allmydata.mutable.servermap import ServerMap |
---|
4420 | from allmydata.mutable.layout import pack_prefix, pack_share, unpack_header, pack_checkstring, \ |
---|
4421 | - unpack_checkstring, SIGNED_PREFIX |
---|
4422 | + unpack_checkstring, SIGNED_PREFIX, MDMFSlotWriteProxy |
---|
4423 | + |
---|
4424 | +KiB = 1024 |
---|
4425 | +DEFAULT_MAX_SEGMENT_SIZE = 128 * KiB |
---|
4426 | |
---|
4427 | class PublishStatus: |
---|
4428 | implements(IPublishStatus) |
---|
4429 | hunk ./src/allmydata/mutable/publish.py 112 |
---|
4430 | self._status.set_helper(False) |
---|
4431 | self._status.set_progress(0.0) |
---|
4432 | self._status.set_active(True) |
---|
4433 | + # We use this to control how the file is written. |
---|
4434 | + version = self._node.get_version() |
---|
4435 | + assert version in (SDMF_VERSION, MDMF_VERSION) |
---|
4436 | + self._version = version |
---|
4437 | |
---|
4438 | def get_status(self): |
---|
4439 | return self._status |
---|
4440 | hunk ./src/allmydata/mutable/publish.py 134 |
---|
4441 | simultaneous write. |
---|
4442 | """ |
---|
4443 | |
---|
4444 | - # 1: generate shares (SDMF: files are small, so we can do it in RAM) |
---|
4445 | - # 2: perform peer selection, get candidate servers |
---|
4446 | - # 2a: send queries to n+epsilon servers, to determine current shares |
---|
4447 | - # 2b: based upon responses, create target map |
---|
4448 | - # 3: send slot_testv_and_readv_and_writev messages |
---|
4449 | - # 4: as responses return, update share-dispatch table |
---|
4450 | - # 4a: may need to run recovery algorithm |
---|
4451 | - # 5: when enough responses are back, we're done |
---|
4452 | + # 0. Setup encoding parameters, encoder, and other such things. |
---|
4453 | + # 1. Encrypt, encode, and publish segments. |
---|
4454 | |
---|
4455 | self.log("starting publish, datalen is %s" % len(newdata)) |
---|
4456 | self._status.set_size(len(newdata)) |
---|
4457 | hunk ./src/allmydata/mutable/publish.py 187 |
---|
4458 | self.bad_peers = set() # peerids who have errbacked/refused requests |
---|
4459 | |
---|
4460 | self.newdata = newdata |
---|
4461 | - self.salt = os.urandom(16) |
---|
4462 | |
---|
4463 | hunk ./src/allmydata/mutable/publish.py 188 |
---|
4464 | + # This will set self.segment_size, self.num_segments, and |
---|
4465 | + # self.fec. |
---|
4466 | self.setup_encoding_parameters() |
---|
4467 | |
---|
4468 | # if we experience any surprises (writes which were rejected because |
---|
4469 | hunk ./src/allmydata/mutable/publish.py 238 |
---|
4470 | self.bad_share_checkstrings[key] = old_checkstring |
---|
4471 | self.connections[peerid] = self._servermap.connections[peerid] |
---|
4472 | |
---|
4473 | - # create the shares. We'll discard these as they are delivered. SDMF: |
---|
4474 | - # we're allowed to hold everything in memory. |
---|
4475 | + # Now, the process dovetails -- if this is an SDMF file, we need |
---|
4476 | + # to write an SDMF file. Otherwise, we need to write an MDMF |
---|
4477 | + # file. |
---|
4478 | + if self._version == MDMF_VERSION: |
---|
4479 | + return self._publish_mdmf() |
---|
4480 | + else: |
---|
4481 | + return self._publish_sdmf() |
---|
4482 | + #return self.done_deferred |
---|
4483 | + |
---|
4484 | + def _publish_mdmf(self): |
---|
4485 | + # Next, we find homes for all of the shares that we don't have |
---|
4486 | + # homes for yet. |
---|
4487 | + # TODO: Make this part do peer selection. |
---|
4488 | + self.update_goal() |
---|
4489 | + self.writers = {} |
---|
4490 | + # For each (peerid, shnum) in self.goal, we make an |
---|
4491 | + # MDMFSlotWriteProxy for that peer. We'll use this to write |
---|
4492 | + # shares to the peer. |
---|
4493 | + for key in self.goal: |
---|
4494 | + peerid, shnum = key |
---|
4495 | + write_enabler = self._node.get_write_enabler(peerid) |
---|
4496 | + renew_secret = self._node.get_renewal_secret(peerid) |
---|
4497 | + cancel_secret = self._node.get_cancel_secret(peerid) |
---|
4498 | + secrets = (write_enabler, renew_secret, cancel_secret) |
---|
4499 | + |
---|
4500 | + self.writers[shnum] = MDMFSlotWriteProxy(shnum, |
---|
4501 | + self.connections[peerid], |
---|
4502 | + self._storage_index, |
---|
4503 | + secrets, |
---|
4504 | + self._new_seqnum, |
---|
4505 | + self.required_shares, |
---|
4506 | + self.total_shares, |
---|
4507 | + self.segment_size, |
---|
4508 | + len(self.newdata)) |
---|
4509 | + if (peerid, shnum) in self._servermap.servermap: |
---|
4510 | + old_versionid, old_timestamp = self._servermap.servermap[key] |
---|
4511 | + (old_seqnum, old_root_hash, old_salt, old_segsize, |
---|
4512 | + old_datalength, old_k, old_N, old_prefix, |
---|
4513 | + old_offsets_tuple) = old_versionid |
---|
4514 | + old_checkstring = pack_checkstring(old_seqnum, |
---|
4515 | + old_root_hash, |
---|
4516 | + old_salt, 1) |
---|
4517 | + self.writers[shnum].set_checkstring(old_checkstring) |
---|
4518 | + |
---|
4519 | + # Now, we start pushing shares. |
---|
4520 | + self._status.timings["setup"] = time.time() - self._started |
---|
4521 | + def _start_pushing(res): |
---|
4522 | + self._started_pushing = time.time() |
---|
4523 | + return res |
---|
4524 | + |
---|
4525 | + # First, we encrypt, encode, and publish the shares that we need |
---|
4526 | + # to encrypt, encode, and publish. |
---|
4527 | + |
---|
4528 | + # This will eventually hold the block hash chain for each share |
---|
4529 | + # that we publish. We define it this way so that empty publishes |
---|
4530 | + # will still have something to write to the remote slot. |
---|
4531 | + self.blockhashes = dict([(i, []) for i in xrange(self.total_shares)]) |
---|
4532 | + self.sharehash_leaves = None # eventually [sharehashes] |
---|
4533 | + self.sharehashes = {} # shnum -> [sharehash leaves necessary to |
---|
4534 | + # validate the share] |
---|
4535 | |
---|
4536 | hunk ./src/allmydata/mutable/publish.py 299 |
---|
4537 | + d = defer.succeed(None) |
---|
4538 | + self.log("Starting push") |
---|
4539 | + for i in xrange(self.num_segments - 1): |
---|
4540 | + d.addCallback(lambda ignored, i=i: |
---|
4541 | + self.push_segment(i)) |
---|
4542 | + d.addCallback(self._turn_barrier) |
---|
4543 | + # We have at least one segment, so we will have a tail segment |
---|
4544 | + if self.num_segments > 0: |
---|
4545 | + d.addCallback(lambda ignored: |
---|
4546 | + self.push_tail_segment()) |
---|
4547 | + |
---|
4548 | + d.addCallback(lambda ignored: |
---|
4549 | + self.push_encprivkey()) |
---|
4550 | + d.addCallback(lambda ignored: |
---|
4551 | + self.push_blockhashes()) |
---|
4552 | + d.addCallback(lambda ignored: |
---|
4553 | + self.push_salthashes()) |
---|
4554 | + d.addCallback(lambda ignored: |
---|
4555 | + self.push_sharehashes()) |
---|
4556 | + d.addCallback(lambda ignored: |
---|
4557 | + self.push_toplevel_hashes_and_signature()) |
---|
4558 | + d.addCallback(lambda ignored: |
---|
4559 | + self.finish_publishing()) |
---|
4560 | + return d |
---|
4561 | + |
---|
4562 | + |
---|
4563 | + def _publish_sdmf(self): |
---|
4564 | self._status.timings["setup"] = time.time() - self._started |
---|
4565 | hunk ./src/allmydata/mutable/publish.py 327 |
---|
4566 | + self.salt = os.urandom(16) |
---|
4567 | + |
---|
4568 | d = self._encrypt_and_encode() |
---|
4569 | d.addCallback(self._generate_shares) |
---|
4570 | def _start_pushing(res): |
---|
4571 | hunk ./src/allmydata/mutable/publish.py 340 |
---|
4572 | |
---|
4573 | return self.done_deferred |
---|
4574 | |
---|
4575 | + |
---|
4576 | def setup_encoding_parameters(self): |
---|
4577 | hunk ./src/allmydata/mutable/publish.py 342 |
---|
4578 | - segment_size = len(self.newdata) |
---|
4579 | + if self._version == MDMF_VERSION: |
---|
4580 | + segment_size = DEFAULT_MAX_SEGMENT_SIZE # 128 KiB by default |
---|
4581 | + else: |
---|
4582 | + segment_size = len(self.newdata) # SDMF is only one segment |
---|
4583 | # this must be a multiple of self.required_shares |
---|
4584 | segment_size = mathutil.next_multiple(segment_size, |
---|
4585 | self.required_shares) |
---|
4586 | hunk ./src/allmydata/mutable/publish.py 355 |
---|
4587 | segment_size) |
---|
4588 | else: |
---|
4589 | self.num_segments = 0 |
---|
4590 | - assert self.num_segments in [0, 1,] # SDMF restrictions |
---|
4591 | + if self._version == SDMF_VERSION: |
---|
4592 | + assert self.num_segments in (0, 1) # SDMF |
---|
4593 | + return |
---|
4594 | + # calculate the tail segment size. |
---|
4595 | + self.tail_segment_size = len(self.newdata) % segment_size |
---|
4596 | + |
---|
4597 | + if self.tail_segment_size == 0: |
---|
4598 | + # The tail segment is the same size as the other segments. |
---|
4599 | + self.tail_segment_size = segment_size |
---|
4600 | + |
---|
4601 | + # We'll make an encoder ahead-of-time for the normal-sized |
---|
4602 | + # segments (defined as any segment of segment_size size. |
---|
4603 | + # (the part of the code that puts the tail segment will make its |
---|
4604 | + # own encoder for that part) |
---|
4605 | + fec = codec.CRSEncoder() |
---|
4606 | + fec.set_params(self.segment_size, |
---|
4607 | + self.required_shares, self.total_shares) |
---|
4608 | + self.piece_size = fec.get_block_size() |
---|
4609 | + self.fec = fec |
---|
4610 | + # This is not technically part of the encoding parameters, but |
---|
4611 | + # that we are setting up the encoder and encoding parameters is |
---|
4612 | + # a good indicator that we will soon need it. |
---|
4613 | + self.salt_hashes = [] |
---|
4614 | + |
---|
4615 | + |
---|
4616 | + def push_segment(self, segnum): |
---|
4617 | + started = time.time() |
---|
4618 | + segsize = self.segment_size |
---|
4619 | + self.log("Pushing segment %d of %d" % (segnum + 1, self.num_segments)) |
---|
4620 | + data = self.newdata[segsize * segnum:segsize*(segnum + 1)] |
---|
4621 | + assert len(data) == segsize |
---|
4622 | + |
---|
4623 | + salt = os.urandom(16) |
---|
4624 | + self.salt_hashes.append(hashutil.mutable_salt_hash(salt)) |
---|
4625 | + |
---|
4626 | + key = hashutil.ssk_readkey_data_hash(salt, self.readkey) |
---|
4627 | + enc = AES(key) |
---|
4628 | + crypttext = enc.process(data) |
---|
4629 | + assert len(crypttext) == len(data) |
---|
4630 | + |
---|
4631 | + now = time.time() |
---|
4632 | + self._status.timings["encrypt"] = now - started |
---|
4633 | + started = now |
---|
4634 | + |
---|
4635 | + # now apply FEC |
---|
4636 | + |
---|
4637 | + self._status.set_status("Encoding") |
---|
4638 | + crypttext_pieces = [None] * self.required_shares |
---|
4639 | + piece_size = self.piece_size |
---|
4640 | + for i in range(len(crypttext_pieces)): |
---|
4641 | + offset = i * piece_size |
---|
4642 | + piece = crypttext[offset:offset+piece_size] |
---|
4643 | + piece = piece + "\x00"*(piece_size - len(piece)) # padding |
---|
4644 | + crypttext_pieces[i] = piece |
---|
4645 | + assert len(piece) == piece_size |
---|
4646 | + d = self.fec.encode(crypttext_pieces) |
---|
4647 | + def _done_encoding(res): |
---|
4648 | + elapsed = time.time() - started |
---|
4649 | + self._status.timings["encode"] = elapsed |
---|
4650 | + return res |
---|
4651 | + d.addCallback(_done_encoding) |
---|
4652 | + |
---|
4653 | + def _push_shares_and_salt(results): |
---|
4654 | + shares, shareids = results |
---|
4655 | + dl = [] |
---|
4656 | + for i in xrange(len(shares)): |
---|
4657 | + sharedata = shares[i] |
---|
4658 | + shareid = shareids[i] |
---|
4659 | + block_hash = hashutil.block_hash(sharedata) |
---|
4660 | + self.blockhashes[shareid].append(block_hash) |
---|
4661 | + |
---|
4662 | + # find the writer for this share |
---|
4663 | + d = self.writers[shareid].put_block(sharedata, segnum, salt) |
---|
4664 | + dl.append(d) |
---|
4665 | + # TODO: Naturally, we need to check on the results of these. |
---|
4666 | + return defer.DeferredList(dl) |
---|
4667 | + d.addCallback(_push_shares_and_salt) |
---|
4668 | + return d |
---|
4669 | + |
---|
4670 | + |
---|
4671 | + def push_tail_segment(self): |
---|
4672 | + # This is essentially the same as push_segment, except that we |
---|
4673 | + # don't use the cached encoder that we use elsewhere. |
---|
4674 | + self.log("Pushing tail segment") |
---|
4675 | + started = time.time() |
---|
4676 | + segsize = self.segment_size |
---|
4677 | + data = self.newdata[segsize * (self.num_segments-1):] |
---|
4678 | + assert len(data) == self.tail_segment_size |
---|
4679 | + salt = os.urandom(16) |
---|
4680 | + self.salt_hashes.append(hashutil.mutable_salt_hash(salt)) |
---|
4681 | + |
---|
4682 | + key = hashutil.ssk_readkey_data_hash(salt, self.readkey) |
---|
4683 | + enc = AES(key) |
---|
4684 | + crypttext = enc.process(data) |
---|
4685 | + assert len(crypttext) == len(data) |
---|
4686 | + |
---|
4687 | + now = time.time() |
---|
4688 | + self._status.timings['encrypt'] = now - started |
---|
4689 | + started = now |
---|
4690 | + |
---|
4691 | + self._status.set_status("Encoding") |
---|
4692 | + tail_fec = codec.CRSEncoder() |
---|
4693 | + tail_fec.set_params(self.tail_segment_size, |
---|
4694 | + self.required_shares, |
---|
4695 | + self.total_shares) |
---|
4696 | + |
---|
4697 | + crypttext_pieces = [None] * self.required_shares |
---|
4698 | + piece_size = tail_fec.get_block_size() |
---|
4699 | + for i in range(len(crypttext_pieces)): |
---|
4700 | + offset = i * piece_size |
---|
4701 | + piece = crypttext[offset:offset+piece_size] |
---|
4702 | + piece = piece + "\x00"*(piece_size - len(piece)) # padding |
---|
4703 | + crypttext_pieces[i] = piece |
---|
4704 | + assert len(piece) == piece_size |
---|
4705 | + d = tail_fec.encode(crypttext_pieces) |
---|
4706 | + def _push_shares_and_salt(results): |
---|
4707 | + shares, shareids = results |
---|
4708 | + dl = [] |
---|
4709 | + for i in xrange(len(shares)): |
---|
4710 | + sharedata = shares[i] |
---|
4711 | + shareid = shareids[i] |
---|
4712 | + block_hash = hashutil.block_hash(sharedata) |
---|
4713 | + self.blockhashes[shareid].append(block_hash) |
---|
4714 | + # find the writer for this share |
---|
4715 | + d = self.writers[shareid].put_block(sharedata, |
---|
4716 | + self.num_segments - 1, |
---|
4717 | + salt) |
---|
4718 | + dl.append(d) |
---|
4719 | + # TODO: Naturally, we need to check on the results of these. |
---|
4720 | + return defer.DeferredList(dl) |
---|
4721 | + d.addCallback(_push_shares_and_salt) |
---|
4722 | + return d |
---|
4723 | + |
---|
4724 | + |
---|
4725 | + def push_encprivkey(self): |
---|
4726 | + started = time.time() |
---|
4727 | + encprivkey = self._encprivkey |
---|
4728 | + dl = [] |
---|
4729 | + def _spy_on_writer(results): |
---|
4730 | + print results |
---|
4731 | + return results |
---|
4732 | + for shnum, writer in self.writers.iteritems(): |
---|
4733 | + d = writer.put_encprivkey(encprivkey) |
---|
4734 | + dl.append(d) |
---|
4735 | + d = defer.DeferredList(dl) |
---|
4736 | + return d |
---|
4737 | + |
---|
4738 | + |
---|
4739 | + def push_blockhashes(self): |
---|
4740 | + started = time.time() |
---|
4741 | + dl = [] |
---|
4742 | + def _spy_on_results(results): |
---|
4743 | + print results |
---|
4744 | + return results |
---|
4745 | + self.sharehash_leaves = [None] * len(self.blockhashes) |
---|
4746 | + for shnum, blockhashes in self.blockhashes.iteritems(): |
---|
4747 | + t = hashtree.HashTree(blockhashes) |
---|
4748 | + self.blockhashes[shnum] = list(t) |
---|
4749 | + # set the leaf for future use. |
---|
4750 | + self.sharehash_leaves[shnum] = t[0] |
---|
4751 | + d = self.writers[shnum].put_blockhashes(self.blockhashes[shnum]) |
---|
4752 | + dl.append(d) |
---|
4753 | + d = defer.DeferredList(dl) |
---|
4754 | + return d |
---|
4755 | + |
---|
4756 | + |
---|
4757 | + def push_salthashes(self): |
---|
4758 | + started = time.time() |
---|
4759 | + dl = [] |
---|
4760 | + t = hashtree.HashTree(self.salt_hashes) |
---|
4761 | + pushing = list(t) |
---|
4762 | + for shnum in self.writers.iterkeys(): |
---|
4763 | + d = self.writers[shnum].put_salthashes(t) |
---|
4764 | + dl.append(d) |
---|
4765 | + dl = defer.DeferredList(dl) |
---|
4766 | + return dl |
---|
4767 | + |
---|
4768 | + |
---|
4769 | + def push_sharehashes(self): |
---|
4770 | + share_hash_tree = hashtree.HashTree(self.sharehash_leaves) |
---|
4771 | + share_hash_chain = {} |
---|
4772 | + ds = [] |
---|
4773 | + def _spy_on_results(results): |
---|
4774 | + print results |
---|
4775 | + return results |
---|
4776 | + for shnum in xrange(len(self.sharehash_leaves)): |
---|
4777 | + needed_indices = share_hash_tree.needed_hashes(shnum, |
---|
4778 | + include_leaf=True) |
---|
4779 | + self.sharehashes[shnum] = dict( [ (i, share_hash_tree[i]) |
---|
4780 | + for i in needed_indices] ) |
---|
4781 | + d = self.writers[shnum].put_sharehashes(self.sharehashes[shnum]) |
---|
4782 | + ds.append(d) |
---|
4783 | + self.root_hash = share_hash_tree[0] |
---|
4784 | + d = defer.DeferredList(ds) |
---|
4785 | + return d |
---|
4786 | + |
---|
4787 | + |
---|
4788 | + def push_toplevel_hashes_and_signature(self): |
---|
4789 | + # We need to to three things here: |
---|
4790 | + # - Push the root hash and salt hash |
---|
4791 | + # - Get the checkstring of the resulting layout; sign that. |
---|
4792 | + # - Push the signature |
---|
4793 | + ds = [] |
---|
4794 | + def _spy_on_results(results): |
---|
4795 | + print results |
---|
4796 | + return results |
---|
4797 | + for shnum in xrange(self.total_shares): |
---|
4798 | + d = self.writers[shnum].put_root_hash(self.root_hash) |
---|
4799 | + ds.append(d) |
---|
4800 | + d = defer.DeferredList(ds) |
---|
4801 | + def _make_and_place_signature(ignored): |
---|
4802 | + signable = self.writers[0].get_signable() |
---|
4803 | + self.signature = self._privkey.sign(signable) |
---|
4804 | + |
---|
4805 | + ds = [] |
---|
4806 | + for (shnum, writer) in self.writers.iteritems(): |
---|
4807 | + d = writer.put_signature(self.signature) |
---|
4808 | + ds.append(d) |
---|
4809 | + return defer.DeferredList(ds) |
---|
4810 | + d.addCallback(_make_and_place_signature) |
---|
4811 | + return d |
---|
4812 | + |
---|
4813 | + |
---|
4814 | + def finish_publishing(self): |
---|
4815 | + # We're almost done -- we just need to put the verification key |
---|
4816 | + # and the offsets |
---|
4817 | + ds = [] |
---|
4818 | + verification_key = self._pubkey.serialize() |
---|
4819 | + |
---|
4820 | + def _spy_on_results(results): |
---|
4821 | + print results |
---|
4822 | + return results |
---|
4823 | + for (shnum, writer) in self.writers.iteritems(): |
---|
4824 | + d = writer.put_verification_key(verification_key) |
---|
4825 | + d.addCallback(lambda ignored, writer=writer: |
---|
4826 | + writer.finish_publishing()) |
---|
4827 | + ds.append(d) |
---|
4828 | + return defer.DeferredList(ds) |
---|
4829 | + |
---|
4830 | + |
---|
4831 | + def _turn_barrier(self, res): |
---|
4832 | + # putting this method in a Deferred chain imposes a guaranteed |
---|
4833 | + # reactor turn between the pre- and post- portions of that chain. |
---|
4834 | + # This can be useful to limit memory consumption: since Deferreds do |
---|
4835 | + # not do tail recursion, code which uses defer.succeed(result) for |
---|
4836 | + # consistency will cause objects to live for longer than you might |
---|
4837 | + # normally expect. |
---|
4838 | + return fireEventually(res) |
---|
4839 | + |
---|
4840 | |
---|
4841 | def _fatal_error(self, f): |
---|
4842 | self.log("error during loop", failure=f, level=log.UNUSUAL) |
---|
4843 | hunk ./src/allmydata/mutable/publish.py 740 |
---|
4844 | self.log_goal(self.goal, "after update: ") |
---|
4845 | |
---|
4846 | |
---|
4847 | - |
---|
4848 | def _encrypt_and_encode(self): |
---|
4849 | # this returns a Deferred that fires with a list of (sharedata, |
---|
4850 | # sharenum) tuples. TODO: cache the ciphertext, only produce the |
---|
4851 | hunk ./src/allmydata/mutable/publish.py 781 |
---|
4852 | d.addCallback(_done_encoding) |
---|
4853 | return d |
---|
4854 | |
---|
4855 | + |
---|
4856 | def _generate_shares(self, shares_and_shareids): |
---|
4857 | # this sets self.shares and self.root_hash |
---|
4858 | self.log("_generate_shares") |
---|
4859 | hunk ./src/allmydata/mutable/publish.py 815 |
---|
4860 | share_hash_tree = hashtree.HashTree(share_hash_leaves) |
---|
4861 | share_hash_chain = {} |
---|
4862 | for shnum in range(self.total_shares): |
---|
4863 | - needed_hashes = share_hash_tree.needed_hashes(shnum) |
---|
4864 | + needed_hashes = share_hash_tree.needed_hashes(shnum, |
---|
4865 | + include_leaf=True) |
---|
4866 | share_hash_chain[shnum] = dict( [ (i, share_hash_tree[i]) |
---|
4867 | for i in needed_hashes ] ) |
---|
4868 | root_hash = share_hash_tree[0] |
---|
4869 | hunk ./src/allmydata/mutable/publish.py 1170 |
---|
4870 | self._status.set_progress(1.0) |
---|
4871 | eventually(self.done_deferred.callback, res) |
---|
4872 | |
---|
4873 | - |
---|
4874 | } |
---|
4875 | [Add objects for MDMF shares in support of a new segmented uploader |
---|
4876 | Kevan Carstensen <kevan@isnotajoke.com>**20100623001912 |
---|
4877 | Ignore-this: e4a0a7251899450114b569d1c8a01899 |
---|
4878 | |
---|
4879 | This patch adds the following: |
---|
4880 | - MDMFSlotWriteProxy, which can write MDMF shares to the storage |
---|
4881 | server in the new format. |
---|
4882 | - MDMFSlotReadProxy, which can read both SDMF and MDMF shares from |
---|
4883 | the storage server. |
---|
4884 | |
---|
4885 | This patch also includes tests for these new object. |
---|
4886 | ] { |
---|
4887 | hunk ./src/allmydata/interfaces.py 7 |
---|
4888 | ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable |
---|
4889 | |
---|
4890 | HASH_SIZE=32 |
---|
4891 | +SALT_SIZE=16 |
---|
4892 | |
---|
4893 | SDMF_VERSION=0 |
---|
4894 | MDMF_VERSION=1 |
---|
4895 | hunk ./src/allmydata/mutable/layout.py 4 |
---|
4896 | |
---|
4897 | import struct |
---|
4898 | from allmydata.mutable.common import NeedMoreDataError, UnknownVersionError |
---|
4899 | +from allmydata.interfaces import HASH_SIZE, SALT_SIZE, SDMF_VERSION, \ |
---|
4900 | + MDMF_VERSION |
---|
4901 | +from allmydata.util import mathutil |
---|
4902 | +from twisted.python import failure |
---|
4903 | +from twisted.internet import defer |
---|
4904 | + |
---|
4905 | + |
---|
4906 | +# These strings describe the format of the packed structs they help process |
---|
4907 | +# Here's what they mean: |
---|
4908 | +# |
---|
4909 | +# PREFIX: |
---|
4910 | +# >: Big-endian byte order; the most significant byte is first (leftmost). |
---|
4911 | +# B: The version information; an 8 bit version identifier. Stored as |
---|
4912 | +# an unsigned char. This is currently 00 00 00 00; our modifications |
---|
4913 | +# will turn it into 00 00 00 01. |
---|
4914 | +# Q: The sequence number; this is sort of like a revision history for |
---|
4915 | +# mutable files; they start at 1 and increase as they are changed after |
---|
4916 | +# being uploaded. Stored as an unsigned long long, which is 8 bytes in |
---|
4917 | +# length. |
---|
4918 | +# 32s: The root hash of the share hash tree. We use sha-256d, so we use 32 |
---|
4919 | +# characters = 32 bytes to store the value. |
---|
4920 | +# 16s: The salt for the readkey. This is a 16-byte random value, stored as |
---|
4921 | +# 16 characters. |
---|
4922 | +# |
---|
4923 | +# SIGNED_PREFIX additions, things that are covered by the signature: |
---|
4924 | +# B: The "k" encoding parameter. We store this as an 8-bit character, |
---|
4925 | +# which is convenient because our erasure coding scheme cannot |
---|
4926 | +# encode if you ask for more than 255 pieces. |
---|
4927 | +# B: The "N" encoding parameter. Stored as an 8-bit character for the |
---|
4928 | +# same reasons as above. |
---|
4929 | +# Q: The segment size of the uploaded file. This will essentially be the |
---|
4930 | +# length of the file in SDMF. An unsigned long long, so we can store |
---|
4931 | +# files of quite large size. |
---|
4932 | +# Q: The data length of the uploaded file. Modulo padding, this will be |
---|
4933 | +# the same of the data length field. Like the data length field, it is |
---|
4934 | +# an unsigned long long and can be quite large. |
---|
4935 | +# |
---|
4936 | +# HEADER additions: |
---|
4937 | +# L: The offset of the signature of this. An unsigned long. |
---|
4938 | +# L: The offset of the share hash chain. An unsigned long. |
---|
4939 | +# L: The offset of the block hash tree. An unsigned long. |
---|
4940 | +# L: The offset of the share data. An unsigned long. |
---|
4941 | +# Q: The offset of the encrypted private key. An unsigned long long, to |
---|
4942 | +# account for the possibility of a lot of share data. |
---|
4943 | +# Q: The offset of the EOF. An unsigned long long, to account for the |
---|
4944 | +# possibility of a lot of share data. |
---|
4945 | +# |
---|
4946 | +# After all of these, we have the following: |
---|
4947 | +# - The verification key: Occupies the space between the end of the header |
---|
4948 | +# and the start of the signature (i.e.: data[HEADER_LENGTH:o['signature']]. |
---|
4949 | +# - The signature, which goes from the signature offset to the share hash |
---|
4950 | +# chain offset. |
---|
4951 | +# - The share hash chain, which goes from the share hash chain offset to |
---|
4952 | +# the block hash tree offset. |
---|
4953 | +# - The share data, which goes from the share data offset to the encrypted |
---|
4954 | +# private key offset. |
---|
4955 | +# - The encrypted private key offset, which goes until the end of the file. |
---|
4956 | +# |
---|
4957 | +# The block hash tree in this encoding has only one share, so the offset of |
---|
4958 | +# the share data will be 32 bits more than the offset of the block hash tree. |
---|
4959 | +# Given this, we may need to check to see how many bytes a reasonably sized |
---|
4960 | +# block hash tree will take up. |
---|
4961 | |
---|
4962 | PREFIX = ">BQ32s16s" # each version has a different prefix |
---|
4963 | SIGNED_PREFIX = ">BQ32s16s BBQQ" # this is covered by the signature |
---|
4964 | hunk ./src/allmydata/mutable/layout.py 191 |
---|
4965 | return (share_hash_chain, block_hash_tree, share_data) |
---|
4966 | |
---|
4967 | |
---|
4968 | -def pack_checkstring(seqnum, root_hash, IV): |
---|
4969 | +def pack_checkstring(seqnum, root_hash, IV, version=0): |
---|
4970 | return struct.pack(PREFIX, |
---|
4971 | hunk ./src/allmydata/mutable/layout.py 193 |
---|
4972 | - 0, # version, |
---|
4973 | + version, |
---|
4974 | seqnum, |
---|
4975 | root_hash, |
---|
4976 | IV) |
---|
4977 | hunk ./src/allmydata/mutable/layout.py 266 |
---|
4978 | encprivkey]) |
---|
4979 | return final_share |
---|
4980 | |
---|
4981 | +def pack_prefix(seqnum, root_hash, IV, |
---|
4982 | + required_shares, total_shares, |
---|
4983 | + segment_size, data_length): |
---|
4984 | + prefix = struct.pack(SIGNED_PREFIX, |
---|
4985 | + 0, # version, |
---|
4986 | + seqnum, |
---|
4987 | + root_hash, |
---|
4988 | + IV, |
---|
4989 | + required_shares, |
---|
4990 | + total_shares, |
---|
4991 | + segment_size, |
---|
4992 | + data_length, |
---|
4993 | + ) |
---|
4994 | + return prefix |
---|
4995 | + |
---|
4996 | + |
---|
4997 | +MDMFHEADER = ">BQ32s32sBBQQ LQQQQQQQ" |
---|
4998 | +MDMFHEADERWITHOUTOFFSETS = ">BQ32s32sBBQQ" |
---|
4999 | +MDMFHEADERSIZE = struct.calcsize(MDMFHEADER) |
---|
5000 | +MDMFCHECKSTRING = ">BQ32s32s" |
---|
5001 | +MDMFSIGNABLEHEADER = ">BQ32s32sBBQQ" |
---|
5002 | +MDMFOFFSETS = ">LQQQQQQQ" |
---|
5003 | + |
---|
5004 | +class MDMFSlotWriteProxy: |
---|
5005 | + #implements(IMutableSlotWriter) TODO |
---|
5006 | + |
---|
5007 | + """ |
---|
5008 | + I represent a remote write slot for an MDMF mutable file. |
---|
5009 | + |
---|
5010 | + I abstract away from my caller the details of block and salt |
---|
5011 | + management, and the implementation of the on-disk format for MDMF |
---|
5012 | + shares. |
---|
5013 | + """ |
---|
5014 | + |
---|
5015 | + # Expected layout, MDMF: |
---|
5016 | + # offset: size: name: |
---|
5017 | + #-- signed part -- |
---|
5018 | + # 0 1 version number (01) |
---|
5019 | + # 1 8 sequence number |
---|
5020 | + # 9 32 share tree root hash |
---|
5021 | + # 41 32 salt tree root hash |
---|
5022 | + # 73 1 The "k" encoding parameter |
---|
5023 | + # 74 1 The "N" encoding parameter |
---|
5024 | + # 75 8 The segment size of the uploaded file |
---|
5025 | + # 83 8 The data length of the uploaded file |
---|
5026 | + #-- end signed part -- |
---|
5027 | + # 91 4 The offset of the share data |
---|
5028 | + # 95 8 The offset of the encrypted private key |
---|
5029 | + # 103 8 The offset of the block hash tree |
---|
5030 | + # 111 8 The offset of the salt hash tree |
---|
5031 | + # 119 8 The offset of the signature hash chain |
---|
5032 | + # 127 8 The offset of the signature |
---|
5033 | + # 135 8 The offset of the verification key |
---|
5034 | + # 143 8 offset of the EOF |
---|
5035 | + # |
---|
5036 | + # followed by salts, share data, the encrypted private key, the |
---|
5037 | + # block hash tree, the salt hash tree, the share hash chain, a |
---|
5038 | + # signature over the first eight fields, and a verification key. |
---|
5039 | + # |
---|
5040 | + # The checkstring is the first four fields -- the version number, |
---|
5041 | + # sequence number, root hash and root salt hash. This is consistent |
---|
5042 | + # in meaning to what we have with SDMF files, except now instead of |
---|
5043 | + # using the literal salt, we use a value derived from all of the |
---|
5044 | + # salts. |
---|
5045 | + # |
---|
5046 | + # The ordering of the offsets is different to reflect the dependencies |
---|
5047 | + # that we'll run into with an MDMF file. The expected write flow is |
---|
5048 | + # something like this: |
---|
5049 | + # |
---|
5050 | + # 0: Initialize with the sequence number, encoding |
---|
5051 | + # parameters and data length. From this, we can deduce the |
---|
5052 | + # number of segments, and from that we can deduce the size of |
---|
5053 | + # the AES salt field, telling us where to write AES salts, and |
---|
5054 | + # where to write share data. We can also figure out where the |
---|
5055 | + # encrypted private key should go, because we can figure out |
---|
5056 | + # how big the share data will be. |
---|
5057 | + # |
---|
5058 | + # 1: Encrypt, encode, and upload the file in chunks. Do something |
---|
5059 | + # like |
---|
5060 | + # |
---|
5061 | + # put_block(data, segnum, salt) |
---|
5062 | + # |
---|
5063 | + # to write a block and a salt to the disk. We can do both of |
---|
5064 | + # these operations now because we have enough of the offsets to |
---|
5065 | + # know where to put them. |
---|
5066 | + # |
---|
5067 | + # 2: Put the encrypted private key. Use: |
---|
5068 | + # |
---|
5069 | + # put_encprivkey(encprivkey) |
---|
5070 | + # |
---|
5071 | + # Now that we know the length of the private key, we can fill |
---|
5072 | + # in the offset for the block hash tree. |
---|
5073 | + # |
---|
5074 | + # 3: We're now in a position to upload the block hash tree for |
---|
5075 | + # a share. Put that using something like: |
---|
5076 | + # |
---|
5077 | + # put_blockhashes(block_hash_tree) |
---|
5078 | + # |
---|
5079 | + # Note that block_hash_tree is a list of hashes -- we'll take |
---|
5080 | + # care of the details of serializing that appropriately. When |
---|
5081 | + # we get the block hash tree, we are also in a position to |
---|
5082 | + # calculate the offset for the share hash chain, and fill that |
---|
5083 | + # into the offsets table. |
---|
5084 | + # |
---|
5085 | + # 4: At the same time, we're in a position to upload the salt hash |
---|
5086 | + # tree. This is a Merkle tree over all of the salts. We use a |
---|
5087 | + # Merkle tree so that we can validate each block,salt pair as |
---|
5088 | + # we download them later. We do this using |
---|
5089 | + # |
---|
5090 | + # put_salthashes(salt_hash_tree) |
---|
5091 | + # |
---|
5092 | + # When you do this, I automatically put the root of the tree |
---|
5093 | + # (the hash at index 0 of the list) in its appropriate slot in |
---|
5094 | + # the signed prefix of the share. |
---|
5095 | + # |
---|
5096 | + # 5: We're now in a position to upload the share hash chain for |
---|
5097 | + # a share. Do that with something like: |
---|
5098 | + # |
---|
5099 | + # put_sharehashes(share_hash_chain) |
---|
5100 | + # |
---|
5101 | + # share_hash_chain should be a dictionary mapping shnums to |
---|
5102 | + # 32-byte hashes -- the wrapper handles serialization. |
---|
5103 | + # We'll know where to put the signature at this point, also. |
---|
5104 | + # The root of this tree will be put explicitly in the next |
---|
5105 | + # step. |
---|
5106 | + # |
---|
5107 | + # TODO: Why? Why not just include it in the tree here? |
---|
5108 | + # |
---|
5109 | + # 6: Before putting the signature, we must first put the |
---|
5110 | + # root_hash. Do this with: |
---|
5111 | + # |
---|
5112 | + # put_root_hash(root_hash). |
---|
5113 | + # |
---|
5114 | + # In terms of knowing where to put this value, it was always |
---|
5115 | + # possible to place it, but it makes sense semantically to |
---|
5116 | + # place it after the share hash tree, so that's why you do it |
---|
5117 | + # in this order. |
---|
5118 | + # |
---|
5119 | + # 6: With the root hash put, we can now sign the header. Use: |
---|
5120 | + # |
---|
5121 | + # get_signable() |
---|
5122 | + # |
---|
5123 | + # to get the part of the header that you want to sign, and use: |
---|
5124 | + # |
---|
5125 | + # put_signature(signature) |
---|
5126 | + # |
---|
5127 | + # to write your signature to the remote server. |
---|
5128 | + # |
---|
5129 | + # 6: Add the verification key, and finish. Do: |
---|
5130 | + # |
---|
5131 | + # put_verification_key(key) |
---|
5132 | + # |
---|
5133 | + # and |
---|
5134 | + # |
---|
5135 | + # finish_publish() |
---|
5136 | + # |
---|
5137 | + # Checkstring management: |
---|
5138 | + # |
---|
5139 | + # To write to a mutable slot, we have to provide test vectors to ensure |
---|
5140 | + # that we are writing to the same data that we think we are. These |
---|
5141 | + # vectors allow us to detect uncoordinated writes; that is, writes |
---|
5142 | + # where both we and some other shareholder are writing to the |
---|
5143 | + # mutable slot, and to report those back to the parts of the program |
---|
5144 | + # doing the writing. |
---|
5145 | + # |
---|
5146 | + # With SDMF, this was easy -- all of the share data was written in |
---|
5147 | + # one go, so it was easy to detect uncoordinated writes, and we only |
---|
5148 | + # had to do it once. With MDMF, not all of the file is written at |
---|
5149 | + # once. |
---|
5150 | + # |
---|
5151 | + # If a share is new, we write out as much of the header as we can |
---|
5152 | + # before writing out anything else. This gives other writers a |
---|
5153 | + # canary that they can use to detect uncoordinated writes, and, if |
---|
5154 | + # they do the same thing, gives us the same canary. We them update |
---|
5155 | + # the share. We won't be able to write out two fields of the header |
---|
5156 | + # -- the share tree hash and the salt hash -- until we finish |
---|
5157 | + # writing out the share. We only require the writer to provide the |
---|
5158 | + # initial checkstring, and keep track of what it should be after |
---|
5159 | + # updates ourselves. |
---|
5160 | + # |
---|
5161 | + # If we haven't written anything yet, then on the first write (which |
---|
5162 | + # will probably be a block + salt of a share), we'll also write out |
---|
5163 | + # the header. On subsequent passes, we'll expect to see the header. |
---|
5164 | + # This changes in two places: |
---|
5165 | + # |
---|
5166 | + # - When we write out the salt hash |
---|
5167 | + # - When we write out the root of the share hash tree |
---|
5168 | + # |
---|
5169 | + # since these values will change the header. It is possible that we |
---|
5170 | + # can just make those be written in one operation to minimize |
---|
5171 | + # disruption. |
---|
5172 | + def __init__(self, |
---|
5173 | + shnum, |
---|
5174 | + rref, # a remote reference to a storage server |
---|
5175 | + storage_index, |
---|
5176 | + secrets, # (write_enabler, renew_secret, cancel_secret) |
---|
5177 | + seqnum, # the sequence number of the mutable file |
---|
5178 | + required_shares, |
---|
5179 | + total_shares, |
---|
5180 | + segment_size, |
---|
5181 | + data_length): # the length of the original file |
---|
5182 | + self._shnum = shnum |
---|
5183 | + self._rref = rref |
---|
5184 | + self._storage_index = storage_index |
---|
5185 | + self._seqnum = seqnum |
---|
5186 | + self._required_shares = required_shares |
---|
5187 | + assert self._shnum >= 0 and self._shnum < total_shares |
---|
5188 | + self._total_shares = total_shares |
---|
5189 | + # We build up the offset table as we write things. It is the |
---|
5190 | + # last thing we write to the remote server. |
---|
5191 | + self._offsets = {} |
---|
5192 | + self._testvs = [] |
---|
5193 | + self._secrets = secrets |
---|
5194 | + # The segment size needs to be a multiple of the k parameter -- |
---|
5195 | + # any padding should have been carried out by the publisher |
---|
5196 | + # already. |
---|
5197 | + assert segment_size % required_shares == 0 |
---|
5198 | + self._segment_size = segment_size |
---|
5199 | + self._data_length = data_length |
---|
5200 | + |
---|
5201 | + # These are set later -- we define them here so that we can |
---|
5202 | + # check for their existence easily |
---|
5203 | + |
---|
5204 | + # This is the root of the share hash tree -- the Merkle tree |
---|
5205 | + # over the roots of the block hash trees computed for shares in |
---|
5206 | + # this upload. |
---|
5207 | + self._root_hash = None |
---|
5208 | + # This is the root of the salt hash tree -- the Merkle tree over |
---|
5209 | + # the hashes of the salts used for each segment of the file. |
---|
5210 | + self._salt_hash = None |
---|
5211 | + |
---|
5212 | + # We haven't yet written anything to the remote bucket. By |
---|
5213 | + # setting this, we tell the _write method as much. The write |
---|
5214 | + # method will then know that it also needs to add a write vector |
---|
5215 | + # for the checkstring (or what we have of it) to the first write |
---|
5216 | + # request. We'll then record that value for future use. If |
---|
5217 | + # we're expecting something to be there already, we need to call |
---|
5218 | + # set_checkstring before we write anything to tell the first |
---|
5219 | + # write about that. |
---|
5220 | + self._written = False |
---|
5221 | + |
---|
5222 | + # When writing data to the storage servers, we get a read vector |
---|
5223 | + # for free. We'll read the checkstring, which will help us |
---|
5224 | + # figure out what's gone wrong if a write fails. |
---|
5225 | + self._readv = [(0, struct.calcsize(MDMFCHECKSTRING))] |
---|
5226 | + |
---|
5227 | + # We calculate the number of segments because it tells us |
---|
5228 | + # where the salt part of the file ends/share segment begins, |
---|
5229 | + # and also because it provides a useful amount of bounds checking. |
---|
5230 | + self._num_segments = mathutil.div_ceil(self._data_length, |
---|
5231 | + self._segment_size) |
---|
5232 | + self._block_size = self._segment_size / self._required_shares |
---|
5233 | + # We also calculate the share size, to help us with block |
---|
5234 | + # constraints later. |
---|
5235 | + tail_size = self._data_length % self._segment_size |
---|
5236 | + if not tail_size: |
---|
5237 | + self._tail_block_size = self._block_size |
---|
5238 | + else: |
---|
5239 | + self._tail_block_size = mathutil.next_multiple(tail_size, |
---|
5240 | + self._required_shares) |
---|
5241 | + self._tail_block_size /= self._required_shares |
---|
5242 | + |
---|
5243 | + # We already know where the AES salts start; right after the end |
---|
5244 | + # of the header (which is defined as the signable part + the offsets) |
---|
5245 | + # We need to calculate where the share data starts, since we're |
---|
5246 | + # responsible (after this method) for being able to write it. |
---|
5247 | + self._offsets['share-data'] = MDMFHEADERSIZE |
---|
5248 | + self._offsets['share-data'] += self._num_segments * SALT_SIZE |
---|
5249 | + # We can also calculate where the encrypted private key begins |
---|
5250 | + # from what we know know. |
---|
5251 | + self._offsets['enc_privkey'] = self._offsets['share-data'] |
---|
5252 | + self._offsets['enc_privkey'] += self._block_size * (self._num_segments - 1) |
---|
5253 | + self._offsets['enc_privkey'] += self._tail_block_size |
---|
5254 | + # We'll wait for the rest. Callers can now call my "put_block" and |
---|
5255 | + # "set_checkstring" methods. |
---|
5256 | + |
---|
5257 | + |
---|
5258 | + def set_checkstring(self, checkstring): |
---|
5259 | + """ |
---|
5260 | + Set checkstring checkstring for the given shnum. |
---|
5261 | + |
---|
5262 | + By default, I assume that I am writing new shares to the grid. |
---|
5263 | + If you don't explcitly set your own checkstring, I will use |
---|
5264 | + one that requires that the remote share not exist. You will want |
---|
5265 | + to use this method if you are updating a share in-place; |
---|
5266 | + otherwise, writes will fail. |
---|
5267 | + """ |
---|
5268 | + # You're allowed to overwrite checkstrings with this method; |
---|
5269 | + # I assume that users know what they are doing when they call |
---|
5270 | + # it. |
---|
5271 | + if checkstring == "": |
---|
5272 | + # We special-case this, since len("") = 0, but we need |
---|
5273 | + # length of 1 for the case of an empty share to work on the |
---|
5274 | + # storage server, which is what a checkstring that is the |
---|
5275 | + # empty string means. |
---|
5276 | + self._testvs = [] |
---|
5277 | + else: |
---|
5278 | + self._testvs = [] |
---|
5279 | + self._testvs.append((0, len(checkstring), "eq", checkstring)) |
---|
5280 | + |
---|
5281 | + |
---|
5282 | + def __repr__(self): |
---|
5283 | + return "MDMFSlotWriteProxy for share %d" % self._shnum |
---|
5284 | + |
---|
5285 | + |
---|
5286 | + def get_checkstring(self): |
---|
5287 | + """ |
---|
5288 | + Given a share number, I return a representation of what the |
---|
5289 | + checkstring for that share on the server will look like. |
---|
5290 | + """ |
---|
5291 | + if self._root_hash: |
---|
5292 | + roothash = self._root_hash |
---|
5293 | + else: |
---|
5294 | + roothash = "\x00" * 32 |
---|
5295 | + # self._salt_hash and self._root_hash means that we've written |
---|
5296 | + # both of these things to the server. self._salt_hash will be |
---|
5297 | + # set first, though, and if self._root_hash isn't also set then |
---|
5298 | + # neither of them are written to the server, so we need to leave |
---|
5299 | + # them alone. |
---|
5300 | + if self._salt_hash and self._root_hash: |
---|
5301 | + salthash = self._salt_hash |
---|
5302 | + else: |
---|
5303 | + salthash = "\x00" * 32 |
---|
5304 | + checkstring = struct.pack(MDMFCHECKSTRING, |
---|
5305 | + 1, |
---|
5306 | + self._seqnum, |
---|
5307 | + roothash, |
---|
5308 | + salthash) |
---|
5309 | + return checkstring |
---|
5310 | + |
---|
5311 | + |
---|
5312 | + def put_block(self, data, segnum, salt): |
---|
5313 | + """ |
---|
5314 | + Put the encrypted-and-encoded data segment in the slot, along |
---|
5315 | + with the salt. |
---|
5316 | + """ |
---|
5317 | + if segnum >= self._num_segments: |
---|
5318 | + raise LayoutInvalid("I won't overwrite the private key") |
---|
5319 | + if len(salt) != SALT_SIZE: |
---|
5320 | + raise LayoutInvalid("I was given a salt of size %d, but " |
---|
5321 | + "I wanted a salt of size %d") |
---|
5322 | + if segnum + 1 == self._num_segments: |
---|
5323 | + if len(data) != self._tail_block_size: |
---|
5324 | + raise LayoutInvalid("I was given the wrong size block to write") |
---|
5325 | + elif len(data) != self._block_size: |
---|
5326 | + raise LayoutInvalid("I was given the wrong size block to write") |
---|
5327 | + |
---|
5328 | + # We want to write at offsets['share-data'] + segnum * block_size. |
---|
5329 | + assert self._offsets |
---|
5330 | + assert self._offsets['share-data'] |
---|
5331 | + |
---|
5332 | + offset = self._offsets['share-data'] + segnum * self._block_size |
---|
5333 | + datavs = [tuple([offset, data])] |
---|
5334 | + # We also have to write the salt. This is at: |
---|
5335 | + salt_offset = MDMFHEADERSIZE + SALT_SIZE * segnum |
---|
5336 | + datavs.append(tuple([salt_offset, salt])) |
---|
5337 | + return self._write(datavs) |
---|
5338 | + |
---|
5339 | + |
---|
5340 | + def put_encprivkey(self, encprivkey): |
---|
5341 | + """ |
---|
5342 | + Put the encrypted private key in the remote slot. |
---|
5343 | + """ |
---|
5344 | + assert self._offsets |
---|
5345 | + assert self._offsets['enc_privkey'] |
---|
5346 | + # You shouldn't re-write the encprivkey after the block hash |
---|
5347 | + # tree is written, since that could cause the private key to run |
---|
5348 | + # into the block hash tree. Before it writes the block hash |
---|
5349 | + # tree, the block hash tree writing method writes the offset of |
---|
5350 | + # the salt hash tree. So that's a good indicator of whether or |
---|
5351 | + # not the block hash tree has been written. |
---|
5352 | + if "salt_hash_tree" in self._offsets: |
---|
5353 | + raise LayoutInvalid("You must write this before the block hash tree") |
---|
5354 | + |
---|
5355 | + self._offsets['block_hash_tree'] = self._offsets['enc_privkey'] + len(encprivkey) |
---|
5356 | + datavs = [(tuple([self._offsets['enc_privkey'], encprivkey]))] |
---|
5357 | + def _on_failure(): |
---|
5358 | + del(self._offsets['block_hash_tree']) |
---|
5359 | + return self._write(datavs, on_failure=_on_failure) |
---|
5360 | + |
---|
5361 | + |
---|
5362 | + def put_blockhashes(self, blockhashes): |
---|
5363 | + """ |
---|
5364 | + Put the block hash tree in the remote slot. |
---|
5365 | + |
---|
5366 | + The encrypted private key must be put before the block hash |
---|
5367 | + tree, since we need to know how large it is to know where the |
---|
5368 | + block hash tree should go. The block hash tree must be put |
---|
5369 | + before the salt hash tree, since its size determines the |
---|
5370 | + offset of the share hash chain. |
---|
5371 | + """ |
---|
5372 | + assert self._offsets |
---|
5373 | + assert isinstance(blockhashes, list) |
---|
5374 | + if "block_hash_tree" not in self._offsets: |
---|
5375 | + raise LayoutInvalid("You must put the encrypted private key " |
---|
5376 | + "before you put the block hash tree") |
---|
5377 | + # If written, the share hash chain causes the signature offset |
---|
5378 | + # to be defined. |
---|
5379 | + if "share_hash_chain" in self._offsets: |
---|
5380 | + raise LayoutInvalid("You must put the block hash tree before " |
---|
5381 | + "you put the salt hash tree") |
---|
5382 | + blockhashes_s = "".join(blockhashes) |
---|
5383 | + self._offsets['salt_hash_tree'] = self._offsets['block_hash_tree'] + len(blockhashes_s) |
---|
5384 | + datavs = [] |
---|
5385 | + datavs.append(tuple([self._offsets['block_hash_tree'], blockhashes_s])) |
---|
5386 | + def _on_failure(): |
---|
5387 | + del(self._offsets['salt_hash_tree']) |
---|
5388 | + return self._write(datavs, on_failure=_on_failure) |
---|
5389 | + |
---|
5390 | + |
---|
5391 | + def put_salthashes(self, salthashes): |
---|
5392 | + """ |
---|
5393 | + Put the salt hash tree in the remote slot. |
---|
5394 | + |
---|
5395 | + The block hash tree must be put before the salt hash tree, since |
---|
5396 | + its size tells us where we need to put the salt hash tree. This |
---|
5397 | + method must be called before the share hash chain can be |
---|
5398 | + uploaded, since the size of the salt hash tree tells us where |
---|
5399 | + the share hash chain can go |
---|
5400 | + """ |
---|
5401 | + assert self._offsets |
---|
5402 | + assert isinstance(salthashes, list) |
---|
5403 | + if "salt_hash_tree" not in self._offsets: |
---|
5404 | + raise LayoutInvalid("You must put the block hash tree " |
---|
5405 | + "before putting the salt hash tree") |
---|
5406 | + if "signature" in self._offsets: |
---|
5407 | + raise LayoutInvalid("You must put the salt hash tree " |
---|
5408 | + "before you put the share hash chain") |
---|
5409 | + # The root of the salt hash tree is at index 0. We'll write this when |
---|
5410 | + # we put the root hash later; we just keep track of it for now. |
---|
5411 | + self._salt_hash = salthashes[0] |
---|
5412 | + salthashes_s = "".join(salthashes[1:]) |
---|
5413 | + self._offsets['share_hash_chain'] = self._offsets['salt_hash_tree'] + len(salthashes_s) |
---|
5414 | + datavs = [] |
---|
5415 | + datavs.append(tuple([self._offsets['salt_hash_tree'], salthashes_s])) |
---|
5416 | + def _on_failure(): |
---|
5417 | + del(self._offsets['share_hash_chain']) |
---|
5418 | + return self._write(datavs, on_failure=_on_failure) |
---|
5419 | + |
---|
5420 | + |
---|
5421 | + def put_sharehashes(self, sharehashes): |
---|
5422 | + """ |
---|
5423 | + Put the share hash chain in the remote slot. |
---|
5424 | + |
---|
5425 | + The salt hash tree must be put before the share hash chain, |
---|
5426 | + since we need to know where the salt hash tree ends before we |
---|
5427 | + can know where the share hash chain starts. The share hash chain |
---|
5428 | + must be put before the signature, since the length of the packed |
---|
5429 | + share hash chain determines the offset of the signature. Also, |
---|
5430 | + semantically, you must know what the root of the salt hash tree |
---|
5431 | + is before you can generate a valid signature. |
---|
5432 | + """ |
---|
5433 | + assert isinstance(sharehashes, dict) |
---|
5434 | + if "share_hash_chain" not in self._offsets: |
---|
5435 | + raise LayoutInvalid("You need to put the salt hash tree before " |
---|
5436 | + "you can put the share hash chain") |
---|
5437 | + # The signature comes after the share hash chain. If the |
---|
5438 | + # signature has already been written, we must not write another |
---|
5439 | + # share hash chain. The signature writes the verification key |
---|
5440 | + # offset when it gets sent to the remote server, so we look for |
---|
5441 | + # that. |
---|
5442 | + if "verification_key" in self._offsets: |
---|
5443 | + raise LayoutInvalid("You must write the share hash chain " |
---|
5444 | + "before you write the signature") |
---|
5445 | + datavs = [] |
---|
5446 | + sharehashes_s = "".join([struct.pack(">H32s", i, sharehashes[i]) |
---|
5447 | + for i in sorted(sharehashes.keys())]) |
---|
5448 | + self._offsets['signature'] = self._offsets['share_hash_chain'] + len(sharehashes_s) |
---|
5449 | + datavs.append(tuple([self._offsets['share_hash_chain'], sharehashes_s])) |
---|
5450 | + def _on_failure(): |
---|
5451 | + del(self._offsets['signature']) |
---|
5452 | + return self._write(datavs, on_failure=_on_failure) |
---|
5453 | + |
---|
5454 | + |
---|
5455 | + def put_root_hash(self, roothash): |
---|
5456 | + """ |
---|
5457 | + Put the root hash (the root of the share hash tree) in the |
---|
5458 | + remote slot. |
---|
5459 | + """ |
---|
5460 | + # It does not make sense to be able to put the root |
---|
5461 | + # hash without first putting the share hashes, since you need |
---|
5462 | + # the share hashes to generate the root hash. |
---|
5463 | + # |
---|
5464 | + # Signature is defined by the routine that places the share hash |
---|
5465 | + # chain, so it's a good thing to look for in finding out whether |
---|
5466 | + # or not the share hash chain exists on the remote server. |
---|
5467 | + if "signature" not in self._offsets: |
---|
5468 | + raise LayoutInvalid("You need to put the share hash chain " |
---|
5469 | + "before you can put the root share hash") |
---|
5470 | + if len(roothash) != HASH_SIZE: |
---|
5471 | + raise LayoutInvalid("hashes and salts must be exactly %d bytes" |
---|
5472 | + % HASH_SIZE) |
---|
5473 | + datavs = [] |
---|
5474 | + self._root_hash = roothash |
---|
5475 | + # To write both of these values, we update the checkstring on |
---|
5476 | + # the remote server, which includes them |
---|
5477 | + checkstring = self.get_checkstring() |
---|
5478 | + datavs.append(tuple([0, checkstring])) |
---|
5479 | + # This write, if successful, changes the checkstring, so we need |
---|
5480 | + # to update our internal checkstring to be consistent with the |
---|
5481 | + # one on the server. |
---|
5482 | + def _on_success(): |
---|
5483 | + self._testvs = [(0, len(checkstring), "eq", checkstring)] |
---|
5484 | + def _on_failure(): |
---|
5485 | + self._root_hash = None |
---|
5486 | + self._salt_hash = None |
---|
5487 | + return self._write(datavs, |
---|
5488 | + on_success=_on_success, |
---|
5489 | + on_failure=_on_failure) |
---|
5490 | + |
---|
5491 | + |
---|
5492 | + def get_signable(self): |
---|
5493 | + """ |
---|
5494 | + Get the first eight fields of the mutable file; the parts that |
---|
5495 | + are signed. |
---|
5496 | + """ |
---|
5497 | + if not self._root_hash or not self._salt_hash: |
---|
5498 | + raise LayoutInvalid("You need to set the root hash and the " |
---|
5499 | + "salt hash before getting something to " |
---|
5500 | + "sign") |
---|
5501 | + return struct.pack(MDMFSIGNABLEHEADER, |
---|
5502 | + 1, |
---|
5503 | + self._seqnum, |
---|
5504 | + self._root_hash, |
---|
5505 | + self._salt_hash, |
---|
5506 | + self._required_shares, |
---|
5507 | + self._total_shares, |
---|
5508 | + self._segment_size, |
---|
5509 | + self._data_length) |
---|
5510 | + |
---|
5511 | + |
---|
5512 | + def put_signature(self, signature): |
---|
5513 | + """ |
---|
5514 | + Put the signature field to the remote slot. |
---|
5515 | + |
---|
5516 | + I require that the root hash and share hash chain have been put |
---|
5517 | + to the grid before I will write the signature to the grid. |
---|
5518 | + """ |
---|
5519 | + if "signature" not in self._offsets: |
---|
5520 | + raise LayoutInvalid("You must put the share hash chain " |
---|
5521 | + # It does not make sense to put a signature without first |
---|
5522 | + # putting the root hash and the salt hash (since otherwise |
---|
5523 | + # the signature would be incomplete), so we don't allow that. |
---|
5524 | + "before putting the signature") |
---|
5525 | + if not self._root_hash: |
---|
5526 | + raise LayoutInvalid("You must complete the signed prefix " |
---|
5527 | + "before computing a signature") |
---|
5528 | + # If we put the signature after we put the verification key, we |
---|
5529 | + # could end up running into the verification key, and will |
---|
5530 | + # probably screw up the offsets as well. So we don't allow that. |
---|
5531 | + # The method that writes the verification key defines the EOF |
---|
5532 | + # offset before writing the verification key, so look for that. |
---|
5533 | + if "EOF" in self._offsets: |
---|
5534 | + raise LayoutInvalid("You must write the signature before the verification key") |
---|
5535 | + |
---|
5536 | + self._offsets['verification_key'] = self._offsets['signature'] + len(signature) |
---|
5537 | + datavs = [] |
---|
5538 | + datavs.append(tuple([self._offsets['signature'], signature])) |
---|
5539 | + def _on_failure(): |
---|
5540 | + del(self._offsets['verification_key']) |
---|
5541 | + return self._write(datavs, on_failure=_on_failure) |
---|
5542 | + |
---|
5543 | + |
---|
5544 | + def put_verification_key(self, verification_key): |
---|
5545 | + """ |
---|
5546 | + Put the verification key into the remote slot. |
---|
5547 | + |
---|
5548 | + I require that the signature have been written to the storage |
---|
5549 | + server before I allow the verification key to be written to the |
---|
5550 | + remote server. |
---|
5551 | + """ |
---|
5552 | + if "verification_key" not in self._offsets: |
---|
5553 | + raise LayoutInvalid("You must put the signature before you " |
---|
5554 | + "can put the verification key") |
---|
5555 | + self._offsets['EOF'] = self._offsets['verification_key'] + len(verification_key) |
---|
5556 | + datavs = [] |
---|
5557 | + datavs.append(tuple([self._offsets['verification_key'], verification_key])) |
---|
5558 | + def _on_failure(): |
---|
5559 | + del(self._offsets['EOF']) |
---|
5560 | + return self._write(datavs, on_failure=_on_failure) |
---|
5561 | + |
---|
5562 | + |
---|
5563 | + def finish_publishing(self): |
---|
5564 | + """ |
---|
5565 | + Write the offset table and encoding parameters to the remote |
---|
5566 | + slot, since that's the only thing we have yet to publish at this |
---|
5567 | + point. |
---|
5568 | + """ |
---|
5569 | + if "EOF" not in self._offsets: |
---|
5570 | + raise LayoutInvalid("You must put the verification key before " |
---|
5571 | + "you can publish the offsets") |
---|
5572 | + offsets_offset = struct.calcsize(MDMFHEADERWITHOUTOFFSETS) |
---|
5573 | + offsets = struct.pack(MDMFOFFSETS, |
---|
5574 | + self._offsets['share-data'], |
---|
5575 | + self._offsets['enc_privkey'], |
---|
5576 | + self._offsets['block_hash_tree'], |
---|
5577 | + self._offsets['salt_hash_tree'], |
---|
5578 | + self._offsets['share_hash_chain'], |
---|
5579 | + self._offsets['signature'], |
---|
5580 | + self._offsets['verification_key'], |
---|
5581 | + self._offsets['EOF']) |
---|
5582 | + datavs = [] |
---|
5583 | + datavs.append(tuple([offsets_offset, offsets])) |
---|
5584 | + encoding_parameters_offset = struct.calcsize(MDMFCHECKSTRING) |
---|
5585 | + params = struct.pack(">BBQQ", |
---|
5586 | + self._required_shares, |
---|
5587 | + self._total_shares, |
---|
5588 | + self._segment_size, |
---|
5589 | + self._data_length) |
---|
5590 | + datavs.append(tuple([encoding_parameters_offset, params])) |
---|
5591 | + return self._write(datavs) |
---|
5592 | + |
---|
5593 | + |
---|
5594 | + def _write(self, datavs, on_failure=None, on_success=None): |
---|
5595 | + """I write the data vectors in datavs to the remote slot.""" |
---|
5596 | + tw_vectors = {} |
---|
5597 | + new_share = False |
---|
5598 | + if not self._testvs: |
---|
5599 | + self._testvs = [] |
---|
5600 | + self._testvs.append(tuple([0, 1, "eq", ""])) |
---|
5601 | + new_share = True |
---|
5602 | + if not self._written: |
---|
5603 | + # Write a new checkstring to the share when we write it, so |
---|
5604 | + # that we have something to check later. |
---|
5605 | + new_checkstring = self.get_checkstring() |
---|
5606 | + datavs.append((0, new_checkstring)) |
---|
5607 | + def _first_write(): |
---|
5608 | + self._written = True |
---|
5609 | + self._testvs = [(0, len(new_checkstring), "eq", new_checkstring)] |
---|
5610 | + on_success = _first_write |
---|
5611 | + tw_vectors[self._shnum] = (self._testvs, datavs, None) |
---|
5612 | + datalength = sum([len(x[1]) for x in datavs]) |
---|
5613 | + d = self._rref.callRemote("slot_testv_and_readv_and_writev", |
---|
5614 | + self._storage_index, |
---|
5615 | + self._secrets, |
---|
5616 | + tw_vectors, |
---|
5617 | + self._readv) |
---|
5618 | + def _result(results): |
---|
5619 | + if isinstance(results, failure.Failure) or not results[0]: |
---|
5620 | + # Do nothing; the write was unsuccessful. |
---|
5621 | + if on_failure: on_failure() |
---|
5622 | + else: |
---|
5623 | + if on_success: on_success() |
---|
5624 | + return results |
---|
5625 | + d.addCallback(_result) |
---|
5626 | + return d |
---|
5627 | + |
---|
5628 | + |
---|
5629 | +class MDMFSlotReadProxy: |
---|
5630 | + """ |
---|
5631 | + I read from a mutable slot filled with data written in the MDMF data |
---|
5632 | + format (which is described above). |
---|
5633 | + |
---|
5634 | + I can be initialized with some amount of data, which I will use (if |
---|
5635 | + it is valid) to eliminate some of the need to fetch it from servers. |
---|
5636 | + """ |
---|
5637 | + def __init__(self, |
---|
5638 | + rref, |
---|
5639 | + storage_index, |
---|
5640 | + shnum, |
---|
5641 | + data=""): |
---|
5642 | + # Start the initialization process. |
---|
5643 | + self._rref = rref |
---|
5644 | + self._storage_index = storage_index |
---|
5645 | + self.shnum = shnum |
---|
5646 | + |
---|
5647 | + # Before doing anything, the reader is probably going to want to |
---|
5648 | + # verify that the signature is correct. To do that, they'll need |
---|
5649 | + # the verification key, and the signature. To get those, we'll |
---|
5650 | + # need the offset table. So fetch the offset table on the |
---|
5651 | + # assumption that that will be the first thing that a reader is |
---|
5652 | + # going to do. |
---|
5653 | + |
---|
5654 | + # The fact that these encoding parameters are None tells us |
---|
5655 | + # that we haven't yet fetched them from the remote share, so we |
---|
5656 | + # should. We could just not set them, but the checks will be |
---|
5657 | + # easier to read if we don't have to use hasattr. |
---|
5658 | + self._version_number = None |
---|
5659 | + self._sequence_number = None |
---|
5660 | + self._root_hash = None |
---|
5661 | + self._salt_hash = None |
---|
5662 | + self._salt = None |
---|
5663 | + self._required_shares = None |
---|
5664 | + self._total_shares = None |
---|
5665 | + self._segment_size = None |
---|
5666 | + self._data_length = None |
---|
5667 | + self._offsets = None |
---|
5668 | + |
---|
5669 | + # If the user has chosen to initialize us with some data, we'll |
---|
5670 | + # try to satisfy subsequent data requests with that data before |
---|
5671 | + # asking the storage server for it. If |
---|
5672 | + self._data = data |
---|
5673 | + # The way callers interact with cache in the filenode returns |
---|
5674 | + # None if there isn't any cached data, but the way we index the |
---|
5675 | + # cached data requires a string, so convert None to "". |
---|
5676 | + if self._data == None: |
---|
5677 | + self._data = "" |
---|
5678 | + |
---|
5679 | + |
---|
5680 | + def _maybe_fetch_offsets_and_header(self, force_remote=False): |
---|
5681 | + """ |
---|
5682 | + I fetch the offset table and the header from the remote slot if |
---|
5683 | + I don't already have them. If I do have them, I do nothing and |
---|
5684 | + return an empty Deferred. |
---|
5685 | + """ |
---|
5686 | + if self._offsets: |
---|
5687 | + return defer.succeed(None) |
---|
5688 | + # At this point, we may be either SDMF or MDMF. Fetching 91 |
---|
5689 | + # bytes will be enough to get information for both SDMF and |
---|
5690 | + # MDMF, though we'll be left with about 20 more bytes than we |
---|
5691 | + # need if this ends up being SDMF. We could just fetch the first |
---|
5692 | + # byte, which would save the extra bytes at the cost of an |
---|
5693 | + # additional roundtrip after we parse the result. |
---|
5694 | + readvs = [(0, 91)] |
---|
5695 | + d = self._read(readvs, force_remote) |
---|
5696 | + d.addCallback(self._process_encoding_parameters) |
---|
5697 | + |
---|
5698 | + # Now, we have the encoding parameters, which will tell us |
---|
5699 | + # where we need to look for the offset table. |
---|
5700 | + def _fetch_offsets(ignored): |
---|
5701 | + if self._version_number == 0: |
---|
5702 | + # In SDMF, the offset table starts at byte 75, and |
---|
5703 | + # extends for 32 bytes |
---|
5704 | + readv = [(75, 32)] # struct.calcsize(">LLLLQQ") == 32 |
---|
5705 | + |
---|
5706 | + elif self._version_number == 1: |
---|
5707 | + # In MDMF, the offset table starts at byte 91 and |
---|
5708 | + # extends for 60 bytes |
---|
5709 | + readv = [(91, 60)] # struct.calcsize(">LQQQQQQQ") == 60 |
---|
5710 | + else: |
---|
5711 | + raise LayoutInvalid("I only understand SDMF and MDMF") |
---|
5712 | + return readv |
---|
5713 | + |
---|
5714 | + d.addCallback(_fetch_offsets) |
---|
5715 | + d.addCallback(lambda readv: |
---|
5716 | + self._read(readv, force_remote)) |
---|
5717 | + d.addCallback(self._process_offsets) |
---|
5718 | + return d |
---|
5719 | + |
---|
5720 | + |
---|
5721 | + def _process_encoding_parameters(self, encoding_parameters): |
---|
5722 | + assert self.shnum in encoding_parameters |
---|
5723 | + encoding_parameters = encoding_parameters[self.shnum][0] |
---|
5724 | + # The first byte is the version number. It will tell us what |
---|
5725 | + # to do next. |
---|
5726 | + (verno,) = struct.unpack(">B", encoding_parameters[:1]) |
---|
5727 | + if verno == MDMF_VERSION: |
---|
5728 | + (verno, |
---|
5729 | + seqnum, |
---|
5730 | + root_hash, |
---|
5731 | + salt_hash, |
---|
5732 | + k, |
---|
5733 | + n, |
---|
5734 | + segsize, |
---|
5735 | + datalen) = struct.unpack(MDMFHEADERWITHOUTOFFSETS, |
---|
5736 | + encoding_parameters) |
---|
5737 | + self._salt_hash = salt_hash |
---|
5738 | + if segsize == 0 and datalen == 0: |
---|
5739 | + # Empty file, no segments. |
---|
5740 | + self._num_segments = 0 |
---|
5741 | + else: |
---|
5742 | + self._num_segments = mathutil.div_ceil(datalen, segsize) |
---|
5743 | + |
---|
5744 | + elif verno == SDMF_VERSION: |
---|
5745 | + (verno, |
---|
5746 | + seqnum, |
---|
5747 | + root_hash, |
---|
5748 | + salt, |
---|
5749 | + k, |
---|
5750 | + n, |
---|
5751 | + segsize, |
---|
5752 | + datalen) = struct.unpack(">BQ32s16s BBQQ", |
---|
5753 | + encoding_parameters[:75]) |
---|
5754 | + self._salt = salt |
---|
5755 | + if segsize == 0 and datalen == 0: |
---|
5756 | + # empty file |
---|
5757 | + self._num_segments = 0 |
---|
5758 | + else: |
---|
5759 | + # non-empty SDMF files have one segment. |
---|
5760 | + self._num_segments = 1 |
---|
5761 | + else: |
---|
5762 | + raise UnknownVersionError("You asked me to read mutable file " |
---|
5763 | + "version %d, but I only understand " |
---|
5764 | + "%d and %d" % (verno, SDMF_VERSION, |
---|
5765 | + MDMF_VERSION)) |
---|
5766 | + |
---|
5767 | + self._version_number = verno |
---|
5768 | + self._sequence_number = seqnum |
---|
5769 | + self._root_hash = root_hash |
---|
5770 | + self._required_shares = k |
---|
5771 | + self._total_shares = n |
---|
5772 | + self._segment_size = segsize |
---|
5773 | + self._data_length = datalen |
---|
5774 | + |
---|
5775 | + self._block_size = self._segment_size / self._required_shares |
---|
5776 | + # We can upload empty files, and need to account for this fact |
---|
5777 | + # so as to avoid zero-division and zero-modulo errors. |
---|
5778 | + if datalen > 0: |
---|
5779 | + tail_size = self._data_length % self._segment_size |
---|
5780 | + else: |
---|
5781 | + tail_size = 0 |
---|
5782 | + if not tail_size: |
---|
5783 | + self._tail_block_size = self._block_size |
---|
5784 | + else: |
---|
5785 | + self._tail_block_size = mathutil.next_multiple(tail_size, |
---|
5786 | + self._required_shares) |
---|
5787 | + self._tail_block_size /= self._required_shares |
---|
5788 | + |
---|
5789 | + |
---|
5790 | + def _process_offsets(self, offsets): |
---|
5791 | + assert self.shnum in offsets |
---|
5792 | + offsets = offsets[self.shnum][0] |
---|
5793 | + if self._version_number == 0: |
---|
5794 | + (signature, |
---|
5795 | + share_hash_chain, |
---|
5796 | + block_hash_tree, |
---|
5797 | + share_data, |
---|
5798 | + enc_privkey, |
---|
5799 | + EOF) = struct.unpack(">LLLLQQ", offsets) |
---|
5800 | + self._offsets = {} |
---|
5801 | + self._offsets['signature'] = signature |
---|
5802 | + self._offsets['share_data'] = share_data |
---|
5803 | + self._offsets['block_hash_tree'] = block_hash_tree |
---|
5804 | + self._offsets['share_hash_chain'] = share_hash_chain |
---|
5805 | + self._offsets['enc_privkey'] = enc_privkey |
---|
5806 | + self._offsets['EOF'] = EOF |
---|
5807 | + elif self._version_number == 1: |
---|
5808 | + (share_data, |
---|
5809 | + encprivkey, |
---|
5810 | + blockhashes, |
---|
5811 | + salthashes, |
---|
5812 | + sharehashes, |
---|
5813 | + signature, |
---|
5814 | + verification_key, |
---|
5815 | + eof) = struct.unpack(MDMFOFFSETS, offsets) |
---|
5816 | + self._offsets = {} |
---|
5817 | + self._offsets['share_data'] = share_data |
---|
5818 | + self._offsets['enc_privkey'] = encprivkey |
---|
5819 | + self._offsets['block_hash_tree'] = blockhashes |
---|
5820 | + self._offsets['salt_hash_tree']= salthashes |
---|
5821 | + self._offsets['share_hash_chain'] = sharehashes |
---|
5822 | + self._offsets['signature'] = signature |
---|
5823 | + self._offsets['verification_key'] = verification_key |
---|
5824 | + self._offsets['EOF'] = eof |
---|
5825 | + |
---|
5826 | + |
---|
5827 | + def get_block_and_salt(self, segnum): |
---|
5828 | + """ |
---|
5829 | + I return (block, salt), where block is the block data and |
---|
5830 | + salt is the salt used to encrypt that segment. |
---|
5831 | + """ |
---|
5832 | + d = self._maybe_fetch_offsets_and_header() |
---|
5833 | + def _then(ignored): |
---|
5834 | + base_share_offset = self._offsets['share_data'] |
---|
5835 | + if self._version_number == 1: |
---|
5836 | + base_salt_offset = struct.calcsize(MDMFHEADER) |
---|
5837 | + salt_offset = base_salt_offset + SALT_SIZE * segnum |
---|
5838 | + else: |
---|
5839 | + salt_offset = None # no per-segment salts in SDMF |
---|
5840 | + return base_share_offset, salt_offset |
---|
5841 | + |
---|
5842 | + d.addCallback(_then) |
---|
5843 | + |
---|
5844 | + def _calculate_share_offset(share_and_salt_offset): |
---|
5845 | + base_share_offset, salt_offset = share_and_salt_offset |
---|
5846 | + if segnum + 1 > self._num_segments: |
---|
5847 | + raise LayoutInvalid("Not a valid segment number") |
---|
5848 | + |
---|
5849 | + share_offset = base_share_offset + self._block_size * segnum |
---|
5850 | + if segnum + 1 == self._num_segments: |
---|
5851 | + data = self._tail_block_size |
---|
5852 | + else: |
---|
5853 | + data = self._block_size |
---|
5854 | + readvs = [(share_offset, data)] |
---|
5855 | + if salt_offset: |
---|
5856 | + readvs.insert(0,(salt_offset, SALT_SIZE)) |
---|
5857 | + return readvs |
---|
5858 | + |
---|
5859 | + d.addCallback(_calculate_share_offset) |
---|
5860 | + d.addCallback(lambda readvs: |
---|
5861 | + self._read(readvs)) |
---|
5862 | + def _process_results(results): |
---|
5863 | + assert self.shnum in results |
---|
5864 | + if self._version_number == 0: |
---|
5865 | + # We only read the share data, but we know the salt from |
---|
5866 | + # when we fetched the header |
---|
5867 | + data = results[self.shnum][0] |
---|
5868 | + salt = self._salt |
---|
5869 | + else: |
---|
5870 | + salt, data = results[self.shnum] |
---|
5871 | + return data, salt |
---|
5872 | + d.addCallback(_process_results) |
---|
5873 | + return d |
---|
5874 | + |
---|
5875 | + |
---|
5876 | + def get_blockhashes(self, needed=None): |
---|
5877 | + """ |
---|
5878 | + I return the block hash tree |
---|
5879 | + |
---|
5880 | + I take an optional argument, needed, which is a set of indices |
---|
5881 | + correspond to hashes that I should fetch. If this argument is |
---|
5882 | + missing, I will fetch the entire block hash tree; otherwise, I |
---|
5883 | + may attempt to fetch fewer hashes, based on what needed says |
---|
5884 | + that I should do. Note that I may fetch as many hashes as I |
---|
5885 | + want, so long as the set of hashes that I do fetch is a superset |
---|
5886 | + of the ones that I am asked for, so callers should be prepared |
---|
5887 | + to tolerate additional hashes. |
---|
5888 | + """ |
---|
5889 | + # TODO: Return only the parts of the block hash tree necessary |
---|
5890 | + # to validate the blocknum provided? |
---|
5891 | + # This is a good idea, but it is hard to implement correctly. It |
---|
5892 | + # is bad to fetch any one block hash more than once, so we |
---|
5893 | + # probably just want to fetch the whole thing at once and then |
---|
5894 | + # serve it. |
---|
5895 | + # XXX: Figure out why this works. |
---|
5896 | + if needed == set([]): |
---|
5897 | + return defer.succeed([]) |
---|
5898 | + d = self._maybe_fetch_offsets_and_header() |
---|
5899 | + def _then(ignored): |
---|
5900 | + blockhashes_offset = self._offsets['block_hash_tree'] |
---|
5901 | + if self._version_number == 1: |
---|
5902 | + blockhashes_length = self._offsets['salt_hash_tree'] - blockhashes_offset |
---|
5903 | + else: |
---|
5904 | + blockhashes_length = self._offsets['share_data'] - blockhashes_offset |
---|
5905 | + readvs = [(blockhashes_offset, blockhashes_length)] |
---|
5906 | + return readvs |
---|
5907 | + d.addCallback(_then) |
---|
5908 | + d.addCallback(lambda readvs: |
---|
5909 | + self._read(readvs)) |
---|
5910 | + def _build_block_hash_tree(results): |
---|
5911 | + assert self.shnum in results |
---|
5912 | + |
---|
5913 | + rawhashes = results[self.shnum][0] |
---|
5914 | + results = [rawhashes[i:i+HASH_SIZE] |
---|
5915 | + for i in range(0, len(rawhashes), HASH_SIZE)] |
---|
5916 | + return results |
---|
5917 | + d.addCallback(_build_block_hash_tree) |
---|
5918 | + return d |
---|
5919 | + |
---|
5920 | + |
---|
5921 | + def get_salthashes(self, needed=None): |
---|
5922 | + """ |
---|
5923 | + I return the salt hash tree. |
---|
5924 | + |
---|
5925 | + I accept an optional argument, needed, which is a set of indices |
---|
5926 | + corresponding to hashes that I should fetch. If this argument is |
---|
5927 | + missing, I will fetch and return the entire salt hash tree. |
---|
5928 | + Otherwise, I may fetch any part of the salt hash tree, so long |
---|
5929 | + as the part that I fetch and return is a superset of the part |
---|
5930 | + that my caller has asked for. Callers should be prepared to |
---|
5931 | + tolerate this behavior. |
---|
5932 | + |
---|
5933 | + This method is only meaningful for MDMF files, as only MDMF |
---|
5934 | + files have a salt hash tree. If the remote file is an SDMF file, |
---|
5935 | + this method will return False. |
---|
5936 | + """ |
---|
5937 | + # TODO: Only get the leaves nodes implied by salthashes |
---|
5938 | + if needed == set([]): |
---|
5939 | + return defer.succeed([]) |
---|
5940 | + d = self._maybe_fetch_offsets_and_header() |
---|
5941 | + def _then(ignored): |
---|
5942 | + if self._version_number == 0: |
---|
5943 | + return [] |
---|
5944 | + else: |
---|
5945 | + salthashes_offset = self._offsets['salt_hash_tree'] |
---|
5946 | + salthashes_length = self._offsets['share_hash_chain'] - salthashes_offset |
---|
5947 | + return [(salthashes_offset, salthashes_length)] |
---|
5948 | + d.addCallback(_then) |
---|
5949 | + def _maybe_read(readvs): |
---|
5950 | + if readvs: |
---|
5951 | + return self._read(readvs) |
---|
5952 | + else: |
---|
5953 | + return False |
---|
5954 | + d.addCallback(_maybe_read) |
---|
5955 | + def _process_results(results): |
---|
5956 | + if not results: |
---|
5957 | + return False |
---|
5958 | + assert self.shnum in results |
---|
5959 | + |
---|
5960 | + rawhashes = results[self.shnum][0] |
---|
5961 | + results = [rawhashes[i:i+HASH_SIZE] |
---|
5962 | + for i in range(0, len(rawhashes), HASH_SIZE)] |
---|
5963 | + return results |
---|
5964 | + d.addCallback(_process_results) |
---|
5965 | + return d |
---|
5966 | + |
---|
5967 | + |
---|
5968 | + def get_sharehashes(self, needed=None): |
---|
5969 | + """ |
---|
5970 | + I return the part of the share hash chain placed to validate |
---|
5971 | + this share. |
---|
5972 | + |
---|
5973 | + I take an optional argument, needed. Needed is a set of indices |
---|
5974 | + that correspond to the hashes that I should fetch. If needed is |
---|
5975 | + not present, I will fetch and return the entire share hash |
---|
5976 | + chain. Otherwise, I may fetch and return any part of the share |
---|
5977 | + hash chain that is a superset of the part that I am asked to |
---|
5978 | + fetch. Callers should be prepared to deal with more hashes than |
---|
5979 | + they've asked for. |
---|
5980 | + """ |
---|
5981 | + if needed == set([]): |
---|
5982 | + return defer.succeed([]) |
---|
5983 | + d = self._maybe_fetch_offsets_and_header() |
---|
5984 | + |
---|
5985 | + def _make_readvs(ignored): |
---|
5986 | + sharehashes_offset = self._offsets['share_hash_chain'] |
---|
5987 | + if self._version_number == 0: |
---|
5988 | + sharehashes_length = self._offsets['block_hash_tree'] - sharehashes_offset |
---|
5989 | + else: |
---|
5990 | + sharehashes_length = self._offsets['signature'] - sharehashes_offset |
---|
5991 | + readvs = [(sharehashes_offset, sharehashes_length)] |
---|
5992 | + return readvs |
---|
5993 | + d.addCallback(_make_readvs) |
---|
5994 | + d.addCallback(lambda readvs: |
---|
5995 | + self._read(readvs)) |
---|
5996 | + def _build_share_hash_chain(results): |
---|
5997 | + assert self.shnum in results |
---|
5998 | + |
---|
5999 | + sharehashes = results[self.shnum][0] |
---|
6000 | + results = [sharehashes[i:i+(HASH_SIZE + 2)] |
---|
6001 | + for i in range(0, len(sharehashes), HASH_SIZE + 2)] |
---|
6002 | + results = dict([struct.unpack(">H32s", data) |
---|
6003 | + for data in results]) |
---|
6004 | + return results |
---|
6005 | + d.addCallback(_build_share_hash_chain) |
---|
6006 | + return d |
---|
6007 | + |
---|
6008 | + |
---|
6009 | + def get_encprivkey(self): |
---|
6010 | + """ |
---|
6011 | + I return the encrypted private key. |
---|
6012 | + """ |
---|
6013 | + d = self._maybe_fetch_offsets_and_header() |
---|
6014 | + |
---|
6015 | + def _make_readvs(ignored): |
---|
6016 | + privkey_offset = self._offsets['enc_privkey'] |
---|
6017 | + if self._version_number == 0: |
---|
6018 | + privkey_length = self._offsets['EOF'] - privkey_offset |
---|
6019 | + else: |
---|
6020 | + privkey_length = self._offsets['block_hash_tree'] - privkey_offset |
---|
6021 | + readvs = [(privkey_offset, privkey_length)] |
---|
6022 | + return readvs |
---|
6023 | + d.addCallback(_make_readvs) |
---|
6024 | + d.addCallback(lambda readvs: |
---|
6025 | + self._read(readvs)) |
---|
6026 | + def _process_results(results): |
---|
6027 | + assert self.shnum in results |
---|
6028 | + privkey = results[self.shnum][0] |
---|
6029 | + return privkey |
---|
6030 | + d.addCallback(_process_results) |
---|
6031 | + return d |
---|
6032 | + |
---|
6033 | + |
---|
6034 | + def get_signature(self): |
---|
6035 | + """ |
---|
6036 | + I return the signature of my share. |
---|
6037 | + """ |
---|
6038 | + d = self._maybe_fetch_offsets_and_header() |
---|
6039 | + |
---|
6040 | + def _make_readvs(ignored): |
---|
6041 | + signature_offset = self._offsets['signature'] |
---|
6042 | + if self._version_number == 1: |
---|
6043 | + signature_length = self._offsets['verification_key'] - signature_offset |
---|
6044 | + else: |
---|
6045 | + signature_length = self._offsets['share_hash_chain'] - signature_offset |
---|
6046 | + readvs = [(signature_offset, signature_length)] |
---|
6047 | + return readvs |
---|
6048 | + d.addCallback(_make_readvs) |
---|
6049 | + d.addCallback(lambda readvs: |
---|
6050 | + self._read(readvs)) |
---|
6051 | + def _process_results(results): |
---|
6052 | + assert self.shnum in results |
---|
6053 | + signature = results[self.shnum][0] |
---|
6054 | + return signature |
---|
6055 | + d.addCallback(_process_results) |
---|
6056 | + return d |
---|
6057 | + |
---|
6058 | + |
---|
6059 | + def get_verification_key(self): |
---|
6060 | + """ |
---|
6061 | + I return the verification key. |
---|
6062 | + """ |
---|
6063 | + d = self._maybe_fetch_offsets_and_header() |
---|
6064 | + |
---|
6065 | + def _make_readvs(ignored): |
---|
6066 | + if self._version_number == 1: |
---|
6067 | + vk_offset = self._offsets['verification_key'] |
---|
6068 | + vk_length = self._offsets['EOF'] - vk_offset |
---|
6069 | + else: |
---|
6070 | + vk_offset = struct.calcsize(">BQ32s16sBBQQLLLLQQ") |
---|
6071 | + vk_length = self._offsets['signature'] - vk_offset |
---|
6072 | + readvs = [(vk_offset, vk_length)] |
---|
6073 | + return readvs |
---|
6074 | + d.addCallback(_make_readvs) |
---|
6075 | + d.addCallback(lambda readvs: |
---|
6076 | + self._read(readvs)) |
---|
6077 | + def _process_results(results): |
---|
6078 | + assert self.shnum in results |
---|
6079 | + verification_key = results[self.shnum][0] |
---|
6080 | + return verification_key |
---|
6081 | + d.addCallback(_process_results) |
---|
6082 | + return d |
---|
6083 | + |
---|
6084 | + |
---|
6085 | + def get_encoding_parameters(self): |
---|
6086 | + """ |
---|
6087 | + I return (k, n, segsize, datalen) |
---|
6088 | + """ |
---|
6089 | + d = self._maybe_fetch_offsets_and_header() |
---|
6090 | + d.addCallback(lambda ignored: |
---|
6091 | + (self._required_shares, |
---|
6092 | + self._total_shares, |
---|
6093 | + self._segment_size, |
---|
6094 | + self._data_length)) |
---|
6095 | + return d |
---|
6096 | + |
---|
6097 | + |
---|
6098 | + def get_seqnum(self): |
---|
6099 | + """ |
---|
6100 | + I return the sequence number for this share. |
---|
6101 | + """ |
---|
6102 | + d = self._maybe_fetch_offsets_and_header() |
---|
6103 | + d.addCallback(lambda ignored: |
---|
6104 | + self._sequence_number) |
---|
6105 | + return d |
---|
6106 | + |
---|
6107 | + |
---|
6108 | + def get_root_hash(self): |
---|
6109 | + """ |
---|
6110 | + I return the root of the block hash tree |
---|
6111 | + """ |
---|
6112 | + d = self._maybe_fetch_offsets_and_header() |
---|
6113 | + d.addCallback(lambda ignored: self._root_hash) |
---|
6114 | + return d |
---|
6115 | + |
---|
6116 | + |
---|
6117 | + def get_salt_hash(self): |
---|
6118 | + """ |
---|
6119 | + I return the flat salt hash |
---|
6120 | + """ |
---|
6121 | + d = self._maybe_fetch_offsets_and_header() |
---|
6122 | + d.addCallback(lambda ignored: self._salt_hash) |
---|
6123 | + return d |
---|
6124 | + |
---|
6125 | + |
---|
6126 | + def get_checkstring(self): |
---|
6127 | + """ |
---|
6128 | + I return the packed representation of the following: |
---|
6129 | + |
---|
6130 | + - version number |
---|
6131 | + - sequence number |
---|
6132 | + - root hash |
---|
6133 | + - salt hash |
---|
6134 | + |
---|
6135 | + which my users use as a checkstring to detect other writers. |
---|
6136 | + """ |
---|
6137 | + d = self._maybe_fetch_offsets_and_header() |
---|
6138 | + def _build_checkstring(ignored): |
---|
6139 | + if self._salt_hash: |
---|
6140 | + checkstring = struct.pack(MDMFCHECKSTRING, |
---|
6141 | + self._version_number, |
---|
6142 | + self._sequence_number, |
---|
6143 | + self._root_hash, |
---|
6144 | + self._salt_hash) |
---|
6145 | + else: |
---|
6146 | + checkstring = strut.pack(PREFIX, |
---|
6147 | + self._version_number, |
---|
6148 | + self._sequence_number, |
---|
6149 | + self._root_hash, |
---|
6150 | + self._salt) |
---|
6151 | + return checkstring |
---|
6152 | + d.addCallback(_build_checkstring) |
---|
6153 | + return d |
---|
6154 | + |
---|
6155 | + |
---|
6156 | + def get_prefix(self, force_remote): |
---|
6157 | + d = self._maybe_fetch_offsets_and_header(force_remote) |
---|
6158 | + d.addCallback(lambda ignored: |
---|
6159 | + self._build_prefix()) |
---|
6160 | + return d |
---|
6161 | + |
---|
6162 | + |
---|
6163 | + def _build_prefix(self): |
---|
6164 | + # The prefix is another name for the part of the remote share |
---|
6165 | + # that gets signed. It consists of everything up to and |
---|
6166 | + # including the datalength, packed by struct. |
---|
6167 | + if self._version_number == SDMF_VERSION: |
---|
6168 | + format_string = SIGNED_PREFIX |
---|
6169 | + salt_to_use = self._salt |
---|
6170 | + else: |
---|
6171 | + format_string = MDMFSIGNABLEHEADER |
---|
6172 | + salt_to_use = self._salt_hash |
---|
6173 | + return struct.pack(format_string, |
---|
6174 | + self._version_number, |
---|
6175 | + self._sequence_number, |
---|
6176 | + self._root_hash, |
---|
6177 | + salt_to_use, |
---|
6178 | + self._required_shares, |
---|
6179 | + self._total_shares, |
---|
6180 | + self._segment_size, |
---|
6181 | + self._data_length) |
---|
6182 | + |
---|
6183 | + |
---|
6184 | + def _get_offsets_tuple(self): |
---|
6185 | + # The offsets tuple is another component of the version |
---|
6186 | + # information tuple. It is basically our offsets dictionary, |
---|
6187 | + # itemized and in a tuple. |
---|
6188 | + return self._offsets.copy() |
---|
6189 | + |
---|
6190 | + |
---|
6191 | + def get_verinfo(self): |
---|
6192 | + """ |
---|
6193 | + I return my verinfo tuple. This is used by the ServermapUpdater |
---|
6194 | + to keep track of versions of mutable files. |
---|
6195 | + |
---|
6196 | + The verinfo tuple for MDMF files contains: |
---|
6197 | + - seqnum |
---|
6198 | + - root hash |
---|
6199 | + - salt hash |
---|
6200 | + - segsize |
---|
6201 | + - datalen |
---|
6202 | + - k |
---|
6203 | + - n |
---|
6204 | + - prefix (the thing that you sign) |
---|
6205 | + - a tuple of offsets |
---|
6206 | + |
---|
6207 | + The verinfo tuple for SDMF files is the same, but contains a |
---|
6208 | + 16-byte IV instead of a hash of salts. |
---|
6209 | + """ |
---|
6210 | + d = self._maybe_fetch_offsets_and_header() |
---|
6211 | + def _build_verinfo(ignored): |
---|
6212 | + if self._version_number == SDMF_VERSION: |
---|
6213 | + salt_to_use = self._salt |
---|
6214 | + else: |
---|
6215 | + salt_to_use = self._salt_hash |
---|
6216 | + return (self._sequence_number, |
---|
6217 | + self._root_hash, |
---|
6218 | + salt_to_use, |
---|
6219 | + self._segment_size, |
---|
6220 | + self._data_length, |
---|
6221 | + self._required_shares, |
---|
6222 | + self._total_shares, |
---|
6223 | + self._build_prefix(), |
---|
6224 | + self._get_offsets_tuple()) |
---|
6225 | + d.addCallback(_build_verinfo) |
---|
6226 | + return d |
---|
6227 | + |
---|
6228 | + |
---|
6229 | + def _read(self, readvs, force_remote=False): |
---|
6230 | + unsatisfiable = filter(lambda x: x[0] + x[1] > len(self._data), readvs) |
---|
6231 | + # TODO: It's entirely possible to tweak this so that it just |
---|
6232 | + # fulfills the requests that it can, and not demand that all |
---|
6233 | + # requests are satisfiable before running it. |
---|
6234 | + if not unsatisfiable and not force_remote: |
---|
6235 | + results = [self._data[offset:offset+length] |
---|
6236 | + for (offset, length) in readvs] |
---|
6237 | + results = {self.shnum: results} |
---|
6238 | + d = defer.succeed(results) |
---|
6239 | + else: |
---|
6240 | + d = self._rref.callRemote("slot_readv", |
---|
6241 | + self._storage_index, |
---|
6242 | + [self.shnum], |
---|
6243 | + readvs) |
---|
6244 | + return d |
---|
6245 | + |
---|
6246 | + |
---|
6247 | + def is_sdmf(self): |
---|
6248 | + """I tell my caller whether or not my remote file is SDMF or MDMF |
---|
6249 | + """ |
---|
6250 | + d = self._maybe_fetch_offsets_and_header() |
---|
6251 | + d.addCallback(lambda ignored: |
---|
6252 | + self._version_number == 0) |
---|
6253 | + return d |
---|
6254 | + |
---|
6255 | + |
---|
6256 | +class LayoutInvalid(Exception): |
---|
6257 | + """ |
---|
6258 | + This isn't a valid MDMF mutable file |
---|
6259 | + """ |
---|
6260 | hunk ./src/allmydata/test/test_storage.py 24 |
---|
6261 | ReadBucketProxy |
---|
6262 | from allmydata.mutable.layout import MDMFSlotWriteProxy, MDMFSlotReadProxy, \ |
---|
6263 | LayoutInvalid, MDMFSIGNABLEHEADER, \ |
---|
6264 | - SIGNED_PREFIX |
---|
6265 | + SIGNED_PREFIX, MDMFHEADER |
---|
6266 | from allmydata.interfaces import BadWriteEnablerError, MDMF_VERSION, \ |
---|
6267 | SDMF_VERSION |
---|
6268 | from allmydata.test.common import LoggingServiceParent, ShouldFailMixin |
---|
6269 | hunk ./src/allmydata/test/test_storage.py 1321 |
---|
6270 | self.encprivkey = "private" |
---|
6271 | self.root_hash = self.block_hash |
---|
6272 | self.salt_hash = self.root_hash |
---|
6273 | + self.salt_hash_tree = [self.salt_hash for i in xrange(6)] |
---|
6274 | self.block_hash_tree_s = self.serialize_blockhashes(self.block_hash_tree) |
---|
6275 | self.share_hash_chain_s = self.serialize_sharehashes(self.share_hash_chain) |
---|
6276 | hunk ./src/allmydata/test/test_storage.py 1324 |
---|
6277 | + # blockhashes and salt hashes are serialized in the same way, |
---|
6278 | + # only we lop off the first element and store that in the |
---|
6279 | + # header. |
---|
6280 | + self.salt_hash_tree_s = self.serialize_blockhashes(self.salt_hash_tree[1:]) |
---|
6281 | |
---|
6282 | |
---|
6283 | def tearDown(self): |
---|
6284 | hunk ./src/allmydata/test/test_storage.py 1393 |
---|
6285 | salts = "" |
---|
6286 | else: |
---|
6287 | salts = self.salt * 6 |
---|
6288 | - share_offset = 143 + len(salts) |
---|
6289 | + share_offset = 151 + len(salts) |
---|
6290 | if tail_segment: |
---|
6291 | sharedata = self.block * 6 |
---|
6292 | elif empty: |
---|
6293 | hunk ./src/allmydata/test/test_storage.py 1404 |
---|
6294 | encrypted_private_key_offset = share_offset + len(sharedata) |
---|
6295 | # The blockhashes come after the private key |
---|
6296 | blockhashes_offset = encrypted_private_key_offset + len(self.encprivkey) |
---|
6297 | - # The sharehashes come after the blockhashes |
---|
6298 | - sharehashes_offset = blockhashes_offset + len(self.block_hash_tree_s) |
---|
6299 | + # The salthashes come after the blockhashes |
---|
6300 | + salthashes_offset = blockhashes_offset + len(self.block_hash_tree_s) |
---|
6301 | + # The sharehashes come after the salt hashes |
---|
6302 | + sharehashes_offset = salthashes_offset + len(self.salt_hash_tree_s) |
---|
6303 | # The signature comes after the share hash chain |
---|
6304 | signature_offset = sharehashes_offset + len(self.share_hash_chain_s) |
---|
6305 | # The verification key comes after the signature |
---|
6306 | hunk ./src/allmydata/test/test_storage.py 1414 |
---|
6307 | verification_offset = signature_offset + len(self.signature) |
---|
6308 | # The EOF comes after the verification key |
---|
6309 | eof_offset = verification_offset + len(self.verification_key) |
---|
6310 | - data += struct.pack(">LQQQQQQ", |
---|
6311 | + data += struct.pack(">LQQQQQQQ", |
---|
6312 | share_offset, |
---|
6313 | encrypted_private_key_offset, |
---|
6314 | blockhashes_offset, |
---|
6315 | hunk ./src/allmydata/test/test_storage.py 1418 |
---|
6316 | + salthashes_offset, |
---|
6317 | sharehashes_offset, |
---|
6318 | signature_offset, |
---|
6319 | verification_offset, |
---|
6320 | hunk ./src/allmydata/test/test_storage.py 1427 |
---|
6321 | self.offsets['share_data'] = share_offset |
---|
6322 | self.offsets['enc_privkey'] = encrypted_private_key_offset |
---|
6323 | self.offsets['block_hash_tree'] = blockhashes_offset |
---|
6324 | + self.offsets['salt_hash_tree'] = salthashes_offset |
---|
6325 | self.offsets['share_hash_chain'] = sharehashes_offset |
---|
6326 | self.offsets['signature'] = signature_offset |
---|
6327 | self.offsets['verification_key'] = verification_offset |
---|
6328 | hunk ./src/allmydata/test/test_storage.py 1440 |
---|
6329 | data += self.encprivkey |
---|
6330 | # the block hash tree, |
---|
6331 | data += self.block_hash_tree_s |
---|
6332 | + # the salt hash tree |
---|
6333 | + data += self.salt_hash_tree_s |
---|
6334 | # the share hash chain, |
---|
6335 | data += self.share_hash_chain_s |
---|
6336 | # the signature, |
---|
6337 | hunk ./src/allmydata/test/test_storage.py 1562 |
---|
6338 | d.addCallback(lambda blockhashes: |
---|
6339 | self.failUnlessEqual(self.block_hash_tree, blockhashes)) |
---|
6340 | |
---|
6341 | + d.addCallback(lambda ignored: |
---|
6342 | + mr.get_salthashes()) |
---|
6343 | + d.addCallback(lambda salthashes: |
---|
6344 | + self.failUnlessEqual(self.salt_hash_tree[1:], salthashes)) |
---|
6345 | + |
---|
6346 | d.addCallback(lambda ignored: |
---|
6347 | mr.get_sharehashes()) |
---|
6348 | d.addCallback(lambda sharehashes: |
---|
6349 | hunk ./src/allmydata/test/test_storage.py 1618 |
---|
6350 | return d |
---|
6351 | |
---|
6352 | |
---|
6353 | + def test_read_salthashes_on_sdmf_file(self): |
---|
6354 | + self.write_sdmf_share_to_server("si1") |
---|
6355 | + mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
6356 | + d = defer.succeed(None) |
---|
6357 | + d.addCallback(lambda ignored: |
---|
6358 | + mr.get_salthashes()) |
---|
6359 | + d.addCallback(lambda results: |
---|
6360 | + self.failIf(results)) |
---|
6361 | + return d |
---|
6362 | + |
---|
6363 | + |
---|
6364 | def test_read_with_different_tail_segment_size(self): |
---|
6365 | self.write_test_share_to_server("si1", tail_segment=True) |
---|
6366 | mr = MDMFSlotReadProxy(self.rref, "si1", 0) |
---|
6367 | hunk ./src/allmydata/test/test_storage.py 1744 |
---|
6368 | mw.put_blockhashes(self.block_hash_tree)) |
---|
6369 | d.addCallback(_check_next_write) |
---|
6370 | d.addCallback(lambda ignored: |
---|
6371 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6372 | + d.addCallback(_check_next_write) |
---|
6373 | + d.addCallback(lambda ignored: |
---|
6374 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6375 | d.addCallback(_check_next_write) |
---|
6376 | # Add the root hash and the salt hash. This should change the |
---|
6377 | hunk ./src/allmydata/test/test_storage.py 1754 |
---|
6378 | # now, since the read vectors are applied before the write |
---|
6379 | # vectors. |
---|
6380 | d.addCallback(lambda ignored: |
---|
6381 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6382 | + mw.put_root_hash(self.root_hash)) |
---|
6383 | def _check_old_testv_after_new_one_is_written(results): |
---|
6384 | result, readvs = results |
---|
6385 | self.failUnless(result) |
---|
6386 | hunk ./src/allmydata/test/test_storage.py 1775 |
---|
6387 | return d |
---|
6388 | |
---|
6389 | |
---|
6390 | - def test_blockhashes_after_share_hash_chain(self): |
---|
6391 | + def test_blockhashes_after_salt_hash_tree(self): |
---|
6392 | mw = self._make_new_mw("si1", 0) |
---|
6393 | d = defer.succeed(None) |
---|
6394 | hunk ./src/allmydata/test/test_storage.py 1778 |
---|
6395 | - # Put everything up to and including the share hash chain |
---|
6396 | + # Put everything up to and including the salt hash tree |
---|
6397 | for i in xrange(6): |
---|
6398 | d.addCallback(lambda ignored, i=i: |
---|
6399 | mw.put_block(self.block, i, self.salt)) |
---|
6400 | hunk ./src/allmydata/test/test_storage.py 1787 |
---|
6401 | d.addCallback(lambda ignored: |
---|
6402 | mw.put_blockhashes(self.block_hash_tree)) |
---|
6403 | d.addCallback(lambda ignored: |
---|
6404 | - mw.put_sharehashes(self.share_hash_chain)) |
---|
6405 | - # Now try to put a block hash tree after the share hash chain. |
---|
6406 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6407 | + # Now try to put a block hash tree after the salt hash tree |
---|
6408 | # This won't necessarily overwrite the share hash chain, but it |
---|
6409 | # is a bad idea in general -- if we write one that is anything |
---|
6410 | # other than the exact size of the initial one, we will either |
---|
6411 | hunk ./src/allmydata/test/test_storage.py 1804 |
---|
6412 | return d |
---|
6413 | |
---|
6414 | |
---|
6415 | + def test_salt_hash_tree_after_share_hash_chain(self): |
---|
6416 | + mw = self._make_new_mw("si1", 0) |
---|
6417 | + d = defer.succeed(None) |
---|
6418 | + # Put everything up to and including the share hash chain |
---|
6419 | + for i in xrange(6): |
---|
6420 | + d.addCallback(lambda ignored, i=i: |
---|
6421 | + mw.put_block(self.block, i, self.salt)) |
---|
6422 | + d.addCallback(lambda ignored: |
---|
6423 | + mw.put_encprivkey(self.encprivkey)) |
---|
6424 | + d.addCallback(lambda ignored: |
---|
6425 | + mw.put_blockhashes(self.block_hash_tree)) |
---|
6426 | + d.addCallback(lambda ignored: |
---|
6427 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6428 | + d.addCallback(lambda ignored: |
---|
6429 | + mw.put_sharehashes(self.share_hash_chain)) |
---|
6430 | + |
---|
6431 | + # Now try to put the salt hash tree again. This should fail for |
---|
6432 | + # the same reason that it fails in the previous test. |
---|
6433 | + d.addCallback(lambda ignored: |
---|
6434 | + self.shouldFail(LayoutInvalid, "test repeat salthashes", |
---|
6435 | + None, |
---|
6436 | + mw.put_salthashes, self.salt_hash_tree)) |
---|
6437 | + return d |
---|
6438 | + |
---|
6439 | + |
---|
6440 | def test_encprivkey_after_blockhashes(self): |
---|
6441 | mw = self._make_new_mw("si1", 0) |
---|
6442 | d = defer.succeed(None) |
---|
6443 | hunk ./src/allmydata/test/test_storage.py 1859 |
---|
6444 | d.addCallback(lambda ignored: |
---|
6445 | mw.put_blockhashes(self.block_hash_tree)) |
---|
6446 | d.addCallback(lambda ignored: |
---|
6447 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6448 | + d.addCallback(lambda ignored: |
---|
6449 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6450 | d.addCallback(lambda ignored: |
---|
6451 | hunk ./src/allmydata/test/test_storage.py 1863 |
---|
6452 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6453 | + mw.put_root_hash(self.root_hash)) |
---|
6454 | d.addCallback(lambda ignored: |
---|
6455 | mw.put_signature(self.signature)) |
---|
6456 | # Now try to put the share hash chain again. This should fail |
---|
6457 | hunk ./src/allmydata/test/test_storage.py 1886 |
---|
6458 | d.addCallback(lambda ignored: |
---|
6459 | mw.put_blockhashes(self.block_hash_tree)) |
---|
6460 | d.addCallback(lambda ignored: |
---|
6461 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6462 | + d.addCallback(lambda ignored: |
---|
6463 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6464 | d.addCallback(lambda ignored: |
---|
6465 | hunk ./src/allmydata/test/test_storage.py 1890 |
---|
6466 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6467 | + mw.put_root_hash(self.root_hash)) |
---|
6468 | d.addCallback(lambda ignored: |
---|
6469 | mw.put_signature(self.signature)) |
---|
6470 | d.addCallback(lambda ignored: |
---|
6471 | hunk ./src/allmydata/test/test_storage.py 1991 |
---|
6472 | mw.put_blockhashes(self.block_hash_tree)) |
---|
6473 | d.addCallback(_check_success) |
---|
6474 | d.addCallback(lambda ignored: |
---|
6475 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6476 | + d.addCallback(_check_success) |
---|
6477 | + d.addCallback(lambda ignored: |
---|
6478 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6479 | d.addCallback(_check_success) |
---|
6480 | def _keep_old_checkstring(ignored): |
---|
6481 | hunk ./src/allmydata/test/test_storage.py 2001 |
---|
6482 | mw.set_checkstring("foobarbaz") |
---|
6483 | d.addCallback(_keep_old_checkstring) |
---|
6484 | d.addCallback(lambda ignored: |
---|
6485 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6486 | + mw.put_root_hash(self.root_hash)) |
---|
6487 | d.addCallback(_check_failure) |
---|
6488 | d.addCallback(lambda ignored: |
---|
6489 | self.failUnlessEqual(self.old_checkstring, mw.get_checkstring())) |
---|
6490 | hunk ./src/allmydata/test/test_storage.py 2009 |
---|
6491 | mw.set_checkstring(self.old_checkstring) |
---|
6492 | d.addCallback(_restore_old_checkstring) |
---|
6493 | d.addCallback(lambda ignored: |
---|
6494 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6495 | + mw.put_root_hash(self.root_hash)) |
---|
6496 | + d.addCallback(_check_success) |
---|
6497 | # The checkstring should have been set appropriately for us on |
---|
6498 | # the last write; if we try to change it to something else, |
---|
6499 | # that change should cause the verification key step to fail. |
---|
6500 | hunk ./src/allmydata/test/test_storage.py 2071 |
---|
6501 | d.addCallback(_fix_checkstring) |
---|
6502 | d.addCallback(lambda ignored: |
---|
6503 | mw.put_blockhashes(self.block_hash_tree)) |
---|
6504 | + d.addCallback(lambda ignored: |
---|
6505 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6506 | d.addCallback(_break_checkstring) |
---|
6507 | d.addCallback(lambda ignored: |
---|
6508 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6509 | hunk ./src/allmydata/test/test_storage.py 2079 |
---|
6510 | d.addCallback(lambda ignored: |
---|
6511 | self.shouldFail(LayoutInvalid, "out-of-order root hash", |
---|
6512 | None, |
---|
6513 | - mw.put_root_and_salt_hashes, |
---|
6514 | - self.root_hash, self.salt_hash)) |
---|
6515 | + mw.put_root_hash, self.root_hash)) |
---|
6516 | d.addCallback(_fix_checkstring) |
---|
6517 | d.addCallback(lambda ignored: |
---|
6518 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6519 | hunk ./src/allmydata/test/test_storage.py 2085 |
---|
6520 | d.addCallback(_break_checkstring) |
---|
6521 | d.addCallback(lambda ignored: |
---|
6522 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6523 | + mw.put_root_hash(self.root_hash)) |
---|
6524 | d.addCallback(lambda ignored: |
---|
6525 | self.shouldFail(LayoutInvalid, "out-of-order signature", |
---|
6526 | None, |
---|
6527 | hunk ./src/allmydata/test/test_storage.py 2092 |
---|
6528 | mw.put_signature, self.signature)) |
---|
6529 | d.addCallback(_fix_checkstring) |
---|
6530 | d.addCallback(lambda ignored: |
---|
6531 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6532 | + mw.put_root_hash(self.root_hash)) |
---|
6533 | d.addCallback(_break_checkstring) |
---|
6534 | d.addCallback(lambda ignored: |
---|
6535 | mw.put_signature(self.signature)) |
---|
6536 | hunk ./src/allmydata/test/test_storage.py 2131 |
---|
6537 | mw2 = self._make_new_mw("si1", 1) |
---|
6538 | # Test writing some blocks. |
---|
6539 | read = self.ss.remote_slot_readv |
---|
6540 | + expected_salt_offset = struct.calcsize(MDMFHEADER) |
---|
6541 | + expected_share_offset = expected_salt_offset + (16 * 6) |
---|
6542 | def _check_block_write(i, share): |
---|
6543 | hunk ./src/allmydata/test/test_storage.py 2134 |
---|
6544 | - self.failUnlessEqual(read("si1", [share], [(239 + (i * 2), 2)]), |
---|
6545 | + self.failUnlessEqual(read("si1", [share], [(expected_share_offset + (i * 2), 2)]), |
---|
6546 | {share: [self.block]}) |
---|
6547 | hunk ./src/allmydata/test/test_storage.py 2136 |
---|
6548 | - self.failUnlessEqual(read("si1", [share], [(143 + (i * 16), 16)]), |
---|
6549 | + self.failUnlessEqual(read("si1", [share], [(expected_salt_offset + (i * 16), 16)]), |
---|
6550 | {share: [self.salt]}) |
---|
6551 | d = defer.succeed(None) |
---|
6552 | for i in xrange(6): |
---|
6553 | hunk ./src/allmydata/test/test_storage.py 2151 |
---|
6554 | d.addCallback(lambda ignored, i=i: |
---|
6555 | _check_block_write(i, 1)) |
---|
6556 | |
---|
6557 | - def _spy_on_results(results): |
---|
6558 | - print read("si1", [], [(0, 40000000)]) |
---|
6559 | - return results |
---|
6560 | - |
---|
6561 | # Next, we make a fake encrypted private key, and put it onto the |
---|
6562 | # storage server. |
---|
6563 | d.addCallback(lambda ignored: |
---|
6564 | hunk ./src/allmydata/test/test_storage.py 2160 |
---|
6565 | # salts: 16 * 6 = 96 bytes |
---|
6566 | # blocks: 2 * 6 = 12 bytes |
---|
6567 | # = 251 bytes |
---|
6568 | - expected_private_key_offset = 251 |
---|
6569 | + expected_private_key_offset = expected_share_offset + len(self.block) * 6 |
---|
6570 | self.failUnlessEqual(len(self.encprivkey), 7) |
---|
6571 | d.addCallback(lambda ignored: |
---|
6572 | hunk ./src/allmydata/test/test_storage.py 2163 |
---|
6573 | - self.failUnlessEqual(read("si1", [0], [(251, 7)]), |
---|
6574 | + self.failUnlessEqual(read("si1", [0], [(expected_private_key_offset, 7)]), |
---|
6575 | {0: [self.encprivkey]})) |
---|
6576 | |
---|
6577 | # Next, we put a fake block hash tree. |
---|
6578 | hunk ./src/allmydata/test/test_storage.py 2173 |
---|
6579 | # header + salts + blocks: 251 bytes |
---|
6580 | # encrypted private key: 7 bytes |
---|
6581 | # = 258 bytes |
---|
6582 | - expected_block_hash_offset = 258 |
---|
6583 | + expected_block_hash_offset = expected_private_key_offset + len(self.encprivkey) |
---|
6584 | self.failUnlessEqual(len(self.block_hash_tree_s), 32 * 6) |
---|
6585 | d.addCallback(lambda ignored: |
---|
6586 | self.failUnlessEqual(read("si1", [0], [(expected_block_hash_offset, 32 * 6)]), |
---|
6587 | hunk ./src/allmydata/test/test_storage.py 2179 |
---|
6588 | {0: [self.block_hash_tree_s]})) |
---|
6589 | |
---|
6590 | + # Next, we put a fake salt hash tree. |
---|
6591 | + d.addCallback(lambda ignored: |
---|
6592 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6593 | + # The salt hash tree got inserted at |
---|
6594 | + # header + salts + blocks + private key = 258 bytes |
---|
6595 | + # block hash tree: 32 * 6 = 192 bytes |
---|
6596 | + # = 450 bytes |
---|
6597 | + expected_salt_hash_offset = expected_block_hash_offset + len(self.block_hash_tree_s) |
---|
6598 | + d.addCallback(lambda ignored: |
---|
6599 | + self.failUnlessEqual(read("si1", [0], [(expected_salt_hash_offset, 32 * 5)]), {0: [self.salt_hash_tree_s]})) |
---|
6600 | + |
---|
6601 | # Next, put a fake share hash chain |
---|
6602 | d.addCallback(lambda ignored: |
---|
6603 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6604 | hunk ./src/allmydata/test/test_storage.py 2196 |
---|
6605 | # The share hash chain got inserted at: |
---|
6606 | # header + salts + blocks + private key = 258 bytes |
---|
6607 | # block hash tree: 32 * 6 = 192 bytes |
---|
6608 | - # = 450 bytes |
---|
6609 | - expected_share_hash_offset = 450 |
---|
6610 | + # salt hash tree: 32 * 5 = 160 bytes |
---|
6611 | + # = 610 |
---|
6612 | + expected_share_hash_offset = expected_salt_hash_offset + len(self.salt_hash_tree_s) |
---|
6613 | d.addCallback(lambda ignored: |
---|
6614 | self.failUnlessEqual(read("si1", [0],[(expected_share_hash_offset, (32 + 2) * 6)]), |
---|
6615 | {0: [self.share_hash_chain_s]})) |
---|
6616 | hunk ./src/allmydata/test/test_storage.py 2204 |
---|
6617 | |
---|
6618 | # Next, we put what is supposed to be the root hash of |
---|
6619 | - # our share hash tree but isn't, along with the flat hash |
---|
6620 | - # of all the salts. |
---|
6621 | + # our share hash tree but isn't |
---|
6622 | d.addCallback(lambda ignored: |
---|
6623 | hunk ./src/allmydata/test/test_storage.py 2206 |
---|
6624 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6625 | + mw.put_root_hash(self.root_hash)) |
---|
6626 | # The root hash gets inserted at byte 9 (its position is in the header, |
---|
6627 | # and is fixed). The salt is right after it. |
---|
6628 | def _check(ignored): |
---|
6629 | hunk ./src/allmydata/test/test_storage.py 2220 |
---|
6630 | d.addCallback(lambda ignored: |
---|
6631 | mw.put_signature(self.signature)) |
---|
6632 | # The signature gets written to: |
---|
6633 | - # header + salts + blocks + block and share hash tree = 654 |
---|
6634 | - expected_signature_offset = 654 |
---|
6635 | + # header + salts + blocks + block and salt and share hash tree = 814 |
---|
6636 | + expected_signature_offset = expected_share_hash_offset + len(self.share_hash_chain_s) |
---|
6637 | self.failUnlessEqual(len(self.signature), 9) |
---|
6638 | d.addCallback(lambda ignored: |
---|
6639 | self.failUnlessEqual(read("si1", [0], [(expected_signature_offset, 9)]), |
---|
6640 | hunk ./src/allmydata/test/test_storage.py 2231 |
---|
6641 | d.addCallback(lambda ignored: |
---|
6642 | mw.put_verification_key(self.verification_key)) |
---|
6643 | # The verification key gets written to: |
---|
6644 | - # 654 + 9 = 663 bytes |
---|
6645 | - expected_verification_key_offset = 663 |
---|
6646 | + # 804 + 9 = 815 bytes |
---|
6647 | + expected_verification_key_offset = expected_signature_offset + len(self.signature) |
---|
6648 | self.failUnlessEqual(len(self.verification_key), 6) |
---|
6649 | d.addCallback(lambda ignored: |
---|
6650 | self.failUnlessEqual(read("si1", [0], [(expected_verification_key_offset, 6)]), |
---|
6651 | hunk ./src/allmydata/test/test_storage.py 2256 |
---|
6652 | # Next, we cause the offset table to be published. |
---|
6653 | d.addCallback(lambda ignored: |
---|
6654 | mw.finish_publishing()) |
---|
6655 | - expected_eof_offset = 669 |
---|
6656 | + expected_eof_offset = expected_verification_key_offset + len(self.verification_key) |
---|
6657 | |
---|
6658 | # The offset table starts at byte 91. Happily, we have already |
---|
6659 | # worked out most of these offsets above, but we want to make |
---|
6660 | hunk ./src/allmydata/test/test_storage.py 2289 |
---|
6661 | self.failUnlessEqual(read("si1", [0], [(83, 8)]), |
---|
6662 | {0: [expected_data_length]}) |
---|
6663 | # 91 4 The offset of the share data |
---|
6664 | - expected_offset = struct.pack(">L", 239) |
---|
6665 | + expected_offset = struct.pack(">L", expected_share_offset) |
---|
6666 | self.failUnlessEqual(read("si1", [0], [(91, 4)]), |
---|
6667 | {0: [expected_offset]}) |
---|
6668 | # 95 8 The offset of the encrypted private key |
---|
6669 | hunk ./src/allmydata/test/test_storage.py 2300 |
---|
6670 | expected_offset = struct.pack(">Q", expected_block_hash_offset) |
---|
6671 | self.failUnlessEqual(read("si1", [0], [(103, 8)]), |
---|
6672 | {0: [expected_offset]}) |
---|
6673 | - # 111 8 The offset of the share hash chain |
---|
6674 | - expected_offset = struct.pack(">Q", expected_share_hash_offset) |
---|
6675 | + # 111 8 The offset of the salt hash tree |
---|
6676 | + expected_offset = struct.pack(">Q", expected_salt_hash_offset) |
---|
6677 | self.failUnlessEqual(read("si1", [0], [(111, 8)]), |
---|
6678 | {0: [expected_offset]}) |
---|
6679 | hunk ./src/allmydata/test/test_storage.py 2304 |
---|
6680 | - # 119 8 The offset of the signature |
---|
6681 | - expected_offset = struct.pack(">Q", expected_signature_offset) |
---|
6682 | + # 119 8 The offset of the share hash chain |
---|
6683 | + expected_offset = struct.pack(">Q", expected_share_hash_offset) |
---|
6684 | self.failUnlessEqual(read("si1", [0], [(119, 8)]), |
---|
6685 | {0: [expected_offset]}) |
---|
6686 | hunk ./src/allmydata/test/test_storage.py 2308 |
---|
6687 | - # 127 8 The offset of the verification key |
---|
6688 | - expected_offset = struct.pack(">Q", expected_verification_key_offset) |
---|
6689 | + # 127 8 The offset of the signature |
---|
6690 | + expected_offset = struct.pack(">Q", expected_signature_offset) |
---|
6691 | self.failUnlessEqual(read("si1", [0], [(127, 8)]), |
---|
6692 | {0: [expected_offset]}) |
---|
6693 | hunk ./src/allmydata/test/test_storage.py 2312 |
---|
6694 | - # 135 8 offset of the EOF |
---|
6695 | - expected_offset = struct.pack(">Q", expected_eof_offset) |
---|
6696 | + # 135 8 offset of the verification_key |
---|
6697 | + expected_offset = struct.pack(">Q", expected_verification_key_offset) |
---|
6698 | self.failUnlessEqual(read("si1", [0], [(135, 8)]), |
---|
6699 | {0: [expected_offset]}) |
---|
6700 | hunk ./src/allmydata/test/test_storage.py 2316 |
---|
6701 | - # = 143 bytes in total. |
---|
6702 | + # 143 8 offset of the EOF |
---|
6703 | + expected_offset = struct.pack(">Q", expected_eof_offset) |
---|
6704 | + self.failUnlessEqual(read("si1", [0], [(143, 8)]), |
---|
6705 | + {0: [expected_offset]}) |
---|
6706 | d.addCallback(_check_offsets) |
---|
6707 | return d |
---|
6708 | |
---|
6709 | hunk ./src/allmydata/test/test_storage.py 2362 |
---|
6710 | return d |
---|
6711 | |
---|
6712 | |
---|
6713 | - def test_write_rejected_with_invalid_salt_hash(self): |
---|
6714 | - # Try writing an invalid salt hash. These should be SHA256d, and |
---|
6715 | - # 32 bytes long as a result. |
---|
6716 | - mw = self._make_new_mw("si2", 0) |
---|
6717 | - invalid_salt_hash = "b" * 31 |
---|
6718 | - d = defer.succeed(None) |
---|
6719 | - # Before this test can work, we need to put some blocks + salts, |
---|
6720 | - # a block hash tree, and a share hash tree. Otherwise, we'll see |
---|
6721 | - # failures that match what we are looking for, but are caused by |
---|
6722 | - # the constraints imposed on operation ordering. |
---|
6723 | - for i in xrange(6): |
---|
6724 | - d.addCallback(lambda ignored, i=i: |
---|
6725 | - mw.put_block(self.block, i, self.salt)) |
---|
6726 | - d.addCallback(lambda ignored: |
---|
6727 | - mw.put_encprivkey(self.encprivkey)) |
---|
6728 | - d.addCallback(lambda ignored: |
---|
6729 | - mw.put_blockhashes(self.block_hash_tree)) |
---|
6730 | - d.addCallback(lambda ignored: |
---|
6731 | - mw.put_sharehashes(self.share_hash_chain)) |
---|
6732 | - d.addCallback(lambda ignored: |
---|
6733 | - self.shouldFail(LayoutInvalid, "invalid root hash", |
---|
6734 | - None, mw.put_root_and_salt_hashes, |
---|
6735 | - self.root_hash, invalid_salt_hash)) |
---|
6736 | - return d |
---|
6737 | - |
---|
6738 | - |
---|
6739 | def test_write_rejected_with_invalid_root_hash(self): |
---|
6740 | # Try writing an invalid root hash. This should be SHA256d, and |
---|
6741 | # 32 bytes long as a result. |
---|
6742 | hunk ./src/allmydata/test/test_storage.py 2381 |
---|
6743 | d.addCallback(lambda ignored: |
---|
6744 | mw.put_blockhashes(self.block_hash_tree)) |
---|
6745 | d.addCallback(lambda ignored: |
---|
6746 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6747 | + d.addCallback(lambda ignored: |
---|
6748 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6749 | d.addCallback(lambda ignored: |
---|
6750 | self.shouldFail(LayoutInvalid, "invalid root hash", |
---|
6751 | hunk ./src/allmydata/test/test_storage.py 2386 |
---|
6752 | - None, mw.put_root_and_salt_hashes, |
---|
6753 | - invalid_root_hash, self.salt_hash)) |
---|
6754 | + None, mw.put_root_hash, invalid_root_hash)) |
---|
6755 | return d |
---|
6756 | |
---|
6757 | |
---|
6758 | hunk ./src/allmydata/test/test_storage.py 2461 |
---|
6759 | mw0.put_encprivkey(self.encprivkey)) |
---|
6760 | |
---|
6761 | |
---|
6762 | + # Try to write the salt hash tree without writing the block hash |
---|
6763 | + # tree. |
---|
6764 | + d.addCallback(lambda ignored: |
---|
6765 | + self.shouldFail(LayoutInvalid, "salt hash tree before bht", |
---|
6766 | + None, |
---|
6767 | + mw0.put_salthashes, self.salt_hash_tree)) |
---|
6768 | + |
---|
6769 | + |
---|
6770 | # Try to write the share hash chain without writing the block |
---|
6771 | # hash tree |
---|
6772 | d.addCallback(lambda ignored: |
---|
6773 | hunk ./src/allmydata/test/test_storage.py 2473 |
---|
6774 | self.shouldFail(LayoutInvalid, "share hash chain before " |
---|
6775 | - "block hash tree", |
---|
6776 | + "salt hash tree", |
---|
6777 | None, |
---|
6778 | mw0.put_sharehashes, self.share_hash_chain)) |
---|
6779 | |
---|
6780 | hunk ./src/allmydata/test/test_storage.py 2478 |
---|
6781 | # Try to write the root hash and salt hash without writing either the |
---|
6782 | - # block hashes or the share hashes |
---|
6783 | + # block hashes or the salt hashes or the share hashes |
---|
6784 | d.addCallback(lambda ignored: |
---|
6785 | self.shouldFail(LayoutInvalid, "root hash before share hashes", |
---|
6786 | None, |
---|
6787 | hunk ./src/allmydata/test/test_storage.py 2482 |
---|
6788 | - mw0.put_root_and_salt_hashes, |
---|
6789 | - self.root_hash, self.salt_hash)) |
---|
6790 | + mw0.put_root_hash, self.root_hash)) |
---|
6791 | |
---|
6792 | # Now write the block hashes and try again |
---|
6793 | d.addCallback(lambda ignored: |
---|
6794 | hunk ./src/allmydata/test/test_storage.py 2487 |
---|
6795 | mw0.put_blockhashes(self.block_hash_tree)) |
---|
6796 | + |
---|
6797 | + d.addCallback(lambda ignored: |
---|
6798 | + self.shouldFail(LayoutInvalid, "share hash before salt hashes", |
---|
6799 | + None, |
---|
6800 | + mw0.put_sharehashes, self.share_hash_chain)) |
---|
6801 | d.addCallback(lambda ignored: |
---|
6802 | self.shouldFail(LayoutInvalid, "root hash before share hashes", |
---|
6803 | hunk ./src/allmydata/test/test_storage.py 2494 |
---|
6804 | - None, mw0.put_root_and_salt_hashes, |
---|
6805 | - self.root_hash, self.salt_hash)) |
---|
6806 | + None, mw0.put_root_hash, self.root_hash)) |
---|
6807 | |
---|
6808 | # We haven't yet put the root hash on the share, so we shouldn't |
---|
6809 | # be able to sign it. |
---|
6810 | hunk ./src/allmydata/test/test_storage.py 2512 |
---|
6811 | None, mw0.put_verification_key, |
---|
6812 | self.verification_key)) |
---|
6813 | |
---|
6814 | - # Now write the share hashes and verify that it works. |
---|
6815 | + # Now write the salt hashes, and try again. |
---|
6816 | d.addCallback(lambda ignored: |
---|
6817 | hunk ./src/allmydata/test/test_storage.py 2514 |
---|
6818 | - mw0.put_sharehashes(self.share_hash_chain)) |
---|
6819 | + mw0.put_salthashes(self.salt_hash_tree)) |
---|
6820 | + |
---|
6821 | + d.addCallback(lambda ignored: |
---|
6822 | + self.shouldFail(LayoutInvalid, "root hash before share hashes", |
---|
6823 | + None, |
---|
6824 | + mw0.put_root_hash, self.root_hash)) |
---|
6825 | |
---|
6826 | # We should still be unable to sign the header |
---|
6827 | d.addCallback(lambda ignored: |
---|
6828 | hunk ./src/allmydata/test/test_storage.py 2527 |
---|
6829 | None, |
---|
6830 | mw0.put_signature, self.signature)) |
---|
6831 | |
---|
6832 | + # Now write the share hashes. |
---|
6833 | + d.addCallback(lambda ignored: |
---|
6834 | + mw0.put_sharehashes(self.share_hash_chain)) |
---|
6835 | # We should be able to write the root hash now too |
---|
6836 | d.addCallback(lambda ignored: |
---|
6837 | hunk ./src/allmydata/test/test_storage.py 2532 |
---|
6838 | - mw0.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6839 | + mw0.put_root_hash(self.root_hash)) |
---|
6840 | |
---|
6841 | # We should still be unable to put the verification key |
---|
6842 | d.addCallback(lambda ignored: |
---|
6843 | hunk ./src/allmydata/test/test_storage.py 2569 |
---|
6844 | d.addCallback(lambda ignored: |
---|
6845 | mw.put_blockhashes(self.block_hash_tree)) |
---|
6846 | d.addCallback(lambda ignored: |
---|
6847 | + mw.put_salthashes(self.salt_hash_tree)) |
---|
6848 | + d.addCallback(lambda ignored: |
---|
6849 | mw.put_sharehashes(self.share_hash_chain)) |
---|
6850 | d.addCallback(lambda ignored: |
---|
6851 | hunk ./src/allmydata/test/test_storage.py 2573 |
---|
6852 | - mw.put_root_and_salt_hashes(self.root_hash, self.salt_hash)) |
---|
6853 | + mw.put_root_hash(self.root_hash)) |
---|
6854 | d.addCallback(lambda ignored: |
---|
6855 | mw.put_signature(self.signature)) |
---|
6856 | d.addCallback(lambda ignored: |
---|
6857 | hunk ./src/allmydata/test/test_storage.py 2768 |
---|
6858 | # This should be enough to fill in both the encoding parameters |
---|
6859 | # and the table of offsets, which will complete the version |
---|
6860 | # information tuple. |
---|
6861 | - d.addCallback(_make_mr, 143) |
---|
6862 | + d.addCallback(_make_mr, 151) |
---|
6863 | d.addCallback(lambda mr: |
---|
6864 | mr.get_verinfo()) |
---|
6865 | def _check_verinfo(verinfo): |
---|
6866 | hunk ./src/allmydata/test/test_storage.py 2804 |
---|
6867 | d.addCallback(_check_verinfo) |
---|
6868 | # This is not enough data to read a block and a share, so the |
---|
6869 | # wrapper should attempt to read this from the remote server. |
---|
6870 | - d.addCallback(_make_mr, 143) |
---|
6871 | + d.addCallback(_make_mr, 151) |
---|
6872 | d.addCallback(lambda mr: |
---|
6873 | mr.get_block_and_salt(0)) |
---|
6874 | def _check_block_and_salt((block, salt)): |
---|
6875 | hunk ./src/allmydata/test/test_storage.py 2815 |
---|
6876 | # are 6 * 16 = 96 bytes of salts before we can write shares. |
---|
6877 | # Each block has two bytes, so 143 + 96 + 2 = 241 bytes should |
---|
6878 | # be enough to read one block. |
---|
6879 | - d.addCallback(_make_mr, 241) |
---|
6880 | + d.addCallback(_make_mr, 249) |
---|
6881 | d.addCallback(lambda mr: |
---|
6882 | mr.get_block_and_salt(0)) |
---|
6883 | d.addCallback(_check_block_and_salt) |
---|
6884 | } |
---|
6885 | |
---|
6886 | Context: |
---|
6887 | |
---|
6888 | [docs: about.html link to home page early on, and be decentralized storage instead of cloud storage this time around |
---|
6889 | zooko@zooko.com**20100619065318 |
---|
6890 | Ignore-this: dc6db03f696e5b6d2848699e754d8053 |
---|
6891 | ] |
---|
6892 | [docs: update about.html, especially to have a non-broken link to quickstart.html, and also to comment out the broken links to "for Paranoids" and "for Corporates" |
---|
6893 | zooko@zooko.com**20100619065124 |
---|
6894 | Ignore-this: e292c7f51c337a84ebfeb366fbd24d6c |
---|
6895 | ] |
---|
6896 | [TAG allmydata-tahoe-1.7.0 |
---|
6897 | zooko@zooko.com**20100619052631 |
---|
6898 | Ignore-this: d21e27afe6d85e2e3ba6a3292ba2be1 |
---|
6899 | ] |
---|
6900 | [docs: update relnotes.txt for Tahoe-LAFS v1.7.0! |
---|
6901 | zooko@zooko.com**20100619052048 |
---|
6902 | Ignore-this: 1dd2c851f02adf3ab5a33040051fe05a |
---|
6903 | ... and remove relnotes-short.txt (just use the first section of relnotes.txt for that purpose) |
---|
6904 | ] |
---|
6905 | [docs: update known_issues.txt with more detail about web browser "safe-browsing" features and slightly tweaked formatting |
---|
6906 | zooko@zooko.com**20100619051734 |
---|
6907 | Ignore-this: afc10be0da2517ddd0b58e42ef9aa46d |
---|
6908 | ] |
---|
6909 | [docs: quickstart.html: link to 1.7.0 zip file and add UTF-8 BOM |
---|
6910 | zooko@zooko.com**20100619050124 |
---|
6911 | Ignore-this: 5104fc90af542b97662b4016da975f34 |
---|
6912 | ] |
---|
6913 | [docs: more CREDITS for Kevan, plus utf-8 BOM |
---|
6914 | zooko@zooko.com**20100619045809 |
---|
6915 | Ignore-this: ee9c3b7cf7e385c8ca396091cebc9ca6 |
---|
6916 | ] |
---|
6917 | [docs: update NEWS for release 1.7.0 |
---|
6918 | zooko@zooko.com**20100619045750 |
---|
6919 | Ignore-this: 112c352fd52297ebff8138896fc6353d |
---|
6920 | ] |
---|
6921 | [docs: apply patch from duck for #937 about "tahoe run" not working on introducers |
---|
6922 | zooko@zooko.com**20100619040754 |
---|
6923 | Ignore-this: d7213313f16e524996e91058e287a954 |
---|
6924 | ] |
---|
6925 | [webapi.txt: fix statement about leap seconds. |
---|
6926 | david-sarah@jacaranda.org**20100619035603 |
---|
6927 | Ignore-this: 80b685446e915877a421cf3e31cedf30 |
---|
6928 | ] |
---|
6929 | [running.html: Tahoe->Tahoe-LAFS in what used to be using.html, and #tahoe->#tahoe-lafs (IRC channel). |
---|
6930 | david-sarah@jacaranda.org**20100619033152 |
---|
6931 | Ignore-this: a0dfdfb46eab639aaa064981fb933c5c |
---|
6932 | ] |
---|
6933 | [test_backupdb.py: skip test_unicode if we can't represent the test filenames. |
---|
6934 | david-sarah@jacaranda.org**20100619022620 |
---|
6935 | Ignore-this: 6ee564b6c07f9bb0e89a25dc5b37194f |
---|
6936 | ] |
---|
6937 | [test_web.py: correct a test that was missed in the change to not write ctime/mtime. |
---|
6938 | david-sarah@jacaranda.org**20100619021718 |
---|
6939 | Ignore-this: 92edc2e1fd43b3e86e6b49bc43bae122 |
---|
6940 | ] |
---|
6941 | [dirnode.py: stop writing 'ctime' and 'mtime' fields. Includes documentation and test changes. |
---|
6942 | david-sarah@jacaranda.org**20100618230119 |
---|
6943 | Ignore-this: 709119898499769dd64c7977db7c84a6 |
---|
6944 | ] |
---|
6945 | [test_storage.py: print more information on test failures. |
---|
6946 | david-sarah@jacaranda.org**20100617034623 |
---|
6947 | Ignore-this: cc9a8656802a718ca4f2a6a530d35977 |
---|
6948 | ] |
---|
6949 | [running.html: describe where 'bin/tahoe' is only once. |
---|
6950 | david-sarah@jacaranda.org**20100617033603 |
---|
6951 | Ignore-this: 6d92d9d8c77f3dfddfa7d061cbf2a791 |
---|
6952 | ] |
---|
6953 | [Merge using.html into running.html. |
---|
6954 | david-sarah@jacaranda.org**20100617012857 |
---|
6955 | Ignore-this: a0fa8b56621fdb976bef4e5f4f6c824a |
---|
6956 | ] |
---|
6957 | [Remove firewall section from running.html and say to read configuration.txt instead. |
---|
6958 | david-sarah@jacaranda.org**20100617004513 |
---|
6959 | Ignore-this: d2e46fffa4855b01093e8240b5fd1eff |
---|
6960 | ] |
---|
6961 | [FTP-and-SFTP.txt: add Known Issues section. |
---|
6962 | david-sarah@jacaranda.org**20100619004311 |
---|
6963 | Ignore-this: 8d9b1da941cbc24657bb6ec268f984dd |
---|
6964 | ] |
---|
6965 | [FTP-and-SFTP.txt: remove description of public key format that is not actually implemented. Document that SFTP does not support server private keys with passphrases, and that FTP cannot list directories containing mutable files. |
---|
6966 | david-sarah@jacaranda.org**20100619001738 |
---|
6967 | Ignore-this: bf9ef53b85b934822ec76060e1fcb3cb |
---|
6968 | ] |
---|
6969 | [configuration.txt and servers-of-happiness.txt: 1 <= happy <= N, not k <= happy <= N. Also minor wording changes. |
---|
6970 | david-sarah@jacaranda.org**20100618050710 |
---|
6971 | Ignore-this: edac0716e753e1f1c4c755c85bec9a19 |
---|
6972 | ] |
---|
6973 | [test_cli.py: fix test failure in CLI.test_listdir_unicode_good due to filenames returned from listdir_unicode no longer being normalized. |
---|
6974 | david-sarah@jacaranda.org**20100618045110 |
---|
6975 | Ignore-this: 598ffaef02d71e075f7e08fac44f48ff |
---|
6976 | ] |
---|
6977 | [tahoe backup: unicode tests. |
---|
6978 | david-sarah@jacaranda.org**20100618035211 |
---|
6979 | Ignore-this: 88ebab9f3218f083fdc635bff6599b60 |
---|
6980 | ] |
---|
6981 | [CLI: allow Unicode patterns in exclude option to 'tahoe backup'. |
---|
6982 | david-sarah@jacaranda.org**20100617033901 |
---|
6983 | Ignore-this: 9d971129e1c8bae3c1cc3220993d592e |
---|
6984 | ] |
---|
6985 | [dirnodes: fix normalization hole where childnames in directories created by nodemaker.create_mutable/immutable_directory would not be normalized. Add a test that we normalize names coming out of a directory. |
---|
6986 | david-sarah@jacaranda.org**20100618000249 |
---|
6987 | Ignore-this: 46a9226eff1003013b067edbdbd4c25b |
---|
6988 | ] |
---|
6989 | [dirnode.py: comments about normalization changes. |
---|
6990 | david-sarah@jacaranda.org**20100617041411 |
---|
6991 | Ignore-this: 9040c4854e73a71dbbb55b50ea3b41b2 |
---|
6992 | ] |
---|
6993 | [stringutils.py: remove unused import. |
---|
6994 | david-sarah@jacaranda.org**20100617034440 |
---|
6995 | Ignore-this: 16ec7d737c34665156c2ac486acd545a |
---|
6996 | ] |
---|
6997 | [test_stringutils.py: take account of the output of listdir_unicode no longer being normalized. Also use Unicode escapes, not UTF-8. |
---|
6998 | david-sarah@jacaranda.org**20100617034409 |
---|
6999 | Ignore-this: 47f3f072f0e2efea0abeac130f84c56f |
---|
7000 | ] |
---|
7001 | [test_dirnode.py: partial tests for normalization changes. |
---|
7002 | david-sarah@jacaranda.org**20100617034025 |
---|
7003 | Ignore-this: 2e3169dd8b120d42dff35bd267dcb417 |
---|
7004 | ] |
---|
7005 | [SFTP: get 'ctime' attribute from 'tahoe:linkmotime'. |
---|
7006 | david-sarah@jacaranda.org**20100617033744 |
---|
7007 | Ignore-this: b2fabe12235f2e2a487c0b56c39953e7 |
---|
7008 | ] |
---|
7009 | [stringutils.py: don't NFC-normalize the output of listdir_unicode. |
---|
7010 | david-sarah@jacaranda.org**20100617015537 |
---|
7011 | Ignore-this: 93c9b6f3d7c6812a0afa8d9e1b0b4faa |
---|
7012 | ] |
---|
7013 | [stringutils.py: Add encoding argument to quote_output. Also work around a bug in locale.getpreferredencoding on older Pythons. |
---|
7014 | david-sarah@jacaranda.org**20100616042012 |
---|
7015 | Ignore-this: 48174c37ad95205997e4d3cdd81f1e28 |
---|
7016 | ] |
---|
7017 | [Provisional patch to NFC-normalize filenames going in and out of Tahoe directories. |
---|
7018 | david-sarah@jacaranda.org**20100616031450 |
---|
7019 | Ignore-this: ed08c9d8df37ef0b7cca42bb562c996b |
---|
7020 | ] |
---|
7021 | [how_to_make_a_tahoe-lafs_release.txt: reordering, add fuse-sshfs@lists.sourceforge.list as place to send relnotes. |
---|
7022 | david-sarah@jacaranda.org**20100618041854 |
---|
7023 | Ignore-this: 2e380a6e72917d3a20a65ceccd9a4df |
---|
7024 | ] |
---|
7025 | [running.html: fix overeager replacement of 'tahoe' with 'Tahoe-LAFS', and some simplifications. |
---|
7026 | david-sarah@jacaranda.org**20100617000952 |
---|
7027 | Ignore-this: 472b4b531c866574ed79f076b58495b5 |
---|
7028 | ] |
---|
7029 | [Add a specification for servers of happiness. |
---|
7030 | Kevan Carstensen <kevan@isnotajoke.com>**20100524003508 |
---|
7031 | Ignore-this: 982e2be8a411be5beaf3582bdfde6151 |
---|
7032 | ] |
---|
7033 | [Note that servers of happiness only applies to immutable files for the moment |
---|
7034 | Kevan Carstensen <kevan@isnotajoke.com>**20100524042836 |
---|
7035 | Ignore-this: cf83cac7a2b3ed347ae278c1a7d9a176 |
---|
7036 | ] |
---|
7037 | [Add a note about running Tahoe-LAFS on a small grid to running.html |
---|
7038 | zooko@zooko.com**20100616140227 |
---|
7039 | Ignore-this: 14dfbff0d47144f7c2375108c6055dc2 |
---|
7040 | also Change "tahoe" and "Tahoe" to "Tahoe-LAFS" in running.html |
---|
7041 | author: Kevan Carstensen |
---|
7042 | ] |
---|
7043 | [test_system.py: investigate failure in allmydata.test.test_system.SystemTest.test_upload_and_download_random_key due to bytes_sent not being an int |
---|
7044 | david-sarah@jacaranda.org**20100616001648 |
---|
7045 | Ignore-this: 9c78092ab7bfdc909acae3a144ddd1f8 |
---|
7046 | ] |
---|
7047 | [SFTP: remove a dubious use of 'pragma: no cover'. |
---|
7048 | david-sarah@jacaranda.org**20100613164356 |
---|
7049 | Ignore-this: 8f96a81b1196017ed6cfa1d914e56fa5 |
---|
7050 | ] |
---|
7051 | [SFTP: test that renaming onto a just-opened file fails. |
---|
7052 | david-sarah@jacaranda.org**20100612033709 |
---|
7053 | Ignore-this: 9b14147ad78b16a5ab0e0e4813491414 |
---|
7054 | ] |
---|
7055 | [SFTP: further small improvements to test coverage. Also ensure that after a test failure, later tests don't fail spuriously due to the checks for heisenfile leaks. |
---|
7056 | david-sarah@jacaranda.org**20100612030737 |
---|
7057 | Ignore-this: 4ec1dd3d7542be42007987a2f51508e7 |
---|
7058 | ] |
---|
7059 | [SFTP: further improve test coverage (paths containing '.', bad data for posix-rename extension, and error in test of openShell). |
---|
7060 | david-sarah@jacaranda.org**20100611213142 |
---|
7061 | Ignore-this: 956f9df7f9e8a66b506ca58dd9a5dbe7 |
---|
7062 | ] |
---|
7063 | [SFTP: improve test coverage for no-write on mutable files, and check for heisenfile table leaks in all relevant tests. Delete test_memory_leak since it is now redundant. |
---|
7064 | david-sarah@jacaranda.org**20100611205752 |
---|
7065 | Ignore-this: 88be1cf323c10dd534a4b8fdac121e31 |
---|
7066 | ] |
---|
7067 | [CLI.txt: introduce 'create-alias' before 'add-alias', document Unicode argument support, and other minor updates. |
---|
7068 | david-sarah@jacaranda.org**20100610225547 |
---|
7069 | Ignore-this: de7326e98d79291cdc15aed86ae61fe8 |
---|
7070 | ] |
---|
7071 | [SFTP: add test for extension of file opened with FXF_APPEND. |
---|
7072 | david-sarah@jacaranda.org**20100610182647 |
---|
7073 | Ignore-this: c0216d26453ce3cb4b92eef37d218fb4 |
---|
7074 | ] |
---|
7075 | [NEWS: add UTF-8 coding declaration. |
---|
7076 | david-sarah@jacaranda.org**20100609234851 |
---|
7077 | Ignore-this: 3e6ef125b278e0a982c88d23180a78ae |
---|
7078 | ] |
---|
7079 | [tests: bump up the timeout on this iputil test from 2s to 4s |
---|
7080 | zooko@zooko.com**20100609143017 |
---|
7081 | Ignore-this: 786b7f7bbc85d45cdf727a6293750798 |
---|
7082 | ] |
---|
7083 | [docs: a few tweaks to NEWS and CREDITS and make quickstart.html point to 1.7.0β! |
---|
7084 | zooko@zooko.com**20100609142927 |
---|
7085 | Ignore-this: f8097d3062f41f06c4420a7c84a56481 |
---|
7086 | ] |
---|
7087 | [docs: Update NEWS file with new features and bugfixes in 1.7.0 |
---|
7088 | francois@ctrlaltdel.ch**20100609091120 |
---|
7089 | Ignore-this: 8c1014e4469ef530e5ff48d7d6ae71c5 |
---|
7090 | ] |
---|
7091 | [docs: wording fix, thanks to Jeremy Visser, fix #987 |
---|
7092 | francois@ctrlaltdel.ch**20100609081103 |
---|
7093 | Ignore-this: 6d2e627e0f1cd58c0e1394e193287a4b |
---|
7094 | ] |
---|
7095 | [SFTP: fix most significant memory leak described in #1045 (due to a file being added to all_heisenfiles under more than one direntry when renamed). |
---|
7096 | david-sarah@jacaranda.org**20100609080003 |
---|
7097 | Ignore-this: 490b4c14207f6725d0dd32c395fbcefa |
---|
7098 | ] |
---|
7099 | [test_stringutils.py: Fix test failure on CentOS builder, possibly Python 2.4.3-related. |
---|
7100 | david-sarah@jacaranda.org**20100609065056 |
---|
7101 | Ignore-this: 503b561b213baf1b92ae641f2fdf080a |
---|
7102 | ] |
---|
7103 | [Fix for Unicode-related test failures on Zooko's OS X 10.6 machine. |
---|
7104 | david-sarah@jacaranda.org**20100609055448 |
---|
7105 | Ignore-this: 395ad16429e56623edfa74457a121190 |
---|
7106 | ] |
---|
7107 | [docs: update relnote.txt for Tahoe-LAFS v1.7.0β |
---|
7108 | zooko@zooko.com**20100609054602 |
---|
7109 | Ignore-this: 52e1bf86a91d45315960fb8806b7a479 |
---|
7110 | ] |
---|
7111 | [stringutils.py, sftpd.py: Portability fixes for Python <= 2.5. |
---|
7112 | david-sarah@jacaranda.org**20100609013302 |
---|
7113 | Ignore-this: 9d9ce476ee1b96796e0f48cc5338f852 |
---|
7114 | ] |
---|
7115 | [setup: move the mock library from install_requires to tests_require (re: #1016) |
---|
7116 | zooko@zooko.com**20100609050542 |
---|
7117 | Ignore-this: c51a4ff3e19ed630755be752d2233db4 |
---|
7118 | ] |
---|
7119 | [Back out Windows-specific Unicode argument support for v1.7. |
---|
7120 | david-sarah@jacaranda.org**20100609000803 |
---|
7121 | Ignore-this: b230ffe6fdaf9a0d85dfe745b37b42fb |
---|
7122 | ] |
---|
7123 | [_auto_deps.py: allow Python 2.4.3 on Redhat-based distributions. |
---|
7124 | david-sarah@jacaranda.org**20100609003646 |
---|
7125 | Ignore-this: ad3cafdff200caf963024873d0ebff3c |
---|
7126 | ] |
---|
7127 | [setup: show-tool-versions.py: print out the output from the unix command "locale" and re-arrange encoding data a little bit |
---|
7128 | zooko@zooko.com**20100609040714 |
---|
7129 | Ignore-this: 69382719b462d13ff940fcd980776004 |
---|
7130 | ] |
---|
7131 | [setup: add zope.interface to the packages described by show-tool-versions.py |
---|
7132 | zooko@zooko.com**20100609034915 |
---|
7133 | Ignore-this: b5262b2af5c953a5f68a60bd48dcaa75 |
---|
7134 | ] |
---|
7135 | [CREDITS: update François's Description |
---|
7136 | zooko@zooko.com**20100608155513 |
---|
7137 | Ignore-this: a266b438d25ca2cb28eafff75aa4b2a |
---|
7138 | ] |
---|
7139 | [CREDITS: jsgf |
---|
7140 | zooko@zooko.com**20100608143052 |
---|
7141 | Ignore-this: 10abe06d40b88e22a9107d30f1b84810 |
---|
7142 | ] |
---|
7143 | [setup: rename the setuptools_trial .egg that comes bundled in the base dir to not have "-py2.6" in its name, since it works with other versions of python as well |
---|
7144 | zooko@zooko.com**20100608041607 |
---|
7145 | Ignore-this: 64fe386d2e5fba0ab441116e74dad5a3 |
---|
7146 | ] |
---|
7147 | [setup: rename the darcsver .egg that comes bundled in the base dir to not have "-py2.6" in its name, since it works with other versions of python as well |
---|
7148 | zooko@zooko.com**20100608041534 |
---|
7149 | Ignore-this: 53f925f160256409cf01b76d2583f83f |
---|
7150 | ] |
---|
7151 | [SFTP: suppress NoSuchChildError if heisenfile attributes have been updated in setAttrs, in the case where the parent is available. |
---|
7152 | david-sarah@jacaranda.org**20100608063753 |
---|
7153 | Ignore-this: 8c72a5a9c15934f8fe4594ba3ee50ddd |
---|
7154 | ] |
---|
7155 | [SFTP: ignore permissions when opening a file (needed for sshfs interoperability). |
---|
7156 | david-sarah@jacaranda.org**20100608055700 |
---|
7157 | Ignore-this: f87f6a430f629326a324ddd94426c797 |
---|
7158 | ] |
---|
7159 | [test_web.py: fix pyflakes warnings introduced by byterange patch. |
---|
7160 | david-sarah@jacaranda.org**20100608042012 |
---|
7161 | Ignore-this: a7612724893b51d1154dec4372e0508 |
---|
7162 | ] |
---|
7163 | [Improve HTTP/1.1 byterange handling |
---|
7164 | Jeremy Fitzhardinge <jeremy@goop.org>**20100310025913 |
---|
7165 | Ignore-this: 6d69e694973d618f0dc65983735cd9be |
---|
7166 | |
---|
7167 | Fix parsing of a Range: header to support: |
---|
7168 | - multiple ranges (parsed, but not returned) |
---|
7169 | - suffix byte ranges ("-2139") |
---|
7170 | - correct handling of incorrectly formatted range headers |
---|
7171 | (correct behaviour is to ignore the header and return the full |
---|
7172 | file) |
---|
7173 | - return appropriate error for ranges outside the file |
---|
7174 | |
---|
7175 | Multiple ranges are parsed, but only the first range is returned. |
---|
7176 | Returning multiple ranges requires using the multipart/byterange |
---|
7177 | content type. |
---|
7178 | |
---|
7179 | ] |
---|
7180 | [tests: bump up the timeout on these tests; MM's buildslave is sometimes extremely slow on tests, but it will complete them if given enough time. MM is working on making that buildslave more predictable in how long it takes to run tests. |
---|
7181 | zooko@zooko.com**20100608033754 |
---|
7182 | Ignore-this: 98dc27692c5ace1e4b0650b6680629d7 |
---|
7183 | ] |
---|
7184 | [test_cli.py: remove invalid 'test_listdir_unicode_bad' test. |
---|
7185 | david-sarah@jacaranda.org**20100607183730 |
---|
7186 | Ignore-this: fadfe87980dc1862f349bfcc21b2145f |
---|
7187 | ] |
---|
7188 | [check_memory.py: adapt to servers-of-happiness changes. |
---|
7189 | david-sarah@jacaranda.org**20100608013528 |
---|
7190 | Ignore-this: c6b28411c543d1aea2f148a955f7998 |
---|
7191 | ] |
---|
7192 | [show-tool-versions.py: platform.linux_distribution() is not always available |
---|
7193 | david-sarah@jacaranda.org**20100608004523 |
---|
7194 | Ignore-this: 793fb4050086723af05d06bed8b1b92a |
---|
7195 | ] |
---|
7196 | [show-tool-versions.py: show platform.linux_distribution() |
---|
7197 | david-sarah@jacaranda.org**20100608003829 |
---|
7198 | Ignore-this: 81cb5e5fc6324044f0fc6d82903c8223 |
---|
7199 | ] |
---|
7200 | [Remove the 'tahoe debug consolidate' subcommand. |
---|
7201 | david-sarah@jacaranda.org**20100607183757 |
---|
7202 | Ignore-this: 4b14daa3ae557cea07d6e119d25dafe9 |
---|
7203 | ] |
---|
7204 | [common_http.py, tahoe_cp.py: Fix an error in calling the superclass constructor in HTTPError and MissingSourceError (introduced by the Unicode fixes). |
---|
7205 | david-sarah@jacaranda.org**20100607174714 |
---|
7206 | Ignore-this: 1a118d593d81c918a4717c887f033aec |
---|
7207 | ] |
---|
7208 | [tests: drastically increase timeout of this very time-consuming test in honor of François's ARM box |
---|
7209 | zooko@zooko.com**20100607115929 |
---|
7210 | Ignore-this: bf1bb52ffb6b5ccae71d4dde14621bc8 |
---|
7211 | ] |
---|
7212 | [setup: update authorship, datestamp, licensing, and add special exceptions to allow combination with Eclipse- and QPL- licensed code |
---|
7213 | zooko@zooko.com**20100607062329 |
---|
7214 | Ignore-this: 5a1d7b12dfafd61283ea65a245416381 |
---|
7215 | ] |
---|
7216 | [FTP-and-SFTP.txt: minor technical correction to doc for 'no-write' flag. |
---|
7217 | david-sarah@jacaranda.org**20100607061600 |
---|
7218 | Ignore-this: 66aee0c1b6c00538602d08631225e114 |
---|
7219 | ] |
---|
7220 | [test_stringutils.py: trivial error in exception message for skipped test. |
---|
7221 | david-sarah@jacaranda.org**20100607061455 |
---|
7222 | Ignore-this: f261a5d4e2b8fe3bcc37e02539ba1ae2 |
---|
7223 | ] |
---|
7224 | [More Unicode test fixes. |
---|
7225 | david-sarah@jacaranda.org**20100607053358 |
---|
7226 | Ignore-this: 6a271fb77c31f28cb7bdba63b26a2dd2 |
---|
7227 | ] |
---|
7228 | [Unicode fixes for platforms with non-native-Unicode filesystems. |
---|
7229 | david-sarah@jacaranda.org**20100607043238 |
---|
7230 | Ignore-this: 2134dc1793c4f8e50350bd749c4c98c2 |
---|
7231 | ] |
---|
7232 | [Unicode fixes. |
---|
7233 | david-sarah@jacaranda.org**20100607010215 |
---|
7234 | Ignore-this: d58727b5cd2ce00e6b6dae3166030138 |
---|
7235 | ] |
---|
7236 | [setup: organize misc/ scripts and tools and remove obsolete ones |
---|
7237 | zooko@zooko.com**20100607051618 |
---|
7238 | Ignore-this: 161db1158c6b7be8365b0b3dee2e0b28 |
---|
7239 | This is for ticket #1068. |
---|
7240 | ] |
---|
7241 | [quickstart.html: link to snapshots page, sorted with most recent first. |
---|
7242 | david-sarah@jacaranda.org**20100606221127 |
---|
7243 | Ignore-this: 93ea7e6ee47acc66f6daac9cabffed2d |
---|
7244 | ] |
---|
7245 | [quickstart.html: We haven't released 1.7beta yet. |
---|
7246 | david-sarah@jacaranda.org**20100606220301 |
---|
7247 | Ignore-this: 4e18898cfdb08cc3ddd1ff94d43fdda7 |
---|
7248 | ] |
---|
7249 | [setup: loosen the Desert Island test to allow it to check the network for new packages as long as it doesn't actually download any |
---|
7250 | zooko@zooko.com**20100606175717 |
---|
7251 | Ignore-this: e438a8eb3c1b0e68080711ec6ff93ffa |
---|
7252 | (You can look but don't touch.) |
---|
7253 | ] |
---|
7254 | [Raise Python version requirement to 2.4.4 for non-UCS-2 builds, to avoid a critical Python security bug. |
---|
7255 | david-sarah@jacaranda.org**20100605031713 |
---|
7256 | Ignore-this: 2df2b6d620c5d8191c79eefe655059e2 |
---|
7257 | ] |
---|
7258 | [setup: have the buildbots print out locale.getpreferredencoding(), locale.getdefaultlocale(), locale.getlocale(), and os.path.supports_unicode_filenames |
---|
7259 | zooko@zooko.com**20100605162932 |
---|
7260 | Ignore-this: 85e31e0e0e1364e9215420e272d58116 |
---|
7261 | Even though that latter one is completely useless, I'm curious. |
---|
7262 | ] |
---|
7263 | [unicode tests: fix missing import |
---|
7264 | zooko@zooko.com**20100604142630 |
---|
7265 | Ignore-this: db437fe8009971882aaea9de05e2bc3 |
---|
7266 | ] |
---|
7267 | [unicode: make test_cli test a non-ascii argument, and make the fallback term encoding be locale.getpreferredencoding() |
---|
7268 | zooko@zooko.com**20100604141251 |
---|
7269 | Ignore-this: b2bfc07942f69141811e59891842bd8c |
---|
7270 | ] |
---|
7271 | [unicode: always decode json manifest as utf-8 then encode for stdout |
---|
7272 | zooko@zooko.com**20100604084840 |
---|
7273 | Ignore-this: ac481692315fae870a0f3562bd7db48e |
---|
7274 | pyflakes pointed out that the exception handler fallback called an un-imported function, showing that the fallback wasn't being exercised. |
---|
7275 | I'm not 100% sure that this patch is right and would appreciate François or someone reviewing it. |
---|
7276 | ] |
---|
7277 | [fix flakes |
---|
7278 | zooko@zooko.com**20100604075845 |
---|
7279 | Ignore-this: 3e6a84b78771b0ad519e771a13605f0 |
---|
7280 | ] |
---|
7281 | [fix syntax of assertion handling that isn't portable to older versions of Python |
---|
7282 | zooko@zooko.com**20100604075805 |
---|
7283 | Ignore-this: 3a12b293aad25883fb17230266eb04ec |
---|
7284 | ] |
---|
7285 | [test_stringutils.py: Skip test test_listdir_unicode_good if filesystem supports only ASCII filenames |
---|
7286 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100521160839 |
---|
7287 | Ignore-this: f2ccdbd04c8d9f42f1efb0eb80018257 |
---|
7288 | ] |
---|
7289 | [test_stringutils.py: Skip test_listdir_unicode on mocked platform which cannot store non-ASCII filenames |
---|
7290 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100521160559 |
---|
7291 | Ignore-this: b93fde736a8904712b506e799250a600 |
---|
7292 | ] |
---|
7293 | [test_stringutils.py: Add a test class for OpenBSD 4.1 with LANG=C |
---|
7294 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100521140053 |
---|
7295 | Ignore-this: 63f568aec259cef0e807752fc8150b73 |
---|
7296 | ] |
---|
7297 | [test_stringutils.py: Mock the open() call in test_open_unicode |
---|
7298 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100521135817 |
---|
7299 | Ignore-this: d8be4e56a6eefe7d60f97f01ea20ac67 |
---|
7300 | |
---|
7301 | This test ensure that open(a_unicode_string) is used on Unicode platforms |
---|
7302 | (Windows or MacOS X) and that open(a_correctly_encoded_bytestring) on other |
---|
7303 | platforms such as Unix. |
---|
7304 | |
---|
7305 | ] |
---|
7306 | [test_stringutils.py: Fix a trivial Python 2.4 syntax incompatibility |
---|
7307 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100521093345 |
---|
7308 | Ignore-this: 9297e3d14a0dd37d0c1a4c6954fd59d3 |
---|
7309 | ] |
---|
7310 | [test_cli.py: Fix tests when sys.stdout.encoding=None and refactor this code into functions |
---|
7311 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100520084447 |
---|
7312 | Ignore-this: cf2286e225aaa4d7b1927c78c901477f |
---|
7313 | ] |
---|
7314 | [Fix handling of correctly encoded unicode filenames (#534) |
---|
7315 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100520004356 |
---|
7316 | Ignore-this: 8a3a7df214a855f5a12dc0eeab6f2e39 |
---|
7317 | |
---|
7318 | Tahoe CLI commands working on local files, for instance 'tahoe cp' or 'tahoe |
---|
7319 | backup', have been improved to correctly handle filenames containing non-ASCII |
---|
7320 | characters. |
---|
7321 | |
---|
7322 | In the case where Tahoe encounters a filename which cannot be decoded using the |
---|
7323 | system encoding, an error will be returned and the operation will fail. Under |
---|
7324 | Linux, this typically happens when the filesystem contains filenames encoded |
---|
7325 | with another encoding, for instance latin1, than the system locale, for |
---|
7326 | instance UTF-8. In such case, you'll need to fix your system with tools such |
---|
7327 | as 'convmv' before using Tahoe CLI. |
---|
7328 | |
---|
7329 | All CLI commands have been improved to support non-ASCII parameters such as |
---|
7330 | filenames and aliases on all supported Operating Systems except Windows as of |
---|
7331 | now. |
---|
7332 | ] |
---|
7333 | [stringutils.py: Unicode helper functions + associated tests |
---|
7334 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100520004105 |
---|
7335 | Ignore-this: 7a73fc31de2fd39d437d6abd278bfa9a |
---|
7336 | |
---|
7337 | This file contains a bunch of helper functions which converts |
---|
7338 | unicode string from and to argv, filenames and stdout. |
---|
7339 | ] |
---|
7340 | [Add dependency on Michael Foord's mock library |
---|
7341 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100519233325 |
---|
7342 | Ignore-this: 9bb01bf1e4780f6b98ed394c3b772a80 |
---|
7343 | ] |
---|
7344 | [Resolve merge conflict for sftpd.py |
---|
7345 | david-sarah@jacaranda.org**20100603182537 |
---|
7346 | Ignore-this: ba8b543e51312ac949798eb8f5bd9d9c |
---|
7347 | ] |
---|
7348 | [SFTP: possible fix for metadata times being shown as the epoch. |
---|
7349 | david-sarah@jacaranda.org**20100602234514 |
---|
7350 | Ignore-this: bdd7dfccf34eff818ff88aa4f3d28790 |
---|
7351 | ] |
---|
7352 | [SFTP: further improvements to test coverage. |
---|
7353 | david-sarah@jacaranda.org**20100602234422 |
---|
7354 | Ignore-this: 87eeee567e8d7562659442ea491e187c |
---|
7355 | ] |
---|
7356 | [SFTP: improve test coverage. Also make creating a directory fail when permissions are read-only (rather than ignoring the permissions). |
---|
7357 | david-sarah@jacaranda.org**20100602041934 |
---|
7358 | Ignore-this: a5e9d9081677bc7f3ddb18ca7a1f531f |
---|
7359 | ] |
---|
7360 | [dirnode.py: fix a bug in the no-write change for Adder, and improve test coverage. Add a 'metadata' argument to create_subdirectory, with documentation. Also update some comments in test_dirnode.py made stale by the ctime/mtime change. |
---|
7361 | david-sarah@jacaranda.org**20100602032641 |
---|
7362 | Ignore-this: 48817b54cd63f5422cb88214c053b03b |
---|
7363 | ] |
---|
7364 | [SFTP: fix a bug that caused the temporary files underlying EncryptedTemporaryFiles not to be closed. |
---|
7365 | david-sarah@jacaranda.org**20100601055310 |
---|
7366 | Ignore-this: 44fee4cfe222b2b1690f4c5e75083a52 |
---|
7367 | ] |
---|
7368 | [SFTP: changes for #1063 ('no-write' field) including comment:1 (clearing owner write permission diminishes to a read cap). Includes documentation changes, but not tests for the new behaviour. |
---|
7369 | david-sarah@jacaranda.org**20100601051139 |
---|
7370 | Ignore-this: eff7c08bd47fd52bfe2b844dabf02558 |
---|
7371 | ] |
---|
7372 | [SFTP: the same bug as in _sync_heisenfiles also occurred in two other places. |
---|
7373 | david-sarah@jacaranda.org**20100530060127 |
---|
7374 | Ignore-this: 8d137658fc6e4596fa42697476c39aa3 |
---|
7375 | ] |
---|
7376 | [SFTP: another try at fixing the _sync_heisenfiles bug. |
---|
7377 | david-sarah@jacaranda.org**20100530055254 |
---|
7378 | Ignore-this: c15f76f32a60083a6b7de6ca0e917934 |
---|
7379 | ] |
---|
7380 | [SFTP: fix silly bug in _sync_heisenfiles ('f is not ignore' vs 'not (f is ignore)'). |
---|
7381 | david-sarah@jacaranda.org**20100530053807 |
---|
7382 | Ignore-this: 71c4bc62613bf8fef835886d8eb61c27 |
---|
7383 | ] |
---|
7384 | [SFTP: log when a sync completes. |
---|
7385 | david-sarah@jacaranda.org**20100530051840 |
---|
7386 | Ignore-this: d99765663ceb673c8a693dfcf88c25ea |
---|
7387 | ] |
---|
7388 | [SFTP: fix bug in previous logging patch. |
---|
7389 | david-sarah@jacaranda.org**20100530050000 |
---|
7390 | Ignore-this: 613e4c115f03fe2d04c621b510340817 |
---|
7391 | ] |
---|
7392 | [SFTP: more logging to track down OpenOffice hang. |
---|
7393 | david-sarah@jacaranda.org**20100530040809 |
---|
7394 | Ignore-this: 6c11f2d1eac9f62e2d0f04f006476a03 |
---|
7395 | ] |
---|
7396 | [SFTP: avoid blocking close on a heisenfile that has been abandoned or never changed. Also, improve the logging to help track down a case where OpenOffice hangs on opening a file with FXF_READ|FXF_WRITE. |
---|
7397 | david-sarah@jacaranda.org**20100530025544 |
---|
7398 | Ignore-this: 9919dddd446fff64de4031ad51490d1c |
---|
7399 | ] |
---|
7400 | [Move suppression of DeprecationWarning about BaseException.message from sftpd.py to main __init__.py. Also, remove the global suppression of the 'integer argument expected, got float' warning, which turned out to be a bug. |
---|
7401 | david-sarah@jacaranda.org**20100529050537 |
---|
7402 | Ignore-this: 87648afa0dec0d2e73614007de102a16 |
---|
7403 | ] |
---|
7404 | [SFTP: cater to clients that assume a file is created as soon as they have made an open request; also, fix some race conditions associated with closing a file at about the same time as renaming or removing it. |
---|
7405 | david-sarah@jacaranda.org**20100529045253 |
---|
7406 | Ignore-this: 2404076b2154ff2659e2b10e0b9e813c |
---|
7407 | ] |
---|
7408 | [SFTP: 'sync' any open files at a direntry before opening any new file at that direntry. This works around the sshfs misbehaviour of returning success to clients immediately on close. |
---|
7409 | david-sarah@jacaranda.org**20100525230257 |
---|
7410 | Ignore-this: 63245d6d864f8f591c86170864d7c57f |
---|
7411 | ] |
---|
7412 | [SFTP: handle removing a file while it is open. Also some simplifications of the logout handling. |
---|
7413 | david-sarah@jacaranda.org**20100525184210 |
---|
7414 | Ignore-this: 660ee80be6ecab783c60452a9da896de |
---|
7415 | ] |
---|
7416 | [SFTP: a posix-rename response should actually return an FXP_STATUS reply, not an FXP_EXTENDED_REPLY as Twisted Conch assumes. Work around this by raising an SFTPError with code FX_OK. |
---|
7417 | david-sarah@jacaranda.org**20100525033323 |
---|
7418 | Ignore-this: fe2914d3ef7f5194bbeaf3f2dda2ad7d |
---|
7419 | ] |
---|
7420 | [SFTP: fix problem with posix-rename code returning a Deferred for the renamed filenode, not for the result of the request (an empty string). |
---|
7421 | david-sarah@jacaranda.org**20100525020209 |
---|
7422 | Ignore-this: 69f7491df2a8f7ea92d999a6d9f0581d |
---|
7423 | ] |
---|
7424 | [SFTP: fix time handling to make sure floats are not passed into twisted.conch, and to print times in the future less ambiguously in directory listings. |
---|
7425 | david-sarah@jacaranda.org**20100524230412 |
---|
7426 | Ignore-this: eb1a3fb72492fa2fb19667b6e4300440 |
---|
7427 | ] |
---|
7428 | [SFTP: name of the POSIX rename extension should be 'posix-rename@openssh.com', not 'extposix-rename@openssh.com'. |
---|
7429 | david-sarah@jacaranda.org**20100524021156 |
---|
7430 | Ignore-this: f90eb1ff9560176635386ee797a3fdc7 |
---|
7431 | ] |
---|
7432 | [SFTP: avoid race condition where .write could be called on an OverwriteableFileConsumer after it had been closed. |
---|
7433 | david-sarah@jacaranda.org**20100523233830 |
---|
7434 | Ignore-this: 55d381064a15bd64381163341df4d09f |
---|
7435 | ] |
---|
7436 | [SFTP: log tracebacks for RAISEd exceptions. |
---|
7437 | david-sarah@jacaranda.org**20100523221535 |
---|
7438 | Ignore-this: c76a7852df099b358642f0631237cc89 |
---|
7439 | ] |
---|
7440 | [SFTP: more logging to investigate behaviour of getAttrs(path). |
---|
7441 | david-sarah@jacaranda.org**20100523204236 |
---|
7442 | Ignore-this: e58fd35dc9015316e16a9f49f19bb469 |
---|
7443 | ] |
---|
7444 | [SFTP: fix pyflakes warnings; drop 'noisy' versions of eventually_callback and eventually_errback; robustify conversion of exception messages to UTF-8. |
---|
7445 | david-sarah@jacaranda.org**20100523140905 |
---|
7446 | Ignore-this: 420196fc58646b05bbc9c3732b6eb314 |
---|
7447 | ] |
---|
7448 | [SFTP: fixes and test cases for renaming of open files. |
---|
7449 | david-sarah@jacaranda.org**20100523032549 |
---|
7450 | Ignore-this: 32e0726be0fc89335f3035157e202c68 |
---|
7451 | ] |
---|
7452 | [SFTP: Increase test_sftp timeout to cater for francois' ARM buildslave. |
---|
7453 | david-sarah@jacaranda.org**20100522191639 |
---|
7454 | Ignore-this: a5acf9660d304677048ab4dd72908ad8 |
---|
7455 | ] |
---|
7456 | [SFTP: Fix error in support for getAttrs on an open file, to index open files by directory entry rather than path. Extend that support to renaming open files. Also, implement the extposix-rename@openssh.org extension, and some other minor refactoring. |
---|
7457 | david-sarah@jacaranda.org**20100522035836 |
---|
7458 | Ignore-this: 8ef93a828e927cce2c23b805250b81a4 |
---|
7459 | ] |
---|
7460 | [SFTP tests: fix test_openDirectory_and_attrs that was failing in timezones west of UTC. |
---|
7461 | david-sarah@jacaranda.org**20100520181027 |
---|
7462 | Ignore-this: 9beaf602beef437c11c7e97f54ce2599 |
---|
7463 | ] |
---|
7464 | [SFTP: allow getAttrs to succeed on a file that has been opened for creation but not yet uploaded or linked (part of #1050). |
---|
7465 | david-sarah@jacaranda.org**20100520035613 |
---|
7466 | Ignore-this: 2f59107d60d5476edac19361ccf6cf94 |
---|
7467 | ] |
---|
7468 | [SFTP: improve logging so that results of requests are (usually) logged. |
---|
7469 | david-sarah@jacaranda.org**20100520003652 |
---|
7470 | Ignore-this: 3f59eeee374a3eba71db9be31d5a95 |
---|
7471 | ] |
---|
7472 | [SFTP: add tests for more combinations of open flags. |
---|
7473 | david-sarah@jacaranda.org**20100519053933 |
---|
7474 | Ignore-this: b97ee351b1e8ecfecabac70698060665 |
---|
7475 | ] |
---|
7476 | [SFTP: allow FXF_WRITE | FXF_TRUNC (#1050). |
---|
7477 | david-sarah@jacaranda.org**20100519043240 |
---|
7478 | Ignore-this: bd70009f11d07ac6e9fd0d1e3fa87a9b |
---|
7479 | ] |
---|
7480 | [SFTP: remove another case where we were logging data. |
---|
7481 | david-sarah@jacaranda.org**20100519012713 |
---|
7482 | Ignore-this: 83115daf3a90278fed0e3fc267607584 |
---|
7483 | ] |
---|
7484 | [SFTP: avoid logging all data passed to callbacks. |
---|
7485 | david-sarah@jacaranda.org**20100519000651 |
---|
7486 | Ignore-this: ade6d69a473ada50acef6389fc7fdf69 |
---|
7487 | ] |
---|
7488 | [SFTP: fixes related to reporting of permissions (needed for sshfs). |
---|
7489 | david-sarah@jacaranda.org**20100518054521 |
---|
7490 | Ignore-this: c51f8a5d0dc76b80d33ffef9b0541325 |
---|
7491 | ] |
---|
7492 | [SFTP: change error code returned for ExistingChildError to FX_FAILURE (fixes gvfs with some picky programs such as gedit). |
---|
7493 | david-sarah@jacaranda.org**20100518004205 |
---|
7494 | Ignore-this: c194c2c9aaf3edba7af84b7413cec375 |
---|
7495 | ] |
---|
7496 | [SFTP: fixed bugs that caused hangs during write (#1037). |
---|
7497 | david-sarah@jacaranda.org**20100517044228 |
---|
7498 | Ignore-this: b8b95e82c4057367388a1e6baada993b |
---|
7499 | ] |
---|
7500 | [SFTP: work around a probable bug in twisted.conch.ssh.session:loseConnection(). Also some minor error handling cleanups. |
---|
7501 | david-sarah@jacaranda.org**20100517012606 |
---|
7502 | Ignore-this: 5d3da7c4219cb0c14547e7fd70c74204 |
---|
7503 | ] |
---|
7504 | [SFTP: Support statvfs extensions, avoid logging actual data, and decline shell sessions politely. |
---|
7505 | david-sarah@jacaranda.org**20100516154347 |
---|
7506 | Ignore-this: 9d05d23ba77693c03a61accd348ccbe5 |
---|
7507 | ] |
---|
7508 | [SFTP: fix error in SFTPUserHandler arguments introduced by execCommand patch. |
---|
7509 | david-sarah@jacaranda.org**20100516014045 |
---|
7510 | Ignore-this: f5ee494dc6ad6aa536cc8144bd2e3d19 |
---|
7511 | ] |
---|
7512 | [SFTP: implement execCommand to interoperate with clients that issue a 'df -P -k /' command. Also eliminate use of Zope adaptation. |
---|
7513 | david-sarah@jacaranda.org**20100516012754 |
---|
7514 | Ignore-this: 2d0ed28b759f67f83875b1eaf5778992 |
---|
7515 | ] |
---|
7516 | [sftpd.py: 'log.OPERATIONAL' should be just 'OPERATIONAL'. |
---|
7517 | david-sarah@jacaranda.org**20100515155533 |
---|
7518 | Ignore-this: f2347cb3301bbccc086356f6edc685 |
---|
7519 | ] |
---|
7520 | [Attempt to fix #1040 by making SFTPUser implement ISession. |
---|
7521 | david-sarah@jacaranda.org**20100515005719 |
---|
7522 | Ignore-this: b3baaf088ba567e861e61e347195dfc4 |
---|
7523 | ] |
---|
7524 | [Eliminate Windows newlines from sftpd.py. |
---|
7525 | david-sarah@jacaranda.org**20100515005656 |
---|
7526 | Ignore-this: cd54fd25beb957887514ae76e08c277 |
---|
7527 | ] |
---|
7528 | [Update SFTP implementation and tests: fix #1038 and switch to foolscap logging; also some code reorganization. |
---|
7529 | david-sarah@jacaranda.org**20100514043113 |
---|
7530 | Ignore-this: 262f76d953dcd4317210789f2b2bf5da |
---|
7531 | ] |
---|
7532 | [Tests for new SFTP implementation |
---|
7533 | david-sarah@jacaranda.org**20100512060552 |
---|
7534 | Ignore-this: 20308d4a59b3ebc868aad55ae0a7a981 |
---|
7535 | ] |
---|
7536 | [New SFTP implementation: mutable files, read/write support, streaming download, Unicode filenames, and more |
---|
7537 | david-sarah@jacaranda.org**20100512055407 |
---|
7538 | Ignore-this: 906f51c48d974ba9cf360c27845c55eb |
---|
7539 | ] |
---|
7540 | [setup: adjust make clean target to ignore our bundled build tools |
---|
7541 | zooko@zooko.com**20100604051250 |
---|
7542 | Ignore-this: d24d2a3b849000790cfbfab69237454e |
---|
7543 | ] |
---|
7544 | [setup: bundle a copy of setuptools_trial as an unzipped egg in the base dir of the Tahoe-LAFS source tree |
---|
7545 | zooko@zooko.com**20100604044648 |
---|
7546 | Ignore-this: a4736e9812b4dab2d5a2bc4bfc5c3b28 |
---|
7547 | This is to work-around this Distribute issue: |
---|
7548 | http://bitbucket.org/tarek/distribute/issue/55/revision-control-plugin-automatically-installed-as-a-build-dependency-is-not-present-when-another-build-dependency-is-being |
---|
7549 | ] |
---|
7550 | [setup: bundle a copy of darcsver in unzipped egg form in the root of the Tahoe-LAFS source tree |
---|
7551 | zooko@zooko.com**20100604044146 |
---|
7552 | Ignore-this: a51a52e82dd3a39225657ffa27decae2 |
---|
7553 | This is to work-around this Distribute issue: |
---|
7554 | http://bitbucket.org/tarek/distribute/issue/55/revision-control-plugin-automatically-installed-as-a-build-dependency-is-not-present-when-another-build-dependency-is-being |
---|
7555 | ] |
---|
7556 | [quickstart.html: warn against installing Python at a path containing spaces. |
---|
7557 | david-sarah@jacaranda.org**20100604032413 |
---|
7558 | Ignore-this: c7118332573abd7762d9a897e650bc6a |
---|
7559 | ] |
---|
7560 | [setup: undo the previous patch to quote the executable in scripts |
---|
7561 | zooko@zooko.com**20100604025204 |
---|
7562 | Ignore-this: beda3b951c49d1111478618b8cabe005 |
---|
7563 | The problem isn't in the script, it is in the cli.exe script that is built by setuptools. This might be related to |
---|
7564 | http://bugs.python.org/issue6792 |
---|
7565 | and |
---|
7566 | http://bugs.python.org/setuptools/issue2 |
---|
7567 | Or it might be a separate issue involving the launcher.c code e.g. http://tahoe-lafs.org/trac/zetuptoolz/browser/launcher.c?rev=576#L210 and its handling of the interpreter name. |
---|
7568 | ] |
---|
7569 | [setup: put quotes around the path to executable in case it has spaces in it, when building a tahoe.exe for win32 |
---|
7570 | zooko@zooko.com**20100604020836 |
---|
7571 | Ignore-this: 478684843169c94a9c14726fedeeed7d |
---|
7572 | ] |
---|
7573 | [Add must_exist, must_be_directory, and must_be_file arguments to DirectoryNode.delete. This will be used to fixes a minor condition in the SFTP frontend. |
---|
7574 | david-sarah@jacaranda.org**20100527194529 |
---|
7575 | Ignore-this: 6d8114cef4450c52c57639f82852716f |
---|
7576 | ] |
---|
7577 | [Fix test failures in test_web caused by changes to web page titles in #1062. Also, change a 'target' field to '_blank' instead of 'blank' in welcome.xhtml. |
---|
7578 | david-sarah@jacaranda.org**20100603232105 |
---|
7579 | Ignore-this: 6e2cc63f42b07e2a3b2d1a857abc50a6 |
---|
7580 | ] |
---|
7581 | [misc/show-tool-versions.py: Display additional Python interpreter encoding informations (stdout, stdin and filesystem) |
---|
7582 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100521094313 |
---|
7583 | Ignore-this: 3ae9b0b07fd1d53fb632ef169f7c5d26 |
---|
7584 | ] |
---|
7585 | [dirnode.py: Fix bug that caused 'tahoe' fields, 'ctime' and 'mtime' not to be updated when new metadata is present. |
---|
7586 | david-sarah@jacaranda.org**20100602014644 |
---|
7587 | Ignore-this: 5bac95aa897b68f2785d481e49b6a66 |
---|
7588 | ] |
---|
7589 | [dirnode.py: Fix #1034 (MetadataSetter does not enforce restriction on setting 'tahoe' subkeys), and expose the metadata updater for use by SFTP. Also, support diminishing a child cap to read-only if 'no-write' is set in the metadata. |
---|
7590 | david-sarah@jacaranda.org**20100601045428 |
---|
7591 | Ignore-this: 14f26e17e58db97fad0dcfd350b38e95 |
---|
7592 | ] |
---|
7593 | [Change doc comments in interfaces.py to take into account unknown nodes. |
---|
7594 | david-sarah@jacaranda.org**20100528171922 |
---|
7595 | Ignore-this: d2fde6890b3bca9c7275775f64fbff56 |
---|
7596 | ] |
---|
7597 | [Trivial whitespace changes. |
---|
7598 | david-sarah@jacaranda.org**20100527194114 |
---|
7599 | Ignore-this: 98d611bc54ee20b01a5f6b334ff61b2d |
---|
7600 | ] |
---|
7601 | [Suppress 'integer argument expected, got float' DeprecationWarning everywhere |
---|
7602 | david-sarah@jacaranda.org**20100523221157 |
---|
7603 | Ignore-this: 80efd7e27798f5d2ad66c7a53e7048e5 |
---|
7604 | ] |
---|
7605 | [Change shouldFail to avoid Unicode errors when converting Failure to str |
---|
7606 | david-sarah@jacaranda.org**20100512060754 |
---|
7607 | Ignore-this: 86ed419d332d9c33090aae2cde1dc5df |
---|
7608 | ] |
---|
7609 | [SFTP: relax pyasn1 version dependency to >= 0.0.8a. |
---|
7610 | david-sarah@jacaranda.org**20100520181437 |
---|
7611 | Ignore-this: 2c7b3dee7b7e14ba121d3118193a386a |
---|
7612 | ] |
---|
7613 | [SFTP: add pyasn1 as dependency, needed if we are using Twisted >= 9.0.0. |
---|
7614 | david-sarah@jacaranda.org**20100516193710 |
---|
7615 | Ignore-this: 76fd92e8a950bb1983a90a09e89c54d3 |
---|
7616 | ] |
---|
7617 | [allmydata.org -> tahoe-lafs.org in __init__.py |
---|
7618 | david-sarah@jacaranda.org**20100603063530 |
---|
7619 | Ignore-this: f7d82331d5b4a3c4c0938023409335af |
---|
7620 | ] |
---|
7621 | [small change to CREDITS |
---|
7622 | david-sarah@jacaranda.org**20100603062421 |
---|
7623 | Ignore-this: 2909cdbedc19da5573dec810fc23243 |
---|
7624 | ] |
---|
7625 | [Resolve conflict in patch to change imports to absolute. |
---|
7626 | david-sarah@jacaranda.org**20100603054608 |
---|
7627 | Ignore-this: 15aa1caa88e688ffa6dc53bed7dcca7d |
---|
7628 | ] |
---|
7629 | [Minor documentation tweaks. |
---|
7630 | david-sarah@jacaranda.org**20100603054458 |
---|
7631 | Ignore-this: e30ae407b0039dfa5b341d8f88e7f959 |
---|
7632 | ] |
---|
7633 | [title_rename_xhtml.dpatch.txt |
---|
7634 | freestorm77@gmail.com**20100529172542 |
---|
7635 | Ignore-this: d2846afcc9ea72ac443a62ecc23d121b |
---|
7636 | |
---|
7637 | - Renamed xhtml Title from "Allmydata - Tahoe" to "Tahoe-LAFS" |
---|
7638 | - Renamed Tahoe to Tahoe-LAFS in page content |
---|
7639 | - Changed Tahoe-LAFS home page link to http://tahoe-lafs.org (added target="blank") |
---|
7640 | - Deleted commented css script in info.xhtml |
---|
7641 | |
---|
7642 | |
---|
7643 | ] |
---|
7644 | [tests: refactor test_web.py to have less duplication of literal caps-from-the-future |
---|
7645 | zooko@zooko.com**20100519055146 |
---|
7646 | Ignore-this: 49e5412e6cc4566ca67f069ffd850af6 |
---|
7647 | This is a prelude to a patch which will add tests of caps from the future which have non-ascii chars in them. |
---|
7648 | ] |
---|
7649 | [doc_reformat_stats.txt |
---|
7650 | freestorm77@gmail.com**20100424114615 |
---|
7651 | Ignore-this: af315db5f7e3a17219ff8fb39bcfcd60 |
---|
7652 | |
---|
7653 | |
---|
7654 | - Added heading format begining and ending by "==" |
---|
7655 | - Added Index |
---|
7656 | - Added Title |
---|
7657 | |
---|
7658 | Note: No change are made in paragraphs content |
---|
7659 | |
---|
7660 | |
---|
7661 | **END OF DESCRIPTION*** |
---|
7662 | |
---|
7663 | Place the long patch description above the ***END OF DESCRIPTION*** marker. |
---|
7664 | The first line of this file will be the patch name. |
---|
7665 | |
---|
7666 | |
---|
7667 | This patch contains the following changes: |
---|
7668 | |
---|
7669 | M ./docs/stats.txt -2 +2 |
---|
7670 | ] |
---|
7671 | [doc_reformat_performance.txt |
---|
7672 | freestorm77@gmail.com**20100424114444 |
---|
7673 | Ignore-this: 55295ff5cd8a5b67034eb661a5b0699d |
---|
7674 | |
---|
7675 | - Added heading format begining and ending by "==" |
---|
7676 | - Added Index |
---|
7677 | - Added Title |
---|
7678 | |
---|
7679 | Note: No change are made in paragraphs content |
---|
7680 | |
---|
7681 | |
---|
7682 | ] |
---|
7683 | [doc_refomat_logging.txt |
---|
7684 | freestorm77@gmail.com**20100424114316 |
---|
7685 | Ignore-this: 593f0f9914516bf1924dfa6eee74e35f |
---|
7686 | |
---|
7687 | - Added heading format begining and ending by "==" |
---|
7688 | - Added Index |
---|
7689 | - Added Title |
---|
7690 | |
---|
7691 | Note: No change are made in paragraphs content |
---|
7692 | |
---|
7693 | ] |
---|
7694 | [doc_reformat_known_issues.txt |
---|
7695 | freestorm77@gmail.com**20100424114118 |
---|
7696 | Ignore-this: 9577c3965d77b7ac18698988cfa06049 |
---|
7697 | |
---|
7698 | - Added heading format begining and ending by "==" |
---|
7699 | - Added Index |
---|
7700 | - Added Title |
---|
7701 | |
---|
7702 | Note: No change are made in paragraphs content |
---|
7703 | |
---|
7704 | |
---|
7705 | ] |
---|
7706 | [doc_reformat_helper.txt |
---|
7707 | freestorm77@gmail.com**20100424120649 |
---|
7708 | Ignore-this: de2080d6152ae813b20514b9908e37fb |
---|
7709 | |
---|
7710 | |
---|
7711 | - Added heading format begining and ending by "==" |
---|
7712 | - Added Index |
---|
7713 | - Added Title |
---|
7714 | |
---|
7715 | Note: No change are made in paragraphs content |
---|
7716 | |
---|
7717 | ] |
---|
7718 | [doc_reformat_garbage-collection.txt |
---|
7719 | freestorm77@gmail.com**20100424120830 |
---|
7720 | Ignore-this: aad3e4c99670871b66467062483c977d |
---|
7721 | |
---|
7722 | |
---|
7723 | - Added heading format begining and ending by "==" |
---|
7724 | - Added Index |
---|
7725 | - Added Title |
---|
7726 | |
---|
7727 | Note: No change are made in paragraphs content |
---|
7728 | |
---|
7729 | ] |
---|
7730 | [doc_reformat_FTP-and-SFTP.txt |
---|
7731 | freestorm77@gmail.com**20100424121334 |
---|
7732 | Ignore-this: 3736b3d8f9a542a3521fbb566d44c7cf |
---|
7733 | |
---|
7734 | |
---|
7735 | - Added heading format begining and ending by "==" |
---|
7736 | - Added Index |
---|
7737 | - Added Title |
---|
7738 | |
---|
7739 | Note: No change are made in paragraphs content |
---|
7740 | |
---|
7741 | ] |
---|
7742 | [doc_reformat_debian.txt |
---|
7743 | freestorm77@gmail.com**20100424120537 |
---|
7744 | Ignore-this: 45fe4355bb869e55e683405070f47eff |
---|
7745 | |
---|
7746 | |
---|
7747 | - Added heading format begining and ending by "==" |
---|
7748 | - Added Index |
---|
7749 | - Added Title |
---|
7750 | |
---|
7751 | Note: No change are made in paragraphs content |
---|
7752 | |
---|
7753 | ] |
---|
7754 | [doc_reformat_configuration.txt |
---|
7755 | freestorm77@gmail.com**20100424104903 |
---|
7756 | Ignore-this: 4fbabc51b8122fec69ce5ad1672e79f2 |
---|
7757 | |
---|
7758 | |
---|
7759 | - Added heading format begining and ending by "==" |
---|
7760 | - Added Index |
---|
7761 | - Added Title |
---|
7762 | |
---|
7763 | Note: No change are made in paragraphs content |
---|
7764 | |
---|
7765 | ] |
---|
7766 | [doc_reformat_CLI.txt |
---|
7767 | freestorm77@gmail.com**20100424121512 |
---|
7768 | Ignore-this: 2d3a59326810adcb20ea232cea405645 |
---|
7769 | |
---|
7770 | - Added heading format begining and ending by "==" |
---|
7771 | - Added Index |
---|
7772 | - Added Title |
---|
7773 | |
---|
7774 | Note: No change are made in paragraphs content |
---|
7775 | |
---|
7776 | ] |
---|
7777 | [doc_reformat_backupdb.txt |
---|
7778 | freestorm77@gmail.com**20100424120416 |
---|
7779 | Ignore-this: fed696530e9d2215b6f5058acbedc3ab |
---|
7780 | |
---|
7781 | |
---|
7782 | - Added heading format begining and ending by "==" |
---|
7783 | - Added Index |
---|
7784 | - Added Title |
---|
7785 | |
---|
7786 | Note: No change are made in paragraphs content |
---|
7787 | |
---|
7788 | ] |
---|
7789 | [doc_reformat_architecture.txt |
---|
7790 | freestorm77@gmail.com**20100424120133 |
---|
7791 | Ignore-this: 6e2cab4635080369f2b8cadf7b2f58e |
---|
7792 | |
---|
7793 | |
---|
7794 | - Added heading format begining and ending by "==" |
---|
7795 | - Added Index |
---|
7796 | - Added Title |
---|
7797 | |
---|
7798 | Note: No change are made in paragraphs content |
---|
7799 | |
---|
7800 | |
---|
7801 | ] |
---|
7802 | [Correct harmless indentation errors found by pylint |
---|
7803 | david-sarah@jacaranda.org**20100226052151 |
---|
7804 | Ignore-this: 41335bce830700b18b80b6e00b45aef5 |
---|
7805 | ] |
---|
7806 | [Change relative imports to absolute |
---|
7807 | david-sarah@jacaranda.org**20100226071433 |
---|
7808 | Ignore-this: 32e6ce1a86e2ffaaba1a37d9a1a5de0e |
---|
7809 | ] |
---|
7810 | [Document reason for the trialcoverage version requirement being 0.3.3. |
---|
7811 | david-sarah@jacaranda.org**20100525004444 |
---|
7812 | Ignore-this: 2f9f1df6882838b000c063068f258aec |
---|
7813 | ] |
---|
7814 | [Downgrade version requirement for trialcoverage to 0.3.3 (from 0.3.10), to avoid needing to compile coveragepy on Windows. |
---|
7815 | david-sarah@jacaranda.org**20100524233707 |
---|
7816 | Ignore-this: 9c397a374c8b8017e2244b8a686432a8 |
---|
7817 | ] |
---|
7818 | [Suppress deprecation warning for twisted.web.error.NoResource when using Twisted >= 9.0.0. |
---|
7819 | david-sarah@jacaranda.org**20100516205625 |
---|
7820 | Ignore-this: 2361a3023cd3db86bde5e1af759ed01 |
---|
7821 | ] |
---|
7822 | [docs: CREDITS for Jeremy Visser |
---|
7823 | zooko@zooko.com**20100524081829 |
---|
7824 | Ignore-this: d7c1465fd8d4e25b8d46d38a1793465b |
---|
7825 | ] |
---|
7826 | [test: show stdout and stderr in case of non-zero exit code from "tahoe" command |
---|
7827 | zooko@zooko.com**20100524073348 |
---|
7828 | Ignore-this: 695e81cd6683f4520229d108846cd551 |
---|
7829 | ] |
---|
7830 | [setup: upgrade bundled zetuptoolz to zetuptoolz-0.6c15dev and make it unpacked and directly loaded by setup.py |
---|
7831 | zooko@zooko.com**20100523205228 |
---|
7832 | Ignore-this: 24fb32aaee3904115a93d1762f132c7 |
---|
7833 | Also fix the relevant "make clean" target behavior. |
---|
7834 | ] |
---|
7835 | [setup: remove bundled zipfile egg of setuptools |
---|
7836 | zooko@zooko.com**20100523205120 |
---|
7837 | Ignore-this: c68b5f2635bb93d1c1fa7b613a026f9e |
---|
7838 | We're about to replace it with bundled unpacked source code of setuptools, which is much nicer for debugging and evolving under revision control. |
---|
7839 | ] |
---|
7840 | [setup: remove bundled copy of setuptools_trial-0.5.2.tar |
---|
7841 | zooko@zooko.com**20100522221539 |
---|
7842 | Ignore-this: 140f90eb8fb751a509029c4b24afe647 |
---|
7843 | Hopefully it will get installed automatically as needed and we won't bundle it anymore. |
---|
7844 | ] |
---|
7845 | [setup: remove bundled setuptools_darcs-1.2.8.tar |
---|
7846 | zooko@zooko.com**20100522015333 |
---|
7847 | Ignore-this: 378b1964b513ae7fe22bae2d3478285d |
---|
7848 | This version of setuptools_darcs had a bug when used on Windows which has been fixed in setuptools_darcs-1.2.9. Hopefully we will not need to bundle a copy of setuptools_darcs-1.2.9 in with Tahoe-LAFS and can instead rely on it to be downloaded from PyPI or bundled in the "tahoe deps" separate tarball. |
---|
7849 | ] |
---|
7850 | [tests: fix pyflakes warnings in bench_dirnode.py |
---|
7851 | zooko@zooko.com**20100521202511 |
---|
7852 | Ignore-this: f23d55b4ed05e52865032c65a15753c4 |
---|
7853 | ] |
---|
7854 | [setup: if the string '--reporter=bwverbose-coverage' appears on sys.argv then you need trialcoverage |
---|
7855 | zooko@zooko.com**20100521122226 |
---|
7856 | Ignore-this: e760c45dcfb5a43c1dc1e8a27346bdc2 |
---|
7857 | ] |
---|
7858 | [tests: don't let bench_dirnode.py do stuff and have side-effects at import time (unless __name__ == '__main__') |
---|
7859 | zooko@zooko.com**20100521122052 |
---|
7860 | Ignore-this: 96144a412250d9bbb5fccbf83b8753b8 |
---|
7861 | ] |
---|
7862 | [tests: increase timeout to give François's ARM buildslave a chance to complete the tests |
---|
7863 | zooko@zooko.com**20100520134526 |
---|
7864 | Ignore-this: 3dd399fdc8b91149c82b52f955b50833 |
---|
7865 | ] |
---|
7866 | [run_trial.darcspath |
---|
7867 | freestorm77@gmail.com**20100510232829 |
---|
7868 | Ignore-this: 5ebb4df74e9ea8a4bdb22b65373d1ff2 |
---|
7869 | ] |
---|
7870 | [docs: line-wrap README.txt |
---|
7871 | zooko@zooko.com**20100518174240 |
---|
7872 | Ignore-this: 670a02d360df7de51ebdcf4fae752577 |
---|
7873 | ] |
---|
7874 | [Hush pyflakes warnings |
---|
7875 | Kevan Carstensen <kevan@isnotajoke.com>**20100515184344 |
---|
7876 | Ignore-this: fd602c3bba115057770715c36a87b400 |
---|
7877 | ] |
---|
7878 | [setup: new improved misc/show-tool-versions.py |
---|
7879 | zooko@zooko.com**20100516050122 |
---|
7880 | Ignore-this: ce9b1de1b35b07d733e6cf823b66335a |
---|
7881 | ] |
---|
7882 | [Improve code coverage of the Tahoe2PeerSelector tests. |
---|
7883 | Kevan Carstensen <kevan@isnotajoke.com>**20100515032913 |
---|
7884 | Ignore-this: 793151b63ffa65fdae6915db22d9924a |
---|
7885 | ] |
---|
7886 | [Remove a comment that no longer makes sense. |
---|
7887 | Kevan Carstensen <kevan@isnotajoke.com>**20100514203516 |
---|
7888 | Ignore-this: 956983c7e7c7e4477215494dfce8f058 |
---|
7889 | ] |
---|
7890 | [docs: update docs/architecture.txt to more fully and correctly explain the upload procedure |
---|
7891 | zooko@zooko.com**20100514043458 |
---|
7892 | Ignore-this: 538b6ea256a49fed837500342092efa3 |
---|
7893 | ] |
---|
7894 | [Fix up the behavior of #778, per reviewers' comments |
---|
7895 | Kevan Carstensen <kevan@isnotajoke.com>**20100514004917 |
---|
7896 | Ignore-this: 9c20b60716125278b5456e8feb396bff |
---|
7897 | |
---|
7898 | - Make some important utility functions clearer and more thoroughly |
---|
7899 | documented. |
---|
7900 | - Assert in upload.servers_of_happiness that the buckets attributes |
---|
7901 | of PeerTrackers passed to it are mutually disjoint. |
---|
7902 | - Get rid of some silly non-Pythonisms that I didn't see when I first |
---|
7903 | wrote these patches. |
---|
7904 | - Make sure that should_add_server returns true when queried about a |
---|
7905 | shnum that it doesn't know about yet. |
---|
7906 | - Change Tahoe2PeerSelector.preexisting_shares to map a shareid to a set |
---|
7907 | of peerids, alter dependencies to deal with that. |
---|
7908 | - Remove upload.should_add_servers, because it is no longer necessary |
---|
7909 | - Move upload.shares_of_happiness and upload.shares_by_server to a utility |
---|
7910 | file. |
---|
7911 | - Change some points in Tahoe2PeerSelector. |
---|
7912 | - Compute servers_of_happiness using a bipartite matching algorithm that |
---|
7913 | we know is optimal instead of an ad-hoc greedy algorithm that isn't. |
---|
7914 | - Change servers_of_happiness to just take a sharemap as an argument, |
---|
7915 | change its callers to merge existing_shares and used_peers before |
---|
7916 | calling it. |
---|
7917 | - Change an error message in the encoder to be more appropriate for |
---|
7918 | servers of happiness. |
---|
7919 | - Clarify the wording of an error message in immutable/upload.py |
---|
7920 | - Refactor a happiness failure message to happinessutil.py, and make |
---|
7921 | immutable/upload.py and immutable/encode.py use it. |
---|
7922 | - Move the word "only" as far to the right as possible in failure |
---|
7923 | messages. |
---|
7924 | - Use a better definition of progress during peer selection. |
---|
7925 | - Do read-only peer share detection queries in parallel, not sequentially. |
---|
7926 | - Clean up logging semantics; print the query statistics whenever an |
---|
7927 | upload is unsuccessful, not just in one case. |
---|
7928 | |
---|
7929 | ] |
---|
7930 | [Alter the error message when an upload fails, per some comments in #778. |
---|
7931 | Kevan Carstensen <kevan@isnotajoke.com>**20091230210344 |
---|
7932 | Ignore-this: ba97422b2f9737c46abeb828727beb1 |
---|
7933 | |
---|
7934 | When I first implemented #778, I just altered the error messages to refer to |
---|
7935 | servers where they referred to shares. The resulting error messages weren't |
---|
7936 | very good. These are a bit better. |
---|
7937 | ] |
---|
7938 | [Change "UploadHappinessError" to "UploadUnhappinessError" |
---|
7939 | Kevan Carstensen <kevan@isnotajoke.com>**20091205043037 |
---|
7940 | Ignore-this: 236b64ab19836854af4993bb5c1b221a |
---|
7941 | ] |
---|
7942 | [Alter the error message returned when peer selection fails |
---|
7943 | Kevan Carstensen <kevan@isnotajoke.com>**20091123002405 |
---|
7944 | Ignore-this: b2a7dc163edcab8d9613bfd6907e5166 |
---|
7945 | |
---|
7946 | The Tahoe2PeerSelector returned either NoSharesError or NotEnoughSharesError |
---|
7947 | for a variety of error conditions that weren't informatively described by them. |
---|
7948 | This patch creates a new error, UploadHappinessError, replaces uses of |
---|
7949 | NoSharesError and NotEnoughSharesError with it, and alters the error message |
---|
7950 | raised with the errors to be more in line with the new servers_of_happiness |
---|
7951 | behavior. See ticket #834 for more information. |
---|
7952 | ] |
---|
7953 | [Eliminate overcounting iof servers_of_happiness in Tahoe2PeerSelector; also reorganize some things. |
---|
7954 | Kevan Carstensen <kevan@isnotajoke.com>**20091118014542 |
---|
7955 | Ignore-this: a6cb032cbff74f4f9d4238faebd99868 |
---|
7956 | ] |
---|
7957 | [Change stray "shares_of_happiness" to "servers_of_happiness" |
---|
7958 | Kevan Carstensen <kevan@isnotajoke.com>**20091116212459 |
---|
7959 | Ignore-this: 1c971ba8c3c4d2e7ba9f020577b28b73 |
---|
7960 | ] |
---|
7961 | [Alter Tahoe2PeerSelector to make sure that it recognizes existing shares on readonly servers, fixing an issue in #778 |
---|
7962 | Kevan Carstensen <kevan@isnotajoke.com>**20091116192805 |
---|
7963 | Ignore-this: 15289f4d709e03851ed0587b286fd955 |
---|
7964 | ] |
---|
7965 | [Alter 'immutable/encode.py' and 'immutable/upload.py' to use servers_of_happiness instead of shares_of_happiness. |
---|
7966 | Kevan Carstensen <kevan@isnotajoke.com>**20091104111222 |
---|
7967 | Ignore-this: abb3283314820a8bbf9b5d0cbfbb57c8 |
---|
7968 | ] |
---|
7969 | [Alter the signature of set_shareholders in IEncoder to add a 'servermap' parameter, which gives IEncoders enough information to perform a sane check for servers_of_happiness. |
---|
7970 | Kevan Carstensen <kevan@isnotajoke.com>**20091104033241 |
---|
7971 | Ignore-this: b3a6649a8ac66431beca1026a31fed94 |
---|
7972 | ] |
---|
7973 | [Alter CiphertextDownloader to work with servers_of_happiness |
---|
7974 | Kevan Carstensen <kevan@isnotajoke.com>**20090924041932 |
---|
7975 | Ignore-this: e81edccf0308c2d3bedbc4cf217da197 |
---|
7976 | ] |
---|
7977 | [Revisions of the #778 tests, per reviewers' comments |
---|
7978 | Kevan Carstensen <kevan@isnotajoke.com>**20100514012542 |
---|
7979 | Ignore-this: 735bbc7f663dce633caeb3b66a53cf6e |
---|
7980 | |
---|
7981 | - Fix comments and confusing naming. |
---|
7982 | - Add tests for the new error messages suggested by David-Sarah |
---|
7983 | and Zooko. |
---|
7984 | - Alter existing tests for new error messages. |
---|
7985 | - Make sure that the tests continue to work with the trunk. |
---|
7986 | - Add a test for a mutual disjointedness assertion that I added to |
---|
7987 | upload.servers_of_happiness. |
---|
7988 | - Fix the comments to correctly reflect read-onlyness |
---|
7989 | - Add a test for an edge case in should_add_server |
---|
7990 | - Add an assertion to make sure that share redistribution works as it |
---|
7991 | should |
---|
7992 | - Alter tests to work with revised servers_of_happiness semantics |
---|
7993 | - Remove tests for should_add_server, since that function no longer exists. |
---|
7994 | - Alter tests to know about merge_peers, and to use it before calling |
---|
7995 | servers_of_happiness. |
---|
7996 | - Add tests for merge_peers. |
---|
7997 | - Add Zooko's puzzles to the tests. |
---|
7998 | - Edit encoding tests to expect the new kind of failure message. |
---|
7999 | - Edit tests to expect error messages with the word "only" moved as far |
---|
8000 | to the right as possible. |
---|
8001 | - Extended and cleaned up some helper functions. |
---|
8002 | - Changed some tests to call more appropriate helper functions. |
---|
8003 | - Added a test for the failing redistribution algorithm |
---|
8004 | - Added a test for the progress message |
---|
8005 | - Added a test for the upper bound on readonly peer share discovery. |
---|
8006 | |
---|
8007 | ] |
---|
8008 | [Alter various unit tests to work with the new happy behavior |
---|
8009 | Kevan Carstensen <kevan@isnotajoke.com>**20100107181325 |
---|
8010 | Ignore-this: 132032bbf865e63a079f869b663be34a |
---|
8011 | ] |
---|
8012 | [Replace "UploadHappinessError" with "UploadUnhappinessError" in tests. |
---|
8013 | Kevan Carstensen <kevan@isnotajoke.com>**20091205043453 |
---|
8014 | Ignore-this: 83f4bc50c697d21b5f4e2a4cd91862ca |
---|
8015 | ] |
---|
8016 | [Add tests for the behavior described in #834. |
---|
8017 | Kevan Carstensen <kevan@isnotajoke.com>**20091123012008 |
---|
8018 | Ignore-this: d8e0aa0f3f7965ce9b5cea843c6d6f9f |
---|
8019 | ] |
---|
8020 | [Re-work 'test_upload.py' to be more readable; add more tests for #778 |
---|
8021 | Kevan Carstensen <kevan@isnotajoke.com>**20091116192334 |
---|
8022 | Ignore-this: 7e8565f92fe51dece5ae28daf442d659 |
---|
8023 | ] |
---|
8024 | [Test Tahoe2PeerSelector to make sure that it recognizeses existing shares on readonly servers |
---|
8025 | Kevan Carstensen <kevan@isnotajoke.com>**20091109003735 |
---|
8026 | Ignore-this: 12f9b4cff5752fca7ed32a6ebcff6446 |
---|
8027 | ] |
---|
8028 | [Add more tests for comment:53 in ticket #778 |
---|
8029 | Kevan Carstensen <kevan@isnotajoke.com>**20091104112849 |
---|
8030 | Ignore-this: 3bb2edd299a944cc9586e14d5d83ec8c |
---|
8031 | ] |
---|
8032 | [Add a test for upload.shares_by_server |
---|
8033 | Kevan Carstensen <kevan@isnotajoke.com>**20091104111324 |
---|
8034 | Ignore-this: f9802e82d6982a93e00f92e0b276f018 |
---|
8035 | ] |
---|
8036 | [Minor tweak to an existing test -- make the first server read-write, instead of read-only |
---|
8037 | Kevan Carstensen <kevan@isnotajoke.com>**20091104034232 |
---|
8038 | Ignore-this: a951a46c93f7f58dd44d93d8623b2aee |
---|
8039 | ] |
---|
8040 | [Alter tests to use the new form of set_shareholders |
---|
8041 | Kevan Carstensen <kevan@isnotajoke.com>**20091104033602 |
---|
8042 | Ignore-this: 3deac11fc831618d11441317463ef830 |
---|
8043 | ] |
---|
8044 | [Refactor some behavior into a mixin, and add tests for the behavior described in #778 |
---|
8045 | "Kevan Carstensen" <kevan@isnotajoke.com>**20091030091908 |
---|
8046 | Ignore-this: a6f9797057ca135579b249af3b2b66ac |
---|
8047 | ] |
---|
8048 | [Alter NoNetworkGrid to allow the creation of readonly servers for testing purposes. |
---|
8049 | Kevan Carstensen <kevan@isnotajoke.com>**20091018013013 |
---|
8050 | Ignore-this: e12cd7c4ddeb65305c5a7e08df57c754 |
---|
8051 | ] |
---|
8052 | [Update 'docs/architecture.txt' to reflect readonly share discovery |
---|
8053 | kevan@isnotajoke.com**20100514003852 |
---|
8054 | Ignore-this: 7ead71b34df3b1ecfdcfd3cb2882e4f9 |
---|
8055 | ] |
---|
8056 | [Alter the wording in docs/architecture.txt to more accurately describe the servers_of_happiness behavior. |
---|
8057 | Kevan Carstensen <kevan@isnotajoke.com>**20100428002455 |
---|
8058 | Ignore-this: 6eff7fa756858a1c6f73728d989544cc |
---|
8059 | ] |
---|
8060 | [Alter wording in 'interfaces.py' to be correct wrt #778 |
---|
8061 | "Kevan Carstensen" <kevan@isnotajoke.com>**20091205034005 |
---|
8062 | Ignore-this: c9913c700ac14e7a63569458b06980e0 |
---|
8063 | ] |
---|
8064 | [Update 'docs/configuration.txt' to reflect the servers_of_happiness behavior. |
---|
8065 | Kevan Carstensen <kevan@isnotajoke.com>**20091205033813 |
---|
8066 | Ignore-this: 5e1cb171f8239bfb5b565d73c75ac2b8 |
---|
8067 | ] |
---|
8068 | [Clarify quickstart instructions for installing pywin32 |
---|
8069 | david-sarah@jacaranda.org**20100511180300 |
---|
8070 | Ignore-this: d4668359673600d2acbc7cd8dd44b93c |
---|
8071 | ] |
---|
8072 | [web: add a simple test that you can load directory.xhtml |
---|
8073 | zooko@zooko.com**20100510063729 |
---|
8074 | Ignore-this: e49b25fa3c67b3c7a56c8b1ae01bb463 |
---|
8075 | ] |
---|
8076 | [setup: fix typos in misc/show-tool-versions.py |
---|
8077 | zooko@zooko.com**20100510063615 |
---|
8078 | Ignore-this: 2181b1303a0e288e7a9ebd4c4855628 |
---|
8079 | ] |
---|
8080 | [setup: show code-coverage tool versions in show-tools-versions.py |
---|
8081 | zooko@zooko.com**20100510062955 |
---|
8082 | Ignore-this: 4b4c68eb3780b762c8dbbd22b39df7cf |
---|
8083 | ] |
---|
8084 | [docs: update README, mv it to README.txt, update setup.py |
---|
8085 | zooko@zooko.com**20100504094340 |
---|
8086 | Ignore-this: 40e28ca36c299ea1fd12d3b91e5b421c |
---|
8087 | ] |
---|
8088 | [Dependency on Windmill test framework is not needed yet. |
---|
8089 | david-sarah@jacaranda.org**20100504161043 |
---|
8090 | Ignore-this: be088712bec650d4ef24766c0026ebc8 |
---|
8091 | ] |
---|
8092 | [tests: pass z to tar so that BSD tar will know to ungzip |
---|
8093 | zooko@zooko.com**20100504090628 |
---|
8094 | Ignore-this: 1339e493f255e8fc0b01b70478f23a09 |
---|
8095 | ] |
---|
8096 | [setup: update comments and URLs in setup.cfg |
---|
8097 | zooko@zooko.com**20100504061653 |
---|
8098 | Ignore-this: f97692807c74bcab56d33100c899f829 |
---|
8099 | ] |
---|
8100 | [setup: reorder and extend the show-tool-versions script, the better to glean information about our new buildslaves |
---|
8101 | zooko@zooko.com**20100504045643 |
---|
8102 | Ignore-this: 836084b56b8d4ee8f1de1f4efb706d36 |
---|
8103 | ] |
---|
8104 | [CLI: Support for https url in option --node-url |
---|
8105 | Francois Deppierraz <francois@ctrlaltdel.ch>**20100430185609 |
---|
8106 | Ignore-this: 1717176b4d27c877e6bc67a944d9bf34 |
---|
8107 | |
---|
8108 | This patch modifies the regular expression used for verifying of '--node-url' |
---|
8109 | parameter. Support for accessing a Tahoe gateway over HTTPS was already |
---|
8110 | present, thanks to Python's urllib. |
---|
8111 | |
---|
8112 | ] |
---|
8113 | [backupdb.did_create_directory: use REPLACE INTO, not INSERT INTO + ignore error |
---|
8114 | Brian Warner <warner@lothar.com>**20100428050803 |
---|
8115 | Ignore-this: 1fca7b8f364a21ae413be8767161e32f |
---|
8116 | |
---|
8117 | This handles the case where we upload a new tahoe directory for a |
---|
8118 | previously-processed local directory, possibly creating a new dircap (if the |
---|
8119 | metadata had changed). Now we replace the old dirhash->dircap record. The |
---|
8120 | previous behavior left the old record in place (with the old dircap and |
---|
8121 | timestamps), so we'd never stop creating new directories and never converge |
---|
8122 | on a null backup. |
---|
8123 | ] |
---|
8124 | ["tahoe webopen": add --info flag, to get ?t=info |
---|
8125 | Brian Warner <warner@lothar.com>**20100424233003 |
---|
8126 | Ignore-this: 126b0bb6db340fabacb623d295eb45fa |
---|
8127 | |
---|
8128 | Also fix some trailing whitespace. |
---|
8129 | ] |
---|
8130 | [docs: install.html http-equiv refresh to quickstart.html |
---|
8131 | zooko@zooko.com**20100421165708 |
---|
8132 | Ignore-this: 52b4b619f9dde5886ae2cd7f1f3b734b |
---|
8133 | ] |
---|
8134 | [docs: install.html -> quickstart.html |
---|
8135 | zooko@zooko.com**20100421155757 |
---|
8136 | Ignore-this: 6084e203909306bed93efb09d0e6181d |
---|
8137 | It is not called "installing" because that implies that it is going to change the configuration of your operating system. It is not called "building" because that implies that you need developer tools like a compiler. Also I added a stern warning against looking at the "InstallDetails" wiki page, which I have renamed to "AdvancedInstall". |
---|
8138 | ] |
---|
8139 | [Fix another typo in tahoe_storagespace munin plugin |
---|
8140 | david-sarah@jacaranda.org**20100416220935 |
---|
8141 | Ignore-this: ad1f7aa66b554174f91dfb2b7a3ea5f3 |
---|
8142 | ] |
---|
8143 | [Add dependency on windmill >= 1.3 |
---|
8144 | david-sarah@jacaranda.org**20100416190404 |
---|
8145 | Ignore-this: 4437a7a464e92d6c9012926b18676211 |
---|
8146 | ] |
---|
8147 | [licensing: phrase the OpenSSL-exemption in the vocabulary of copyright instead of computer technology, and replicate the exemption from the GPL to the TGPPL |
---|
8148 | zooko@zooko.com**20100414232521 |
---|
8149 | Ignore-this: a5494b2f582a295544c6cad3f245e91 |
---|
8150 | ] |
---|
8151 | [munin-tahoe_storagespace |
---|
8152 | freestorm77@gmail.com**20100221203626 |
---|
8153 | Ignore-this: 14d6d6a587afe1f8883152bf2e46b4aa |
---|
8154 | |
---|
8155 | Plugin configuration rename |
---|
8156 | |
---|
8157 | ] |
---|
8158 | [setup: add licensing declaration for setuptools (noticed by the FSF compliance folks) |
---|
8159 | zooko@zooko.com**20100309184415 |
---|
8160 | Ignore-this: 2dfa7d812d65fec7c72ddbf0de609ccb |
---|
8161 | ] |
---|
8162 | [setup: fix error in licensing declaration from Shawn Willden, as noted by the FSF compliance division |
---|
8163 | zooko@zooko.com**20100309163736 |
---|
8164 | Ignore-this: c0623d27e469799d86cabf67921a13f8 |
---|
8165 | ] |
---|
8166 | [CREDITS to Jacob Appelbaum |
---|
8167 | zooko@zooko.com**20100304015616 |
---|
8168 | Ignore-this: 70db493abbc23968fcc8db93f386ea54 |
---|
8169 | ] |
---|
8170 | [desert-island-build-with-proper-versions |
---|
8171 | jacob@appelbaum.net**20100304013858] |
---|
8172 | [docs: a few small edits to try to guide newcomers through the docs |
---|
8173 | zooko@zooko.com**20100303231902 |
---|
8174 | Ignore-this: a6aab44f5bf5ad97ea73e6976bc4042d |
---|
8175 | These edits were suggested by my watching over Jake Appelbaum's shoulder as he completely ignored/skipped/missed install.html and also as he decided that debian.txt wouldn't help him with basic installation. Then I threw in a few docs edits that have been sitting around in my sandbox asking to be committed for months. |
---|
8176 | ] |
---|
8177 | [TAG allmydata-tahoe-1.6.1 |
---|
8178 | david-sarah@jacaranda.org**20100228062314 |
---|
8179 | Ignore-this: eb5f03ada8ea953ee7780e7fe068539 |
---|
8180 | ] |
---|
8181 | Patch bundle hash: |
---|
8182 | 1e3b9ac8586160361064135a62923650efd0ca7a |
---|