| 1 | """ |
|---|
| 2 | Ported to Python 3. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | from ..common import AsyncTestCase |
|---|
| 6 | from testtools.matchers import Equals |
|---|
| 7 | from allmydata.interfaces import SDMF_VERSION |
|---|
| 8 | from allmydata.monitor import Monitor |
|---|
| 9 | from foolscap.logging import log |
|---|
| 10 | from allmydata.mutable.common import MODE_READ |
|---|
| 11 | from allmydata.mutable.publish import Publish, MutableData |
|---|
| 12 | from allmydata.mutable.servermap import ServerMap, ServermapUpdater |
|---|
| 13 | from ..common_util import DevNullDictionary |
|---|
| 14 | from .util import FakeStorage, make_nodemaker |
|---|
| 15 | |
|---|
| 16 | class MultipleEncodings(AsyncTestCase): |
|---|
| 17 | def setUp(self): |
|---|
| 18 | super(MultipleEncodings, self).setUp() |
|---|
| 19 | self.CONTENTS = b"New contents go here" |
|---|
| 20 | self.uploadable = MutableData(self.CONTENTS) |
|---|
| 21 | self._storage = FakeStorage() |
|---|
| 22 | self._nodemaker = make_nodemaker(self._storage, num_peers=20) |
|---|
| 23 | self._storage_broker = self._nodemaker.storage_broker |
|---|
| 24 | d = self._nodemaker.create_mutable_file(self.uploadable) |
|---|
| 25 | def _created(node): |
|---|
| 26 | self._fn = node |
|---|
| 27 | d.addCallback(_created) |
|---|
| 28 | return d |
|---|
| 29 | |
|---|
| 30 | def _encode(self, k, n, data, version=SDMF_VERSION): |
|---|
| 31 | # encode 'data' into a peerid->shares dict. |
|---|
| 32 | |
|---|
| 33 | fn = self._fn |
|---|
| 34 | # disable the nodecache, since for these tests we explicitly need |
|---|
| 35 | # multiple nodes pointing at the same file |
|---|
| 36 | self._nodemaker._node_cache = DevNullDictionary() |
|---|
| 37 | fn2 = self._nodemaker.create_from_cap(fn.get_uri()) |
|---|
| 38 | # then we copy over other fields that are normally fetched from the |
|---|
| 39 | # existing shares |
|---|
| 40 | fn2._pubkey = fn._pubkey |
|---|
| 41 | fn2._privkey = fn._privkey |
|---|
| 42 | fn2._encprivkey = fn._encprivkey |
|---|
| 43 | # and set the encoding parameters to something completely different |
|---|
| 44 | fn2._required_shares = k |
|---|
| 45 | fn2._total_shares = n |
|---|
| 46 | |
|---|
| 47 | s = self._storage |
|---|
| 48 | s._peers = {} # clear existing storage |
|---|
| 49 | p2 = Publish(fn2, self._storage_broker, None) |
|---|
| 50 | uploadable = MutableData(data) |
|---|
| 51 | d = p2.publish(uploadable) |
|---|
| 52 | def _published(res): |
|---|
| 53 | shares = s._peers |
|---|
| 54 | s._peers = {} |
|---|
| 55 | return shares |
|---|
| 56 | d.addCallback(_published) |
|---|
| 57 | return d |
|---|
| 58 | |
|---|
| 59 | def make_servermap(self, mode=MODE_READ, oldmap=None): |
|---|
| 60 | if oldmap is None: |
|---|
| 61 | oldmap = ServerMap() |
|---|
| 62 | smu = ServermapUpdater(self._fn, self._storage_broker, Monitor(), |
|---|
| 63 | oldmap, mode) |
|---|
| 64 | d = smu.update() |
|---|
| 65 | return d |
|---|
| 66 | |
|---|
| 67 | def test_multiple_encodings(self): |
|---|
| 68 | # we encode the same file in two different ways (3-of-10 and 4-of-9), |
|---|
| 69 | # then mix up the shares, to make sure that download survives seeing |
|---|
| 70 | # a variety of encodings. This is actually kind of tricky to set up. |
|---|
| 71 | |
|---|
| 72 | contents1 = b"Contents for encoding 1 (3-of-10) go here"*1000 |
|---|
| 73 | contents2 = b"Contents for encoding 2 (4-of-9) go here"*1000 |
|---|
| 74 | contents3 = b"Contents for encoding 3 (4-of-7) go here"*1000 |
|---|
| 75 | |
|---|
| 76 | # we make a retrieval object that doesn't know what encoding |
|---|
| 77 | # parameters to use |
|---|
| 78 | fn3 = self._nodemaker.create_from_cap(self._fn.get_uri()) |
|---|
| 79 | |
|---|
| 80 | # now we upload a file through fn1, and grab its shares |
|---|
| 81 | d = self._encode(3, 10, contents1) |
|---|
| 82 | def _encoded_1(shares): |
|---|
| 83 | self._shares1 = shares |
|---|
| 84 | d.addCallback(_encoded_1) |
|---|
| 85 | d.addCallback(lambda res: self._encode(4, 9, contents2)) |
|---|
| 86 | def _encoded_2(shares): |
|---|
| 87 | self._shares2 = shares |
|---|
| 88 | d.addCallback(_encoded_2) |
|---|
| 89 | d.addCallback(lambda res: self._encode(4, 7, contents3)) |
|---|
| 90 | def _encoded_3(shares): |
|---|
| 91 | self._shares3 = shares |
|---|
| 92 | d.addCallback(_encoded_3) |
|---|
| 93 | |
|---|
| 94 | def _merge(res): |
|---|
| 95 | log.msg("merging sharelists") |
|---|
| 96 | # we merge the shares from the two sets, leaving each shnum in |
|---|
| 97 | # its original location, but using a share from set1 or set2 |
|---|
| 98 | # according to the following sequence: |
|---|
| 99 | # |
|---|
| 100 | # 4-of-9 a s2 |
|---|
| 101 | # 4-of-9 b s2 |
|---|
| 102 | # 4-of-7 c s3 |
|---|
| 103 | # 4-of-9 d s2 |
|---|
| 104 | # 3-of-9 e s1 |
|---|
| 105 | # 3-of-9 f s1 |
|---|
| 106 | # 3-of-9 g s1 |
|---|
| 107 | # 4-of-9 h s2 |
|---|
| 108 | # |
|---|
| 109 | # so that neither form can be recovered until fetch [f], at which |
|---|
| 110 | # point version-s1 (the 3-of-10 form) should be recoverable. If |
|---|
| 111 | # the implementation latches on to the first version it sees, |
|---|
| 112 | # then s2 will be recoverable at fetch [g]. |
|---|
| 113 | |
|---|
| 114 | # Later, when we implement code that handles multiple versions, |
|---|
| 115 | # we can use this framework to assert that all recoverable |
|---|
| 116 | # versions are retrieved, and test that 'epsilon' does its job |
|---|
| 117 | |
|---|
| 118 | places = [2, 2, 3, 2, 1, 1, 1, 2] |
|---|
| 119 | |
|---|
| 120 | sharemap = {} |
|---|
| 121 | sb = self._storage_broker |
|---|
| 122 | |
|---|
| 123 | for peerid in sorted(sb.get_all_serverids()): |
|---|
| 124 | for shnum in self._shares1.get(peerid, {}): |
|---|
| 125 | if shnum < len(places): |
|---|
| 126 | which = places[shnum] |
|---|
| 127 | else: |
|---|
| 128 | which = "x" |
|---|
| 129 | self._storage._peers[peerid] = peers = {} |
|---|
| 130 | in_1 = shnum in self._shares1[peerid] |
|---|
| 131 | in_2 = shnum in self._shares2.get(peerid, {}) |
|---|
| 132 | in_3 = shnum in self._shares3.get(peerid, {}) |
|---|
| 133 | if which == 1: |
|---|
| 134 | if in_1: |
|---|
| 135 | peers[shnum] = self._shares1[peerid][shnum] |
|---|
| 136 | sharemap[shnum] = peerid |
|---|
| 137 | elif which == 2: |
|---|
| 138 | if in_2: |
|---|
| 139 | peers[shnum] = self._shares2[peerid][shnum] |
|---|
| 140 | sharemap[shnum] = peerid |
|---|
| 141 | elif which == 3: |
|---|
| 142 | if in_3: |
|---|
| 143 | peers[shnum] = self._shares3[peerid][shnum] |
|---|
| 144 | sharemap[shnum] = peerid |
|---|
| 145 | |
|---|
| 146 | # we don't bother placing any other shares |
|---|
| 147 | # now sort the sequence so that share 0 is returned first |
|---|
| 148 | new_sequence = [sharemap[shnum] |
|---|
| 149 | for shnum in sorted(sharemap.keys())] |
|---|
| 150 | self._storage._sequence = new_sequence |
|---|
| 151 | log.msg("merge done") |
|---|
| 152 | d.addCallback(_merge) |
|---|
| 153 | d.addCallback(lambda res: fn3.download_best_version()) |
|---|
| 154 | def _retrieved(new_contents): |
|---|
| 155 | # the current specified behavior is "first version recoverable" |
|---|
| 156 | self.assertThat(new_contents, Equals(contents1)) |
|---|
| 157 | d.addCallback(_retrieved) |
|---|
| 158 | return d |
|---|