source: trunk/src/allmydata/test/test_checker.py @ c4be4b5

Last change on this file since c4be4b5 was c4be4b5, checked in by Sajith Sasidharan <sajith@…>, at 2020-06-18T01:18:14Z

Make TestRequest? an object

Wouldn't pass PythonTwoRegressions?.test_new_style_classes without
this.

  • Property mode set to 100644
File size: 24.9 KB
Line 
1
2import re
3import json
4import os.path, shutil
5from twisted.trial import unittest
6from twisted.internet import defer
7
8from nevow.inevow import IRequest
9from zope.interface import implementer
10from twisted.web.server import Request
11from twisted.web.test.requesthelper import DummyChannel
12from twisted.web.template import flattenString
13
14from allmydata import check_results, uri
15from allmydata import uri as tahoe_uri
16from allmydata.util import base32
17from allmydata.web import check_results as web_check_results
18from allmydata.storage_client import StorageFarmBroker, NativeStorageServer
19from allmydata.storage.server import storage_index_to_dir
20from allmydata.monitor import Monitor
21from allmydata.test.no_network import GridTestMixin
22from allmydata.immutable.upload import Data
23from allmydata.test.common_web import WebRenderingMixin
24from allmydata.mutable.publish import MutableData
25
26from .common import (
27    EMPTY_CLIENT_CONFIG,
28)
29
30class FakeClient(object):
31    def get_storage_broker(self):
32        return self.storage_broker
33
34# XXX: We have to have this class because `common.get_arg()` expects a
35# `nevow.inevow.IRequest`, which `twisted.web.server.Request` isn't.
36# Also, the request needs to have `args` and `fields` properties so
37# that `allmydata.web.common.get_arg()` won't complain.
38@implementer(IRequest)
39class TestRequest(object, Request):
40    def __init__(self, args=None, fields=None):
41        Request.__init__(self, DummyChannel())
42        self.args = args or {}
43        self.fields = fields or {}
44
45class WebResultsRendering(unittest.TestCase):
46
47    def remove_tags(self, s):
48        s = re.sub(r'<[^>]*>', ' ', s)
49        s = re.sub(r'\s+', ' ', s)
50        return s
51
52    def create_fake_client(self):
53        sb = StorageFarmBroker(True, None, EMPTY_CLIENT_CONFIG)
54        # s.get_name() (the "short description") will be "v0-00000000".
55        # s.get_longname() will include the -long suffix.
56        servers = [("v0-00000000-long", "\x00"*20, "peer-0"),
57                   ("v0-ffffffff-long", "\xff"*20, "peer-f"),
58                   ("v0-11111111-long", "\x11"*20, "peer-11")]
59        for (key_s, binary_tubid, nickname) in servers:
60            server_id = key_s
61            tubid_b32 = base32.b2a(binary_tubid)
62            furl = "pb://%s@nowhere/fake" % tubid_b32
63            ann = { "version": 0,
64                    "service-name": "storage",
65                    "anonymous-storage-FURL": furl,
66                    "permutation-seed-base32": "",
67                    "nickname": unicode(nickname),
68                    "app-versions": {}, # need #466 and v2 introducer
69                    "my-version": "ver",
70                    "oldest-supported": "oldest",
71                    }
72            s = NativeStorageServer(server_id, ann, None, None, None)
73            sb.test_add_server(server_id, s)
74        c = FakeClient()
75        c.storage_broker = sb
76        return c
77
78    def render_json(self, resource):
79        return resource.render(TestRequest(args={"output": ["json"]}))
80
81    def render_element(self, element, args=None):
82        d = flattenString(TestRequest(args), element)
83        return unittest.TestCase().successResultOf(d)
84
85    def test_literal(self):
86        lcr = web_check_results.LiteralCheckResultsElement()
87
88        html = self.render_element(lcr)
89        s = self.remove_tags(html)
90        self.failUnlessIn("Literal files are always healthy", s)
91
92        html = self.render_element(lcr, args={"return_to": ["FOOURL"]})
93        s = self.remove_tags(html)
94        self.failUnlessIn("Literal files are always healthy", s)
95        self.failUnlessIn('<a href="FOOURL">Return to file.</a>', html)
96
97        c = self.create_fake_client()
98        lcr = web_check_results.LiteralCheckResultsRenderer(c)
99
100        js = self.render_json(lcr)
101        j = json.loads(js)
102        self.failUnlessEqual(j["storage-index"], "")
103        self.failUnlessEqual(j["results"]["healthy"], True)
104
105
106    def test_check(self):
107        c = self.create_fake_client()
108        sb = c.storage_broker
109        serverid_1 = "\x00"*20
110        serverid_f = "\xff"*20
111        server_1 = sb.get_stub_server(serverid_1)
112        server_f = sb.get_stub_server(serverid_f)
113        u = uri.CHKFileURI("\x00"*16, "\x00"*32, 3, 10, 1234)
114        data = { "count_happiness": 8,
115                 "count_shares_needed": 3,
116                 "count_shares_expected": 9,
117                 "count_shares_good": 10,
118                 "count_good_share_hosts": 11,
119                 "count_recoverable_versions": 1,
120                 "count_unrecoverable_versions": 0,
121                 "servers_responding": [],
122                 "sharemap": {"shareid1": [server_1, server_f]},
123                 "count_wrong_shares": 0,
124                 "list_corrupt_shares": [],
125                 "count_corrupt_shares": 0,
126                 "list_incompatible_shares": [],
127                 "count_incompatible_shares": 0,
128                 "report": [], "share_problems": [], "servermap": None,
129                 }
130        cr = check_results.CheckResults(u, u.get_storage_index(),
131                                        healthy=True, recoverable=True,
132                                        summary="groovy",
133                                        **data)
134        w = web_check_results.CheckResultsRendererElement(c, cr)
135        html = self.render_element(w)
136        s = self.remove_tags(html)
137        self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
138        self.failUnlessIn("Healthy : groovy", s)
139        self.failUnlessIn("Share Counts: need 3-of-9, have 10", s)
140        self.failUnlessIn("Happiness Level: 8", s)
141        self.failUnlessIn("Hosts with good shares: 11", s)
142        self.failUnlessIn("Corrupt shares: none", s)
143        self.failUnlessIn("Wrong Shares: 0", s)
144        self.failUnlessIn("Recoverable Versions: 1", s)
145        self.failUnlessIn("Unrecoverable Versions: 0", s)
146        self.failUnlessIn("Good Shares (sorted in share order): Share ID Nickname Node ID shareid1 peer-0 00000000 peer-f ffffffff", s)
147
148        cr = check_results.CheckResults(u, u.get_storage_index(),
149                                        healthy=False, recoverable=True,
150                                        summary="ungroovy",
151                                        **data)
152        w = web_check_results.CheckResultsRendererElement(c, cr)
153        html = self.render_element(w)
154        s = self.remove_tags(html)
155        self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
156        self.failUnlessIn("Not Healthy! : ungroovy", s)
157
158        data["count_corrupt_shares"] = 1
159        data["list_corrupt_shares"] = [(server_1, u.get_storage_index(), 2)]
160        cr = check_results.CheckResults(u, u.get_storage_index(),
161                                        healthy=False, recoverable=False,
162                                        summary="rather dead",
163                                        **data)
164        w = web_check_results.CheckResultsRendererElement(c, cr)
165        html = self.render_element(w)
166        s = self.remove_tags(html)
167        self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
168        self.failUnlessIn("Not Recoverable! : rather dead", s)
169        self.failUnlessIn("Corrupt shares: Share ID Nickname Node ID sh#2 peer-0 00000000", s)
170
171        html = self.render_element(w)
172        s = self.remove_tags(html)
173        self.failUnlessIn("File Check Results for SI=2k6avp", s) # abbreviated
174        self.failUnlessIn("Not Recoverable! : rather dead", s)
175
176        html = self.render_element(w, args={"return_to": ["FOOURL"]})
177        self.failUnlessIn('<a href="FOOURL">Return to file/directory.</a>',
178                          html)
179
180        w = web_check_results.CheckResultsRenderer(c, cr)
181        d = self.render_json(w)
182        def _check_json(jdata):
183            j = json.loads(jdata)
184            self.failUnlessEqual(j["summary"], "rather dead")
185            self.failUnlessEqual(j["storage-index"],
186                                 "2k6avpjga3dho3zsjo6nnkt7n4")
187            expected = {'count-happiness': 8,
188                        'count-shares-expected': 9,
189                        'healthy': False,
190                        'count-unrecoverable-versions': 0,
191                        'count-shares-needed': 3,
192                        'sharemap': {"shareid1":
193                                     ["v0-00000000-long", "v0-ffffffff-long"]},
194                        'count-recoverable-versions': 1,
195                        'list-corrupt-shares':
196                        [["v0-00000000-long", "2k6avpjga3dho3zsjo6nnkt7n4", 2]],
197                        'count-good-share-hosts': 11,
198                        'count-wrong-shares': 0,
199                        'count-shares-good': 10,
200                        'count-corrupt-shares': 1,
201                        'servers-responding': [],
202                        'recoverable': False,
203                        }
204            self.failUnlessEqual(j["results"], expected)
205        _check_json(d)
206
207        w = web_check_results.CheckResultsRendererElement(c, cr)
208        d = self.render_element(w)
209        def _check(html):
210            s = self.remove_tags(html)
211            self.failUnlessIn("File Check Results for SI=2k6avp", s)
212            self.failUnlessIn("Not Recoverable! : rather dead", s)
213        _check(html)
214
215    def test_check_and_repair(self):
216        c = self.create_fake_client()
217        sb = c.storage_broker
218        serverid_1 = "\x00"*20
219        serverid_f = "\xff"*20
220        u = uri.CHKFileURI("\x00"*16, "\x00"*32, 3, 10, 1234)
221
222        data = { "count_happiness": 5,
223                 "count_shares_needed": 3,
224                 "count_shares_expected": 10,
225                 "count_shares_good": 6,
226                 "count_good_share_hosts": 7,
227                 "count_recoverable_versions": 1,
228                 "count_unrecoverable_versions": 0,
229                 "servers_responding": [],
230                 "sharemap": {"shareid1": [sb.get_stub_server(serverid_1),
231                                           sb.get_stub_server(serverid_f)]},
232                 "count_wrong_shares": 0,
233                 "list_corrupt_shares": [],
234                 "count_corrupt_shares": 0,
235                 "list_incompatible_shares": [],
236                 "count_incompatible_shares": 0,
237                 "report": [], "share_problems": [], "servermap": None,
238                 }
239        pre_cr = check_results.CheckResults(u, u.get_storage_index(),
240                                            healthy=False, recoverable=True,
241                                            summary="illing",
242                                            **data)
243
244        data = { "count_happiness": 9,
245                 "count_shares_needed": 3,
246                 "count_shares_expected": 10,
247                 "count_shares_good": 10,
248                 "count_good_share_hosts": 11,
249                 "count_recoverable_versions": 1,
250                 "count_unrecoverable_versions": 0,
251                 "servers_responding": [],
252                 "sharemap": {"shareid1": [sb.get_stub_server(serverid_1),
253                                           sb.get_stub_server(serverid_f)]},
254                 "count_wrong_shares": 0,
255                 "count_corrupt_shares": 0,
256                 "list_corrupt_shares": [],
257                 "list_incompatible_shares": [],
258                 "count_incompatible_shares": 0,
259                 "report": [], "share_problems": [], "servermap": None,
260                 }
261        post_cr = check_results.CheckResults(u, u.get_storage_index(),
262                                             healthy=True, recoverable=True,
263                                             summary="groovy",
264                                             **data)
265
266        crr = check_results.CheckAndRepairResults(u.get_storage_index())
267        crr.pre_repair_results = pre_cr
268        crr.post_repair_results = post_cr
269        crr.repair_attempted = False
270
271        w = web_check_results.CheckAndRepairResultsRendererElement(c, crr)
272        html = self.render_element(w)
273        s = self.remove_tags(html)
274
275        self.failUnlessIn("File Check-And-Repair Results for SI=2k6avp", s)
276        self.failUnlessIn("Healthy : groovy", s)
277        self.failUnlessIn("No repair necessary", s)
278        self.failUnlessIn("Post-Repair Checker Results:", s)
279        self.failUnlessIn("Share Counts: need 3-of-10, have 10", s)
280
281        crr.repair_attempted = True
282        crr.repair_successful = True
283        html = self.render_element(w)
284        s = self.remove_tags(html)
285
286        self.failUnlessIn("File Check-And-Repair Results for SI=2k6avp", s)
287        self.failUnlessIn("Healthy : groovy", s)
288        self.failUnlessIn("Repair successful", s)
289        self.failUnlessIn("Post-Repair Checker Results:", s)
290
291        crr.repair_attempted = True
292        crr.repair_successful = False
293        post_cr = check_results.CheckResults(u, u.get_storage_index(),
294                                             healthy=False, recoverable=True,
295                                             summary="better",
296                                             **data)
297        crr.post_repair_results = post_cr
298        html = self.render_element(w)
299        s = self.remove_tags(html)
300
301        self.failUnlessIn("File Check-And-Repair Results for SI=2k6avp", s)
302        self.failUnlessIn("Not Healthy! : better", s)
303        self.failUnlessIn("Repair unsuccessful", s)
304        self.failUnlessIn("Post-Repair Checker Results:", s)
305
306        crr.repair_attempted = True
307        crr.repair_successful = False
308        post_cr = check_results.CheckResults(u, u.get_storage_index(),
309                                             healthy=False, recoverable=False,
310                                             summary="worse",
311                                             **data)
312        crr.post_repair_results = post_cr
313        html = self.render_element(w)
314        s = self.remove_tags(html)
315
316        self.failUnlessIn("File Check-And-Repair Results for SI=2k6avp", s)
317        self.failUnlessIn("Not Recoverable! : worse", s)
318        self.failUnlessIn("Repair unsuccessful", s)
319        self.failUnlessIn("Post-Repair Checker Results:", s)
320
321        w2 = web_check_results.CheckAndRepairResultsRenderer(c, crr)
322        d = self.render_json(w2)
323        def _got_json(data):
324            j = json.loads(data)
325            self.failUnlessEqual(j["repair-attempted"], True)
326            self.failUnlessEqual(j["storage-index"],
327                                 "2k6avpjga3dho3zsjo6nnkt7n4")
328            self.failUnlessEqual(j["pre-repair-results"]["summary"], "illing")
329            self.failUnlessEqual(j["post-repair-results"]["summary"], "worse")
330        _got_json(d)
331
332        w3 = web_check_results.CheckAndRepairResultsRenderer(c, None)
333        d = self.render_json(w3)
334        def _got_lit_results(data):
335            j = json.loads(data)
336            self.failUnlessEqual(j["repair-attempted"], False)
337            self.failUnlessEqual(j["storage-index"], "")
338        _got_lit_results(d)
339
340class BalancingAct(GridTestMixin, unittest.TestCase):
341    # test for #1115 regarding the 'count-good-share-hosts' metric
342
343
344    def add_server(self, server_number, readonly=False):
345        assert self.g, "I tried to find a grid at self.g, but failed"
346        ss = self.g.make_server(server_number, readonly)
347        #log.msg("just created a server, number: %s => %s" % (server_number, ss,))
348        self.g.add_server(server_number, ss)
349
350    def add_server_with_share(self, server_number, uri, share_number=None,
351                              readonly=False):
352        self.add_server(server_number, readonly)
353        if share_number is not None:
354            self.copy_share_to_server(uri, share_number, server_number)
355
356    def copy_share_to_server(self, uri, share_number, server_number):
357        ss = self.g.servers_by_number[server_number]
358        # Copy share i from the directory associated with the first
359        # storage server to the directory associated with this one.
360        assert self.g, "I tried to find a grid at self.g, but failed"
361        assert self.shares, "I tried to find shares at self.shares, but failed"
362        old_share_location = self.shares[share_number][2]
363        new_share_location = os.path.join(ss.storedir, "shares")
364        si = tahoe_uri.from_string(self.uri).get_storage_index()
365        new_share_location = os.path.join(new_share_location,
366                                          storage_index_to_dir(si))
367        if not os.path.exists(new_share_location):
368            os.makedirs(new_share_location)
369        new_share_location = os.path.join(new_share_location,
370                                          str(share_number))
371        if old_share_location != new_share_location:
372            shutil.copy(old_share_location, new_share_location)
373        shares = self.find_uri_shares(uri)
374        # Make sure that the storage server has the share.
375        self.failUnless((share_number, ss.my_nodeid, new_share_location)
376                        in shares)
377
378    def _pretty_shares_chart(self, uri):
379        # Servers are labeled A-Z, shares are labeled 0-9
380        letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
381        assert len(self.g.servers_by_number) < len(letters), \
382            "This little printing function is only meant for < 26 servers"
383        shares_chart = {}
384        names = dict(zip([ss.my_nodeid
385                          for _,ss in self.g.servers_by_number.iteritems()],
386                         letters))
387        for shnum, serverid, _ in self.find_uri_shares(uri):
388            shares_chart.setdefault(shnum, []).append(names[serverid])
389        return shares_chart
390
391    def test_good_share_hosts(self):
392        self.basedir = "checker/BalancingAct/1115"
393        self.set_up_grid(num_servers=1)
394        c0 = self.g.clients[0]
395        c0.encoding_params['happy'] = 1
396        c0.encoding_params['n'] = 4
397        c0.encoding_params['k'] = 3
398
399        DATA = "data" * 100
400        d = c0.upload(Data(DATA, convergence=""))
401        def _stash_immutable(ur):
402            self.imm = c0.create_node_from_uri(ur.get_uri())
403            self.uri = self.imm.get_uri()
404        d.addCallback(_stash_immutable)
405        d.addCallback(lambda ign:
406            self.find_uri_shares(self.uri))
407        def _store_shares(shares):
408            self.shares = shares
409        d.addCallback(_store_shares)
410
411        def add_three(_, i):
412            # Add a new server with just share 3
413            self.add_server_with_share(i, self.uri, 3)
414            #print self._pretty_shares_chart(self.uri)
415        for i in range(1,5):
416            d.addCallback(add_three, i)
417
418        def _check_and_repair(_):
419            return self.imm.check_and_repair(Monitor())
420        def _check_counts(crr, shares_good, good_share_hosts):
421            prr = crr.get_post_repair_results()
422            self.failUnlessEqual(prr.get_share_counter_good(), shares_good)
423            self.failUnlessEqual(prr.get_host_counter_good_shares(),
424                                 good_share_hosts)
425
426        """
427        Initial sharemap:
428            0:[A] 1:[A] 2:[A] 3:[A,B,C,D,E]
429          4 good shares, but 5 good hosts
430        After deleting all instances of share #3 and repairing:
431            0:[A], 1:[A,B], 2:[C,A], 3:[E]
432# actually: {0: ['E', 'A'], 1: ['C', 'A'], 2: ['A', 'B'], 3: ['D']}
433          Still 4 good shares but now 4 good hosts
434            """
435        d.addCallback(_check_and_repair)
436        d.addCallback(_check_counts, 4, 5)
437        d.addCallback(lambda _: self.delete_shares_numbered(self.uri, [3]))
438        d.addCallback(_check_and_repair)
439
440        # it can happen that our uploader will choose, e.g., to upload
441        # to servers B, C, D, E .. which will mean that all 5 serves
442        # now contain our shares (and thus "respond").
443
444        def _check_happy(crr):
445            prr = crr.get_post_repair_results()
446            self.assertTrue(prr.get_host_counter_good_shares() >= 4)
447            return crr
448        d.addCallback(_check_happy)
449        d.addCallback(lambda _: all([self.g.break_server(sid)
450                                     for sid in self.g.get_all_serverids()]))
451        d.addCallback(_check_and_repair)
452        d.addCallback(_check_counts, 0, 0)
453        return d
454
455class AddLease(GridTestMixin, unittest.TestCase):
456    # test for #875, in which failures in the add-lease call cause
457    # false-negatives in the checker
458
459    def test_875(self):
460        self.basedir = "checker/AddLease/875"
461        self.set_up_grid(num_servers=1)
462        c0 = self.g.clients[0]
463        c0.encoding_params['happy'] = 1
464        self.uris = {}
465        DATA = "data" * 100
466        d = c0.upload(Data(DATA, convergence=""))
467        def _stash_immutable(ur):
468            self.imm = c0.create_node_from_uri(ur.get_uri())
469        d.addCallback(_stash_immutable)
470        d.addCallback(lambda ign:
471            c0.create_mutable_file(MutableData("contents")))
472        def _stash_mutable(node):
473            self.mut = node
474        d.addCallback(_stash_mutable)
475
476        def _check_cr(cr, which):
477            self.failUnless(cr.is_healthy(), which)
478
479        # these two should work normally
480        d.addCallback(lambda ign: self.imm.check(Monitor(), add_lease=True))
481        d.addCallback(_check_cr, "immutable-normal")
482        d.addCallback(lambda ign: self.mut.check(Monitor(), add_lease=True))
483        d.addCallback(_check_cr, "mutable-normal")
484
485        really_did_break = []
486        # now break the server's remote_add_lease call
487        def _break_add_lease(ign):
488            def broken_add_lease(*args, **kwargs):
489                really_did_break.append(1)
490                raise KeyError("intentional failure, should be ignored")
491            assert self.g.servers_by_number[0].remote_add_lease
492            self.g.servers_by_number[0].remote_add_lease = broken_add_lease
493        d.addCallback(_break_add_lease)
494
495        # and confirm that the files still look healthy
496        d.addCallback(lambda ign: self.mut.check(Monitor(), add_lease=True))
497        d.addCallback(_check_cr, "mutable-broken")
498        d.addCallback(lambda ign: self.imm.check(Monitor(), add_lease=True))
499        d.addCallback(_check_cr, "immutable-broken")
500
501        d.addCallback(lambda ign: self.failUnless(really_did_break))
502        return d
503
504class CounterHolder(object):
505    def __init__(self):
506        self._num_active_block_fetches = 0
507        self._max_active_block_fetches = 0
508
509from allmydata.immutable.checker import ValidatedReadBucketProxy
510class MockVRBP(ValidatedReadBucketProxy):
511    def __init__(self, sharenum, bucket, share_hash_tree, num_blocks, block_size, share_size, counterholder):
512        ValidatedReadBucketProxy.__init__(self, sharenum, bucket,
513                                          share_hash_tree, num_blocks,
514                                          block_size, share_size)
515        self.counterholder = counterholder
516
517    def get_block(self, blocknum):
518        self.counterholder._num_active_block_fetches += 1
519        if self.counterholder._num_active_block_fetches > self.counterholder._max_active_block_fetches:
520            self.counterholder._max_active_block_fetches = self.counterholder._num_active_block_fetches
521        d = ValidatedReadBucketProxy.get_block(self, blocknum)
522        def _mark_no_longer_active(res):
523            self.counterholder._num_active_block_fetches -= 1
524            return res
525        d.addBoth(_mark_no_longer_active)
526        return d
527
528class TooParallel(GridTestMixin, unittest.TestCase):
529    # bug #1395: immutable verifier was aggressively parallized, checking all
530    # blocks of all shares at the same time, blowing our memory budget and
531    # crashing with MemoryErrors on >1GB files.
532
533    def test_immutable(self):
534        import allmydata.immutable.checker
535        origVRBP = allmydata.immutable.checker.ValidatedReadBucketProxy
536
537        self.basedir = "checker/TooParallel/immutable"
538
539        # If any code asks to instantiate a ValidatedReadBucketProxy,
540        # we give them a MockVRBP which is configured to use our
541        # CounterHolder.
542        counterholder = CounterHolder()
543        def make_mock_VRBP(*args, **kwargs):
544            return MockVRBP(counterholder=counterholder, *args, **kwargs)
545        allmydata.immutable.checker.ValidatedReadBucketProxy = make_mock_VRBP
546
547        d = defer.succeed(None)
548        def _start(ign):
549            self.set_up_grid(num_servers=4)
550            self.c0 = self.g.clients[0]
551            self.c0.encoding_params = { "k": 1,
552                                        "happy": 4,
553                                        "n": 4,
554                                        "max_segment_size": 5,
555                                      }
556            self.uris = {}
557            DATA = "data" * 100 # 400/5 = 80 blocks
558            return self.c0.upload(Data(DATA, convergence=""))
559        d.addCallback(_start)
560        def _do_check(ur):
561            n = self.c0.create_node_from_uri(ur.get_uri())
562            return n.check(Monitor(), verify=True)
563        d.addCallback(_do_check)
564        def _check(cr):
565            # the verifier works on all 4 shares in parallel, but only
566            # fetches one block from each share at a time, so we expect to
567            # see 4 parallel fetches
568            self.failUnlessEqual(counterholder._max_active_block_fetches, 4)
569        d.addCallback(_check)
570        def _clean_up(res):
571            allmydata.immutable.checker.ValidatedReadBucketProxy = origVRBP
572            return res
573        d.addBoth(_clean_up)
574        return d
Note: See TracBrowser for help on using the repository browser.