| 1 | """ |
|---|
| 2 | Ported to Python 3. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | from six import ensure_text |
|---|
| 6 | |
|---|
| 7 | import os, json |
|---|
| 8 | from urllib.parse import quote as url_quote |
|---|
| 9 | |
|---|
| 10 | from twisted.trial import unittest |
|---|
| 11 | from twisted.internet import defer |
|---|
| 12 | from twisted.internet.defer import inlineCallbacks, returnValue |
|---|
| 13 | |
|---|
| 14 | from allmydata.immutable import upload |
|---|
| 15 | from allmydata.mutable.common import UnrecoverableFileError |
|---|
| 16 | from allmydata.mutable.publish import MutableData |
|---|
| 17 | from allmydata.util import idlib |
|---|
| 18 | from allmydata.util import base32 |
|---|
| 19 | from allmydata.interfaces import ICheckResults, ICheckAndRepairResults, \ |
|---|
| 20 | IDeepCheckResults, IDeepCheckAndRepairResults |
|---|
| 21 | from allmydata.monitor import Monitor, OperationCancelledError |
|---|
| 22 | from allmydata.uri import LiteralFileURI |
|---|
| 23 | |
|---|
| 24 | from allmydata.test.common import ErrorMixin, _corrupt_mutable_share_data, \ |
|---|
| 25 | ShouldFailMixin |
|---|
| 26 | from .common_util import StallMixin, run_cli_unicode |
|---|
| 27 | from .common_web import do_http |
|---|
| 28 | from allmydata.test.no_network import GridTestMixin |
|---|
| 29 | from .cli.common import CLITestMixin |
|---|
| 30 | |
|---|
| 31 | |
|---|
| 32 | def run_cli(verb, *argv): |
|---|
| 33 | """Match usage in existing tests by accept *args.""" |
|---|
| 34 | return run_cli_unicode(verb, list(argv)) |
|---|
| 35 | |
|---|
| 36 | |
|---|
| 37 | class MutableChecker(GridTestMixin, unittest.TestCase, ErrorMixin): |
|---|
| 38 | def test_good(self): |
|---|
| 39 | self.basedir = "deepcheck/MutableChecker/good" |
|---|
| 40 | self.set_up_grid() |
|---|
| 41 | CONTENTS = b"a little bit of data" |
|---|
| 42 | CONTENTS_uploadable = MutableData(CONTENTS) |
|---|
| 43 | d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable) |
|---|
| 44 | def _created(node): |
|---|
| 45 | self.node = node |
|---|
| 46 | self.fileurl = "uri/" + url_quote(node.get_uri()) |
|---|
| 47 | d.addCallback(_created) |
|---|
| 48 | # now make sure the webapi verifier sees no problems |
|---|
| 49 | d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true", |
|---|
| 50 | method="POST")) |
|---|
| 51 | def _got_results(out): |
|---|
| 52 | self.failUnless(b"<span>Healthy : Healthy</span>" in out, out) |
|---|
| 53 | self.failUnless(b"Recoverable Versions: 10*seq1-" in out, out) |
|---|
| 54 | self.failIf(b"Not Healthy!" in out, out) |
|---|
| 55 | self.failIf(b"Unhealthy" in out, out) |
|---|
| 56 | self.failIf(b"Corrupt Shares" in out, out) |
|---|
| 57 | d.addCallback(_got_results) |
|---|
| 58 | d.addErrback(self.explain_web_error) |
|---|
| 59 | return d |
|---|
| 60 | |
|---|
| 61 | def test_corrupt(self): |
|---|
| 62 | self.basedir = "deepcheck/MutableChecker/corrupt" |
|---|
| 63 | self.set_up_grid() |
|---|
| 64 | CONTENTS = b"a little bit of data" |
|---|
| 65 | CONTENTS_uploadable = MutableData(CONTENTS) |
|---|
| 66 | d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable) |
|---|
| 67 | def _stash_and_corrupt(node): |
|---|
| 68 | self.node = node |
|---|
| 69 | self.fileurl = "uri/" + url_quote(node.get_uri()) |
|---|
| 70 | self.corrupt_shares_numbered(node.get_uri(), [0], |
|---|
| 71 | _corrupt_mutable_share_data) |
|---|
| 72 | d.addCallback(_stash_and_corrupt) |
|---|
| 73 | # now make sure the webapi verifier notices it |
|---|
| 74 | d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true", |
|---|
| 75 | method="POST")) |
|---|
| 76 | def _got_results(out): |
|---|
| 77 | self.failUnless(b"Not Healthy!" in out, out) |
|---|
| 78 | self.failUnless(b"Unhealthy: best version has only 9 shares (encoding is 3-of-10)" in out, out) |
|---|
| 79 | self.failUnless(b"Corrupt Shares:" in out, out) |
|---|
| 80 | d.addCallback(_got_results) |
|---|
| 81 | |
|---|
| 82 | # now make sure the webapi repairer can fix it |
|---|
| 83 | d.addCallback(lambda ign: |
|---|
| 84 | self.GET(self.fileurl+"?t=check&verify=true&repair=true", |
|---|
| 85 | method="POST")) |
|---|
| 86 | def _got_repair_results(out): |
|---|
| 87 | self.failUnless(b"<div>Repair successful</div>" in out, out) |
|---|
| 88 | d.addCallback(_got_repair_results) |
|---|
| 89 | d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=true", |
|---|
| 90 | method="POST")) |
|---|
| 91 | def _got_postrepair_results(out): |
|---|
| 92 | self.failIf(b"Not Healthy!" in out, out) |
|---|
| 93 | self.failUnless(b"Recoverable Versions: 10*seq" in out, out) |
|---|
| 94 | d.addCallback(_got_postrepair_results) |
|---|
| 95 | d.addErrback(self.explain_web_error) |
|---|
| 96 | |
|---|
| 97 | return d |
|---|
| 98 | |
|---|
| 99 | def test_delete_share(self): |
|---|
| 100 | self.basedir = "deepcheck/MutableChecker/delete_share" |
|---|
| 101 | self.set_up_grid() |
|---|
| 102 | CONTENTS = b"a little bit of data" |
|---|
| 103 | CONTENTS_uploadable = MutableData(CONTENTS) |
|---|
| 104 | d = self.g.clients[0].create_mutable_file(CONTENTS_uploadable) |
|---|
| 105 | def _stash_and_delete(node): |
|---|
| 106 | self.node = node |
|---|
| 107 | self.fileurl = "uri/" + url_quote(node.get_uri()) |
|---|
| 108 | self.delete_shares_numbered(node.get_uri(), [0]) |
|---|
| 109 | d.addCallback(_stash_and_delete) |
|---|
| 110 | # now make sure the webapi checker notices it |
|---|
| 111 | d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=false", |
|---|
| 112 | method="POST")) |
|---|
| 113 | def _got_results(out): |
|---|
| 114 | self.failUnless(b"Not Healthy!" in out, out) |
|---|
| 115 | self.failUnless(b"Unhealthy: best version has only 9 shares (encoding is 3-of-10)" in out, out) |
|---|
| 116 | self.failIf(b"Corrupt Shares" in out, out) |
|---|
| 117 | d.addCallback(_got_results) |
|---|
| 118 | |
|---|
| 119 | # now make sure the webapi repairer can fix it |
|---|
| 120 | d.addCallback(lambda ign: |
|---|
| 121 | self.GET(self.fileurl+"?t=check&verify=false&repair=true", |
|---|
| 122 | method="POST")) |
|---|
| 123 | def _got_repair_results(out): |
|---|
| 124 | self.failUnless(b"Repair successful" in out) |
|---|
| 125 | d.addCallback(_got_repair_results) |
|---|
| 126 | d.addCallback(lambda ign: self.GET(self.fileurl+"?t=check&verify=false", |
|---|
| 127 | method="POST")) |
|---|
| 128 | def _got_postrepair_results(out): |
|---|
| 129 | self.failIf(b"Not Healthy!" in out, out) |
|---|
| 130 | self.failUnless(b"Recoverable Versions: 10*seq" in out) |
|---|
| 131 | d.addCallback(_got_postrepair_results) |
|---|
| 132 | d.addErrback(self.explain_web_error) |
|---|
| 133 | |
|---|
| 134 | return d |
|---|
| 135 | |
|---|
| 136 | |
|---|
| 137 | class DeepCheckBase(GridTestMixin, ErrorMixin, StallMixin, ShouldFailMixin, |
|---|
| 138 | CLITestMixin): |
|---|
| 139 | |
|---|
| 140 | def web_json(self, n, **kwargs): |
|---|
| 141 | kwargs["output"] = "json" |
|---|
| 142 | d = self.web(n, "POST", **kwargs) |
|---|
| 143 | d.addCallback(self.decode_json) |
|---|
| 144 | return d |
|---|
| 145 | |
|---|
| 146 | def decode_json(self, args): |
|---|
| 147 | (s, url) = args |
|---|
| 148 | try: |
|---|
| 149 | data = json.loads(s) |
|---|
| 150 | except ValueError: |
|---|
| 151 | self.fail("%s: not JSON: '%s'" % (url, s)) |
|---|
| 152 | return data |
|---|
| 153 | |
|---|
| 154 | def parse_streamed_json(self, s): |
|---|
| 155 | s = ensure_text(s) |
|---|
| 156 | for unit in s.split("\n"): |
|---|
| 157 | if not unit: |
|---|
| 158 | # stream should end with a newline, so split returns "" |
|---|
| 159 | continue |
|---|
| 160 | try: |
|---|
| 161 | yield json.loads(unit) |
|---|
| 162 | except ValueError as le: |
|---|
| 163 | le.args = tuple(le.args + (unit,)) |
|---|
| 164 | raise |
|---|
| 165 | |
|---|
| 166 | @inlineCallbacks |
|---|
| 167 | def web(self, n, method="GET", **kwargs): |
|---|
| 168 | # returns (data, url) |
|---|
| 169 | url = (self.client_baseurls[0] + "uri/%s" % url_quote(n.get_uri()) |
|---|
| 170 | + "?" + "&".join(["%s=%s" % (k,str(v, "ascii") if isinstance(v, bytes) else v) for (k,v) in kwargs.items()])) |
|---|
| 171 | data = yield do_http(method, url, browser_like_redirects=True) |
|---|
| 172 | returnValue((data,url)) |
|---|
| 173 | |
|---|
| 174 | @inlineCallbacks |
|---|
| 175 | def wait_for_operation(self, ophandle): |
|---|
| 176 | url = self.client_baseurls[0] + "operations/" + str(ophandle, "ascii") |
|---|
| 177 | url += "?t=status&output=JSON" |
|---|
| 178 | while True: |
|---|
| 179 | body = yield do_http("get", url) |
|---|
| 180 | data = json.loads(body) |
|---|
| 181 | if data["finished"]: |
|---|
| 182 | break |
|---|
| 183 | yield self.stall(delay=0.1) |
|---|
| 184 | returnValue(data) |
|---|
| 185 | |
|---|
| 186 | @inlineCallbacks |
|---|
| 187 | def get_operation_results(self, ophandle, output=None): |
|---|
| 188 | url = self.client_baseurls[0] + "operations/" + str(ophandle, "ascii") |
|---|
| 189 | url += "?t=status" |
|---|
| 190 | if output: |
|---|
| 191 | url += "&output=" + output |
|---|
| 192 | body = yield do_http("get", url) |
|---|
| 193 | if output and output.lower() == "json": |
|---|
| 194 | data = json.loads(body) |
|---|
| 195 | else: |
|---|
| 196 | data = body |
|---|
| 197 | returnValue(data) |
|---|
| 198 | |
|---|
| 199 | @inlineCallbacks |
|---|
| 200 | def slow_web(self, n, output=None, **kwargs): |
|---|
| 201 | # use ophandle= |
|---|
| 202 | handle = base32.b2a(os.urandom(4)) |
|---|
| 203 | yield self.web(n, "POST", ophandle=handle, **kwargs) |
|---|
| 204 | yield self.wait_for_operation(handle) |
|---|
| 205 | data = yield self.get_operation_results(handle, output=output) |
|---|
| 206 | returnValue(data) |
|---|
| 207 | |
|---|
| 208 | |
|---|
| 209 | class DeepCheckWebGood(DeepCheckBase, unittest.TestCase): |
|---|
| 210 | # construct a small directory tree (with one dir, one immutable file, one |
|---|
| 211 | # mutable file, two LIT files, one DIR2:LIT empty dir, one DIR2:LIT tiny |
|---|
| 212 | # dir, and a loop), and then check/examine it in various ways. |
|---|
| 213 | |
|---|
| 214 | def set_up_tree(self): |
|---|
| 215 | # 2.9s |
|---|
| 216 | |
|---|
| 217 | c0 = self.g.clients[0] |
|---|
| 218 | d = c0.create_dirnode() |
|---|
| 219 | def _created_root(n): |
|---|
| 220 | self.root = n |
|---|
| 221 | self.root_uri = n.get_uri() |
|---|
| 222 | d.addCallback(_created_root) |
|---|
| 223 | d.addCallback(lambda ign: |
|---|
| 224 | c0.create_mutable_file(MutableData(b"mutable file contents"))) |
|---|
| 225 | d.addCallback(lambda n: self.root.set_node(u"mutable", n)) |
|---|
| 226 | def _created_mutable(n): |
|---|
| 227 | self.mutable = n |
|---|
| 228 | self.mutable_uri = n.get_uri() |
|---|
| 229 | d.addCallback(_created_mutable) |
|---|
| 230 | |
|---|
| 231 | large = upload.Data(b"Lots of data\n" * 1000, None) |
|---|
| 232 | d.addCallback(lambda ign: self.root.add_file(u"large", large)) |
|---|
| 233 | def _created_large(n): |
|---|
| 234 | self.large = n |
|---|
| 235 | self.large_uri = n.get_uri() |
|---|
| 236 | d.addCallback(_created_large) |
|---|
| 237 | |
|---|
| 238 | small = upload.Data(b"Small enough for a LIT", None) |
|---|
| 239 | d.addCallback(lambda ign: self.root.add_file(u"small", small)) |
|---|
| 240 | def _created_small(n): |
|---|
| 241 | self.small = n |
|---|
| 242 | self.small_uri = n.get_uri() |
|---|
| 243 | d.addCallback(_created_small) |
|---|
| 244 | |
|---|
| 245 | small2 = upload.Data(b"Small enough for a LIT too", None) |
|---|
| 246 | d.addCallback(lambda ign: self.root.add_file(u"small2", small2)) |
|---|
| 247 | def _created_small2(n): |
|---|
| 248 | self.small2 = n |
|---|
| 249 | self.small2_uri = n.get_uri() |
|---|
| 250 | d.addCallback(_created_small2) |
|---|
| 251 | |
|---|
| 252 | empty_litdir_uri = b"URI:DIR2-LIT:" |
|---|
| 253 | tiny_litdir_uri = b"URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT |
|---|
| 254 | |
|---|
| 255 | d.addCallback(lambda ign: self.root._create_and_validate_node(None, empty_litdir_uri, name=u"test_deepcheck empty_lit_dir")) |
|---|
| 256 | def _created_empty_lit_dir(n): |
|---|
| 257 | self.empty_lit_dir = n |
|---|
| 258 | self.empty_lit_dir_uri = n.get_uri() |
|---|
| 259 | self.root.set_node(u"empty_lit_dir", n) |
|---|
| 260 | d.addCallback(_created_empty_lit_dir) |
|---|
| 261 | |
|---|
| 262 | d.addCallback(lambda ign: self.root._create_and_validate_node(None, tiny_litdir_uri, name=u"test_deepcheck tiny_lit_dir")) |
|---|
| 263 | def _created_tiny_lit_dir(n): |
|---|
| 264 | self.tiny_lit_dir = n |
|---|
| 265 | self.tiny_lit_dir_uri = n.get_uri() |
|---|
| 266 | self.root.set_node(u"tiny_lit_dir", n) |
|---|
| 267 | d.addCallback(_created_tiny_lit_dir) |
|---|
| 268 | |
|---|
| 269 | d.addCallback(lambda ign: self.root.set_node(u"loop", self.root)) |
|---|
| 270 | return d |
|---|
| 271 | |
|---|
| 272 | def check_is_healthy(self, cr, n, where, incomplete=False): |
|---|
| 273 | self.failUnless(ICheckResults.providedBy(cr), where) |
|---|
| 274 | self.failUnless(cr.is_healthy(), where) |
|---|
| 275 | self.failUnlessEqual(cr.get_storage_index(), n.get_storage_index(), |
|---|
| 276 | where) |
|---|
| 277 | self.failUnlessEqual(cr.get_storage_index_string(), |
|---|
| 278 | base32.b2a(n.get_storage_index()), where) |
|---|
| 279 | num_servers = len(self.g.all_servers) |
|---|
| 280 | self.failUnlessEqual(num_servers, 10, where) |
|---|
| 281 | |
|---|
| 282 | self.failUnlessEqual(cr.get_happiness(), num_servers, where) |
|---|
| 283 | self.failUnlessEqual(cr.get_share_counter_good(), num_servers, where) |
|---|
| 284 | self.failUnlessEqual(cr.get_encoding_needed(), 3, where) |
|---|
| 285 | self.failUnlessEqual(cr.get_encoding_expected(), num_servers, where) |
|---|
| 286 | if not incomplete: |
|---|
| 287 | self.failUnlessEqual(cr.get_host_counter_good_shares(), |
|---|
| 288 | num_servers, where) |
|---|
| 289 | self.failUnlessEqual(cr.get_corrupt_shares(), [], where) |
|---|
| 290 | if not incomplete: |
|---|
| 291 | self.failUnlessEqual(sorted([s.get_serverid() |
|---|
| 292 | for s in cr.get_servers_responding()]), |
|---|
| 293 | sorted(self.g.get_all_serverids()), |
|---|
| 294 | where) |
|---|
| 295 | all_serverids = set() |
|---|
| 296 | for (shareid, servers) in list(cr.get_sharemap().items()): |
|---|
| 297 | all_serverids.update([s.get_serverid() for s in servers]) |
|---|
| 298 | self.failUnlessEqual(sorted(all_serverids), |
|---|
| 299 | sorted(self.g.get_all_serverids()), |
|---|
| 300 | where) |
|---|
| 301 | |
|---|
| 302 | self.failUnlessEqual(cr.get_share_counter_wrong(), 0, where) |
|---|
| 303 | self.failUnlessEqual(cr.get_version_counter_recoverable(), 1, where) |
|---|
| 304 | self.failUnlessEqual(cr.get_version_counter_unrecoverable(), 0, where) |
|---|
| 305 | |
|---|
| 306 | |
|---|
| 307 | def check_and_repair_is_healthy(self, cr, n, where, incomplete=False): |
|---|
| 308 | self.failUnless(ICheckAndRepairResults.providedBy(cr), (where, cr)) |
|---|
| 309 | self.failUnless(cr.get_pre_repair_results().is_healthy(), where) |
|---|
| 310 | self.check_is_healthy(cr.get_pre_repair_results(), n, where, incomplete) |
|---|
| 311 | self.failUnless(cr.get_post_repair_results().is_healthy(), where) |
|---|
| 312 | self.check_is_healthy(cr.get_post_repair_results(), n, where, incomplete) |
|---|
| 313 | self.failIf(cr.get_repair_attempted(), where) |
|---|
| 314 | |
|---|
| 315 | def deep_check_is_healthy(self, cr, num_healthy, where): |
|---|
| 316 | self.failUnless(IDeepCheckResults.providedBy(cr)) |
|---|
| 317 | self.failUnlessEqual(cr.get_counters()["count-objects-healthy"], |
|---|
| 318 | num_healthy, where) |
|---|
| 319 | |
|---|
| 320 | def deep_check_and_repair_is_healthy(self, cr, num_healthy, where): |
|---|
| 321 | self.failUnless(IDeepCheckAndRepairResults.providedBy(cr), where) |
|---|
| 322 | c = cr.get_counters() |
|---|
| 323 | self.failUnlessEqual(c["count-objects-healthy-pre-repair"], |
|---|
| 324 | num_healthy, where) |
|---|
| 325 | self.failUnlessEqual(c["count-objects-healthy-post-repair"], |
|---|
| 326 | num_healthy, where) |
|---|
| 327 | self.failUnlessEqual(c["count-repairs-attempted"], 0, where) |
|---|
| 328 | |
|---|
| 329 | def test_good(self): |
|---|
| 330 | self.basedir = "deepcheck/DeepCheckWebGood/good" |
|---|
| 331 | self.set_up_grid() |
|---|
| 332 | d = self.set_up_tree() |
|---|
| 333 | d.addCallback(self.do_stats) |
|---|
| 334 | d.addCallback(self.do_web_stream_manifest) |
|---|
| 335 | d.addCallback(self.do_web_stream_check) |
|---|
| 336 | d.addCallback(self.do_test_check_good) |
|---|
| 337 | d.addCallback(self.do_test_web_good) |
|---|
| 338 | d.addCallback(self.do_test_cli_good) |
|---|
| 339 | d.addErrback(self.explain_web_error) |
|---|
| 340 | d.addErrback(self.explain_error) |
|---|
| 341 | return d |
|---|
| 342 | |
|---|
| 343 | def do_stats(self, ignored): |
|---|
| 344 | d = defer.succeed(None) |
|---|
| 345 | d.addCallback(lambda ign: self.root.start_deep_stats().when_done()) |
|---|
| 346 | d.addCallback(self.check_stats_good) |
|---|
| 347 | return d |
|---|
| 348 | |
|---|
| 349 | def check_stats_good(self, s): |
|---|
| 350 | self.failUnlessEqual(s["count-directories"], 3) |
|---|
| 351 | self.failUnlessEqual(s["count-files"], 5) |
|---|
| 352 | self.failUnlessEqual(s["count-immutable-files"], 1) |
|---|
| 353 | self.failUnlessEqual(s["count-literal-files"], 3) |
|---|
| 354 | self.failUnlessEqual(s["count-mutable-files"], 1) |
|---|
| 355 | # don't check directories: their size will vary |
|---|
| 356 | # s["largest-directory"] |
|---|
| 357 | # s["size-directories"] |
|---|
| 358 | self.failUnlessEqual(s["largest-directory-children"], 7) |
|---|
| 359 | self.failUnlessEqual(s["largest-immutable-file"], 13000) |
|---|
| 360 | # to re-use this function for both the local |
|---|
| 361 | # dirnode.start_deep_stats() and the webapi t=start-deep-stats, we |
|---|
| 362 | # coerce the result into a list of tuples. dirnode.start_deep_stats() |
|---|
| 363 | # returns a list of tuples, but JSON only knows about lists., so |
|---|
| 364 | # t=start-deep-stats returns a list of lists. |
|---|
| 365 | histogram = [tuple(stuff) for stuff in s["size-files-histogram"]] |
|---|
| 366 | self.failUnlessEqual(histogram, [(4, 10, 1), (11, 31, 2), |
|---|
| 367 | (10001, 31622, 1), |
|---|
| 368 | ]) |
|---|
| 369 | self.failUnlessEqual(s["size-immutable-files"], 13000) |
|---|
| 370 | self.failUnlessEqual(s["size-literal-files"], 56) |
|---|
| 371 | |
|---|
| 372 | def do_web_stream_manifest(self, ignored): |
|---|
| 373 | d = self.web(self.root, method="POST", t="stream-manifest") |
|---|
| 374 | d.addCallback(lambda output_and_url: |
|---|
| 375 | self._check_streamed_manifest(output_and_url[0])) |
|---|
| 376 | return d |
|---|
| 377 | |
|---|
| 378 | def _check_streamed_manifest(self, output): |
|---|
| 379 | units = list(self.parse_streamed_json(output)) |
|---|
| 380 | files = [u for u in units if u["type"] in ("file", "directory")] |
|---|
| 381 | assert units[-1]["type"] == "stats" |
|---|
| 382 | stats = units[-1]["stats"] |
|---|
| 383 | self.failUnlessEqual(len(files), 8) |
|---|
| 384 | # [root,mutable,large] are distributed, [small,small2,empty_litdir,tiny_litdir] are not |
|---|
| 385 | self.failUnlessEqual(len([f for f in files |
|---|
| 386 | if f["verifycap"] != ""]), 3) |
|---|
| 387 | self.failUnlessEqual(len([f for f in files |
|---|
| 388 | if f["verifycap"] == ""]), 5) |
|---|
| 389 | self.failUnlessEqual(len([f for f in files |
|---|
| 390 | if f["repaircap"] != ""]), 3) |
|---|
| 391 | self.failUnlessEqual(len([f for f in files |
|---|
| 392 | if f["repaircap"] == ""]), 5) |
|---|
| 393 | self.failUnlessEqual(len([f for f in files |
|---|
| 394 | if f["storage-index"] != ""]), 3) |
|---|
| 395 | self.failUnlessEqual(len([f for f in files |
|---|
| 396 | if f["storage-index"] == ""]), 5) |
|---|
| 397 | # make sure that a mutable file has filecap==repaircap!=verifycap |
|---|
| 398 | mutable = [f for f in files |
|---|
| 399 | if f["cap"] is not None |
|---|
| 400 | and f["cap"].startswith("URI:SSK:")][0] |
|---|
| 401 | self.failUnlessEqual(mutable["cap"].encode("ascii"), self.mutable_uri) |
|---|
| 402 | self.failIfEqual(mutable["cap"], mutable["verifycap"]) |
|---|
| 403 | self.failUnlessEqual(mutable["cap"], mutable["repaircap"]) |
|---|
| 404 | # for immutable file, verifycap==repaircap!=filecap |
|---|
| 405 | large = [f for f in files |
|---|
| 406 | if f["cap"] is not None |
|---|
| 407 | and f["cap"].startswith("URI:CHK:")][0] |
|---|
| 408 | self.failUnlessEqual(large["cap"].encode("ascii"), self.large_uri) |
|---|
| 409 | self.failIfEqual(large["cap"], large["verifycap"]) |
|---|
| 410 | self.failUnlessEqual(large["verifycap"], large["repaircap"]) |
|---|
| 411 | self.check_stats_good(stats) |
|---|
| 412 | |
|---|
| 413 | def do_web_stream_check(self, ignored): |
|---|
| 414 | # TODO |
|---|
| 415 | return |
|---|
| 416 | d = self.web(self.root, t="stream-deep-check") |
|---|
| 417 | def _check(res): |
|---|
| 418 | units = list(self.parse_streamed_json(res)) |
|---|
| 419 | #files = [u for u in units if u["type"] in ("file", "directory")] |
|---|
| 420 | assert units[-1]["type"] == "stats" |
|---|
| 421 | #stats = units[-1]["stats"] |
|---|
| 422 | # ... |
|---|
| 423 | d.addCallback(_check) |
|---|
| 424 | return d |
|---|
| 425 | |
|---|
| 426 | def do_test_check_good(self, ignored): |
|---|
| 427 | d = defer.succeed(None) |
|---|
| 428 | # check the individual items |
|---|
| 429 | d.addCallback(lambda ign: self.root.check(Monitor())) |
|---|
| 430 | d.addCallback(self.check_is_healthy, self.root, "root") |
|---|
| 431 | d.addCallback(lambda ign: self.mutable.check(Monitor())) |
|---|
| 432 | d.addCallback(self.check_is_healthy, self.mutable, "mutable") |
|---|
| 433 | d.addCallback(lambda ign: self.large.check(Monitor())) |
|---|
| 434 | d.addCallback(self.check_is_healthy, self.large, "large") |
|---|
| 435 | d.addCallback(lambda ign: self.small.check(Monitor())) |
|---|
| 436 | d.addCallback(self.failUnlessEqual, None, "small") |
|---|
| 437 | d.addCallback(lambda ign: self.small2.check(Monitor())) |
|---|
| 438 | d.addCallback(self.failUnlessEqual, None, "small2") |
|---|
| 439 | d.addCallback(lambda ign: self.empty_lit_dir.check(Monitor())) |
|---|
| 440 | d.addCallback(self.failUnlessEqual, None, "empty_lit_dir") |
|---|
| 441 | d.addCallback(lambda ign: self.tiny_lit_dir.check(Monitor())) |
|---|
| 442 | d.addCallback(self.failUnlessEqual, None, "tiny_lit_dir") |
|---|
| 443 | |
|---|
| 444 | # and again with verify=True |
|---|
| 445 | d.addCallback(lambda ign: self.root.check(Monitor(), verify=True)) |
|---|
| 446 | d.addCallback(self.check_is_healthy, self.root, "root") |
|---|
| 447 | d.addCallback(lambda ign: self.mutable.check(Monitor(), verify=True)) |
|---|
| 448 | d.addCallback(self.check_is_healthy, self.mutable, "mutable") |
|---|
| 449 | d.addCallback(lambda ign: self.large.check(Monitor(), verify=True)) |
|---|
| 450 | d.addCallback(self.check_is_healthy, self.large, "large", incomplete=True) |
|---|
| 451 | d.addCallback(lambda ign: self.small.check(Monitor(), verify=True)) |
|---|
| 452 | d.addCallback(self.failUnlessEqual, None, "small") |
|---|
| 453 | d.addCallback(lambda ign: self.small2.check(Monitor(), verify=True)) |
|---|
| 454 | d.addCallback(self.failUnlessEqual, None, "small2") |
|---|
| 455 | d.addCallback(lambda ign: self.empty_lit_dir.check(Monitor(), verify=True)) |
|---|
| 456 | d.addCallback(self.failUnlessEqual, None, "empty_lit_dir") |
|---|
| 457 | d.addCallback(lambda ign: self.tiny_lit_dir.check(Monitor(), verify=True)) |
|---|
| 458 | d.addCallback(self.failUnlessEqual, None, "tiny_lit_dir") |
|---|
| 459 | |
|---|
| 460 | # and check_and_repair(), which should be a nop |
|---|
| 461 | d.addCallback(lambda ign: self.root.check_and_repair(Monitor())) |
|---|
| 462 | d.addCallback(self.check_and_repair_is_healthy, self.root, "root") |
|---|
| 463 | d.addCallback(lambda ign: self.mutable.check_and_repair(Monitor())) |
|---|
| 464 | d.addCallback(self.check_and_repair_is_healthy, self.mutable, "mutable") |
|---|
| 465 | d.addCallback(lambda ign: self.large.check_and_repair(Monitor())) |
|---|
| 466 | d.addCallback(self.check_and_repair_is_healthy, self.large, "large") |
|---|
| 467 | d.addCallback(lambda ign: self.small.check_and_repair(Monitor())) |
|---|
| 468 | d.addCallback(self.failUnlessEqual, None, "small") |
|---|
| 469 | d.addCallback(lambda ign: self.small2.check_and_repair(Monitor())) |
|---|
| 470 | d.addCallback(self.failUnlessEqual, None, "small2") |
|---|
| 471 | d.addCallback(lambda ign: self.empty_lit_dir.check_and_repair(Monitor())) |
|---|
| 472 | d.addCallback(self.failUnlessEqual, None, "empty_lit_dir") |
|---|
| 473 | d.addCallback(lambda ign: self.tiny_lit_dir.check_and_repair(Monitor())) |
|---|
| 474 | |
|---|
| 475 | # check_and_repair(verify=True) |
|---|
| 476 | d.addCallback(lambda ign: self.root.check_and_repair(Monitor(), verify=True)) |
|---|
| 477 | d.addCallback(self.check_and_repair_is_healthy, self.root, "root") |
|---|
| 478 | d.addCallback(lambda ign: self.mutable.check_and_repair(Monitor(), verify=True)) |
|---|
| 479 | d.addCallback(self.check_and_repair_is_healthy, self.mutable, "mutable") |
|---|
| 480 | d.addCallback(lambda ign: self.large.check_and_repair(Monitor(), verify=True)) |
|---|
| 481 | d.addCallback(self.check_and_repair_is_healthy, self.large, "large", incomplete=True) |
|---|
| 482 | d.addCallback(lambda ign: self.small.check_and_repair(Monitor(), verify=True)) |
|---|
| 483 | d.addCallback(self.failUnlessEqual, None, "small") |
|---|
| 484 | d.addCallback(lambda ign: self.small2.check_and_repair(Monitor(), verify=True)) |
|---|
| 485 | d.addCallback(self.failUnlessEqual, None, "small2") |
|---|
| 486 | d.addCallback(self.failUnlessEqual, None, "small2") |
|---|
| 487 | d.addCallback(lambda ign: self.empty_lit_dir.check_and_repair(Monitor(), verify=True)) |
|---|
| 488 | d.addCallback(self.failUnlessEqual, None, "empty_lit_dir") |
|---|
| 489 | d.addCallback(lambda ign: self.tiny_lit_dir.check_and_repair(Monitor(), verify=True)) |
|---|
| 490 | |
|---|
| 491 | |
|---|
| 492 | # now deep-check the root, with various verify= and repair= options |
|---|
| 493 | d.addCallback(lambda ign: |
|---|
| 494 | self.root.start_deep_check().when_done()) |
|---|
| 495 | d.addCallback(self.deep_check_is_healthy, 3, "root") |
|---|
| 496 | d.addCallback(lambda ign: |
|---|
| 497 | self.root.start_deep_check(verify=True).when_done()) |
|---|
| 498 | d.addCallback(self.deep_check_is_healthy, 3, "root") |
|---|
| 499 | d.addCallback(lambda ign: |
|---|
| 500 | self.root.start_deep_check_and_repair().when_done()) |
|---|
| 501 | d.addCallback(self.deep_check_and_repair_is_healthy, 3, "root") |
|---|
| 502 | d.addCallback(lambda ign: |
|---|
| 503 | self.root.start_deep_check_and_repair(verify=True).when_done()) |
|---|
| 504 | d.addCallback(self.deep_check_and_repair_is_healthy, 3, "root") |
|---|
| 505 | |
|---|
| 506 | # and finally, start a deep-check, but then cancel it. |
|---|
| 507 | d.addCallback(lambda ign: self.root.start_deep_check()) |
|---|
| 508 | def _checking(monitor): |
|---|
| 509 | monitor.cancel() |
|---|
| 510 | d = monitor.when_done() |
|---|
| 511 | # this should fire as soon as the next dirnode.list finishes. |
|---|
| 512 | # TODO: add a counter to measure how many list() calls are made, |
|---|
| 513 | # assert that no more than one gets to run before the cancel() |
|---|
| 514 | # takes effect. |
|---|
| 515 | def _finished_normally(res): |
|---|
| 516 | self.fail("this was supposed to fail, not finish normally") |
|---|
| 517 | def _cancelled(f): |
|---|
| 518 | f.trap(OperationCancelledError) |
|---|
| 519 | d.addCallbacks(_finished_normally, _cancelled) |
|---|
| 520 | return d |
|---|
| 521 | d.addCallback(_checking) |
|---|
| 522 | |
|---|
| 523 | return d |
|---|
| 524 | |
|---|
| 525 | def json_check_is_healthy(self, data, n, where, incomplete=False): |
|---|
| 526 | |
|---|
| 527 | self.failUnlessEqual(data["storage-index"], |
|---|
| 528 | str(base32.b2a(n.get_storage_index()), "ascii"), where) |
|---|
| 529 | self.failUnless("summary" in data, (where, data)) |
|---|
| 530 | self.failUnlessEqual(data["summary"].lower(), "healthy", |
|---|
| 531 | "%s: '%s'" % (where, data["summary"])) |
|---|
| 532 | r = data["results"] |
|---|
| 533 | self.failUnlessEqual(r["healthy"], True, where) |
|---|
| 534 | num_servers = len(self.g.all_servers) |
|---|
| 535 | self.failUnlessEqual(num_servers, 10) |
|---|
| 536 | |
|---|
| 537 | self.failIfIn("needs-rebalancing", r) |
|---|
| 538 | self.failUnlessEqual(r["count-happiness"], num_servers, where) |
|---|
| 539 | self.failUnlessEqual(r["count-shares-good"], num_servers, where) |
|---|
| 540 | self.failUnlessEqual(r["count-shares-needed"], 3, where) |
|---|
| 541 | self.failUnlessEqual(r["count-shares-expected"], num_servers, where) |
|---|
| 542 | if not incomplete: |
|---|
| 543 | self.failUnlessEqual(r["count-good-share-hosts"], num_servers, |
|---|
| 544 | where) |
|---|
| 545 | self.failUnlessEqual(r["count-corrupt-shares"], 0, where) |
|---|
| 546 | self.failUnlessEqual(r["list-corrupt-shares"], [], where) |
|---|
| 547 | if not incomplete: |
|---|
| 548 | self.failUnlessEqual(sorted(r["servers-responding"]), |
|---|
| 549 | sorted([idlib.nodeid_b2a(sid) |
|---|
| 550 | for sid in self.g.get_all_serverids()]), |
|---|
| 551 | where) |
|---|
| 552 | self.failUnless("sharemap" in r, where) |
|---|
| 553 | all_serverids = set() |
|---|
| 554 | for (shareid, serverids_s) in list(r["sharemap"].items()): |
|---|
| 555 | all_serverids.update(serverids_s) |
|---|
| 556 | self.failUnlessEqual(sorted(all_serverids), |
|---|
| 557 | sorted([idlib.nodeid_b2a(sid) |
|---|
| 558 | for sid in self.g.get_all_serverids()]), |
|---|
| 559 | where) |
|---|
| 560 | self.failUnlessEqual(r["count-wrong-shares"], 0, where) |
|---|
| 561 | self.failUnlessEqual(r["count-recoverable-versions"], 1, where) |
|---|
| 562 | self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where) |
|---|
| 563 | |
|---|
| 564 | def json_check_and_repair_is_healthy(self, data, n, where, incomplete=False): |
|---|
| 565 | self.failUnlessEqual(data["storage-index"], |
|---|
| 566 | str(base32.b2a(n.get_storage_index()), "ascii"), where) |
|---|
| 567 | self.failUnlessEqual(data["repair-attempted"], False, where) |
|---|
| 568 | self.json_check_is_healthy(data["pre-repair-results"], |
|---|
| 569 | n, where, incomplete) |
|---|
| 570 | self.json_check_is_healthy(data["post-repair-results"], |
|---|
| 571 | n, where, incomplete) |
|---|
| 572 | |
|---|
| 573 | def json_full_deepcheck_is_healthy(self, data, n, where): |
|---|
| 574 | self.failUnlessEqual(data["root-storage-index"], |
|---|
| 575 | str(base32.b2a(n.get_storage_index()), "ascii"), where) |
|---|
| 576 | self.failUnlessEqual(data["count-objects-checked"], 3, where) |
|---|
| 577 | self.failUnlessEqual(data["count-objects-healthy"], 3, where) |
|---|
| 578 | self.failUnlessEqual(data["count-objects-unhealthy"], 0, where) |
|---|
| 579 | self.failUnlessEqual(data["count-corrupt-shares"], 0, where) |
|---|
| 580 | self.failUnlessEqual(data["list-corrupt-shares"], [], where) |
|---|
| 581 | self.failUnlessEqual(data["list-unhealthy-files"], [], where) |
|---|
| 582 | self.json_check_stats_good(data["stats"], where) |
|---|
| 583 | |
|---|
| 584 | def json_full_deepcheck_and_repair_is_healthy(self, data, n, where): |
|---|
| 585 | self.failUnlessEqual(data["root-storage-index"], |
|---|
| 586 | str(base32.b2a(n.get_storage_index()), "ascii"), where) |
|---|
| 587 | self.failUnlessEqual(data["count-objects-checked"], 3, where) |
|---|
| 588 | |
|---|
| 589 | self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 3, where) |
|---|
| 590 | self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0, where) |
|---|
| 591 | self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0, where) |
|---|
| 592 | |
|---|
| 593 | self.failUnlessEqual(data["count-objects-healthy-post-repair"], 3, where) |
|---|
| 594 | self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0, where) |
|---|
| 595 | self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0, where) |
|---|
| 596 | |
|---|
| 597 | self.failUnlessEqual(data["list-corrupt-shares"], [], where) |
|---|
| 598 | self.failUnlessEqual(data["list-remaining-corrupt-shares"], [], where) |
|---|
| 599 | self.failUnlessEqual(data["list-unhealthy-files"], [], where) |
|---|
| 600 | |
|---|
| 601 | self.failUnlessEqual(data["count-repairs-attempted"], 0, where) |
|---|
| 602 | self.failUnlessEqual(data["count-repairs-successful"], 0, where) |
|---|
| 603 | self.failUnlessEqual(data["count-repairs-unsuccessful"], 0, where) |
|---|
| 604 | |
|---|
| 605 | |
|---|
| 606 | def json_check_lit(self, data, n, where): |
|---|
| 607 | self.failUnlessEqual(data["storage-index"], "", where) |
|---|
| 608 | self.failUnlessEqual(data["results"]["healthy"], True, where) |
|---|
| 609 | |
|---|
| 610 | def json_check_stats_good(self, data, where): |
|---|
| 611 | self.check_stats_good(data) |
|---|
| 612 | |
|---|
| 613 | def do_test_web_good(self, ignored): |
|---|
| 614 | d = defer.succeed(None) |
|---|
| 615 | |
|---|
| 616 | # stats |
|---|
| 617 | d.addCallback(lambda ign: |
|---|
| 618 | self.slow_web(self.root, |
|---|
| 619 | t="start-deep-stats", output="json")) |
|---|
| 620 | d.addCallback(self.json_check_stats_good, "deep-stats") |
|---|
| 621 | |
|---|
| 622 | # check, no verify |
|---|
| 623 | d.addCallback(lambda ign: self.web_json(self.root, t="check")) |
|---|
| 624 | d.addCallback(self.json_check_is_healthy, self.root, "root") |
|---|
| 625 | d.addCallback(lambda ign: self.web_json(self.mutable, t="check")) |
|---|
| 626 | d.addCallback(self.json_check_is_healthy, self.mutable, "mutable") |
|---|
| 627 | d.addCallback(lambda ign: self.web_json(self.large, t="check")) |
|---|
| 628 | d.addCallback(self.json_check_is_healthy, self.large, "large") |
|---|
| 629 | d.addCallback(lambda ign: self.web_json(self.small, t="check")) |
|---|
| 630 | d.addCallback(self.json_check_lit, self.small, "small") |
|---|
| 631 | d.addCallback(lambda ign: self.web_json(self.small2, t="check")) |
|---|
| 632 | d.addCallback(self.json_check_lit, self.small2, "small2") |
|---|
| 633 | d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check")) |
|---|
| 634 | d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir") |
|---|
| 635 | d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check")) |
|---|
| 636 | d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir") |
|---|
| 637 | |
|---|
| 638 | # check and verify |
|---|
| 639 | d.addCallback(lambda ign: |
|---|
| 640 | self.web_json(self.root, t="check", verify="true")) |
|---|
| 641 | d.addCallback(self.json_check_is_healthy, self.root, "root+v") |
|---|
| 642 | d.addCallback(lambda ign: |
|---|
| 643 | self.web_json(self.mutable, t="check", verify="true")) |
|---|
| 644 | d.addCallback(self.json_check_is_healthy, self.mutable, "mutable+v") |
|---|
| 645 | d.addCallback(lambda ign: |
|---|
| 646 | self.web_json(self.large, t="check", verify="true")) |
|---|
| 647 | d.addCallback(self.json_check_is_healthy, self.large, "large+v", |
|---|
| 648 | incomplete=True) |
|---|
| 649 | d.addCallback(lambda ign: |
|---|
| 650 | self.web_json(self.small, t="check", verify="true")) |
|---|
| 651 | d.addCallback(self.json_check_lit, self.small, "small+v") |
|---|
| 652 | d.addCallback(lambda ign: |
|---|
| 653 | self.web_json(self.small2, t="check", verify="true")) |
|---|
| 654 | d.addCallback(self.json_check_lit, self.small2, "small2+v") |
|---|
| 655 | d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", verify="true")) |
|---|
| 656 | d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+v") |
|---|
| 657 | d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", verify="true")) |
|---|
| 658 | d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+v") |
|---|
| 659 | |
|---|
| 660 | # check and repair, no verify |
|---|
| 661 | d.addCallback(lambda ign: |
|---|
| 662 | self.web_json(self.root, t="check", repair="true")) |
|---|
| 663 | d.addCallback(self.json_check_and_repair_is_healthy, self.root, "root+r") |
|---|
| 664 | d.addCallback(lambda ign: |
|---|
| 665 | self.web_json(self.mutable, t="check", repair="true")) |
|---|
| 666 | d.addCallback(self.json_check_and_repair_is_healthy, self.mutable, "mutable+r") |
|---|
| 667 | d.addCallback(lambda ign: |
|---|
| 668 | self.web_json(self.large, t="check", repair="true")) |
|---|
| 669 | d.addCallback(self.json_check_and_repair_is_healthy, self.large, "large+r") |
|---|
| 670 | d.addCallback(lambda ign: |
|---|
| 671 | self.web_json(self.small, t="check", repair="true")) |
|---|
| 672 | d.addCallback(self.json_check_lit, self.small, "small+r") |
|---|
| 673 | d.addCallback(lambda ign: |
|---|
| 674 | self.web_json(self.small2, t="check", repair="true")) |
|---|
| 675 | d.addCallback(self.json_check_lit, self.small2, "small2+r") |
|---|
| 676 | d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", repair="true")) |
|---|
| 677 | d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+r") |
|---|
| 678 | d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", repair="true")) |
|---|
| 679 | d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+r") |
|---|
| 680 | |
|---|
| 681 | # check+verify+repair |
|---|
| 682 | d.addCallback(lambda ign: |
|---|
| 683 | self.web_json(self.root, t="check", repair="true", verify="true")) |
|---|
| 684 | d.addCallback(self.json_check_and_repair_is_healthy, self.root, "root+vr") |
|---|
| 685 | d.addCallback(lambda ign: |
|---|
| 686 | self.web_json(self.mutable, t="check", repair="true", verify="true")) |
|---|
| 687 | d.addCallback(self.json_check_and_repair_is_healthy, self.mutable, "mutable+vr") |
|---|
| 688 | d.addCallback(lambda ign: |
|---|
| 689 | self.web_json(self.large, t="check", repair="true", verify="true")) |
|---|
| 690 | d.addCallback(self.json_check_and_repair_is_healthy, self.large, "large+vr", incomplete=True) |
|---|
| 691 | d.addCallback(lambda ign: |
|---|
| 692 | self.web_json(self.small, t="check", repair="true", verify="true")) |
|---|
| 693 | d.addCallback(self.json_check_lit, self.small, "small+vr") |
|---|
| 694 | d.addCallback(lambda ign: |
|---|
| 695 | self.web_json(self.small2, t="check", repair="true", verify="true")) |
|---|
| 696 | d.addCallback(self.json_check_lit, self.small2, "small2+vr") |
|---|
| 697 | d.addCallback(lambda ign: self.web_json(self.empty_lit_dir, t="check", repair="true", verify=True)) |
|---|
| 698 | d.addCallback(self.json_check_lit, self.empty_lit_dir, "empty_lit_dir+vr") |
|---|
| 699 | d.addCallback(lambda ign: self.web_json(self.tiny_lit_dir, t="check", repair="true", verify=True)) |
|---|
| 700 | d.addCallback(self.json_check_lit, self.tiny_lit_dir, "tiny_lit_dir+vr") |
|---|
| 701 | |
|---|
| 702 | # now run a deep-check, with various verify= and repair= flags |
|---|
| 703 | d.addCallback(lambda ign: |
|---|
| 704 | self.slow_web(self.root, t="start-deep-check", output="json")) |
|---|
| 705 | d.addCallback(self.json_full_deepcheck_is_healthy, self.root, "root+d") |
|---|
| 706 | d.addCallback(lambda ign: |
|---|
| 707 | self.slow_web(self.root, t="start-deep-check", verify="true", |
|---|
| 708 | output="json")) |
|---|
| 709 | d.addCallback(self.json_full_deepcheck_is_healthy, self.root, "root+dv") |
|---|
| 710 | d.addCallback(lambda ign: |
|---|
| 711 | self.slow_web(self.root, t="start-deep-check", repair="true", |
|---|
| 712 | output="json")) |
|---|
| 713 | d.addCallback(self.json_full_deepcheck_and_repair_is_healthy, self.root, "root+dr") |
|---|
| 714 | d.addCallback(lambda ign: |
|---|
| 715 | self.slow_web(self.root, t="start-deep-check", verify="true", repair="true", output="json")) |
|---|
| 716 | d.addCallback(self.json_full_deepcheck_and_repair_is_healthy, self.root, "root+dvr") |
|---|
| 717 | |
|---|
| 718 | # now look at t=info |
|---|
| 719 | d.addCallback(lambda ign: self.web(self.root, t="info")) |
|---|
| 720 | # TODO: examine the output |
|---|
| 721 | d.addCallback(lambda ign: self.web(self.mutable, t="info")) |
|---|
| 722 | d.addCallback(lambda ign: self.web(self.large, t="info")) |
|---|
| 723 | d.addCallback(lambda ign: self.web(self.small, t="info")) |
|---|
| 724 | d.addCallback(lambda ign: self.web(self.small2, t="info")) |
|---|
| 725 | d.addCallback(lambda ign: self.web(self.empty_lit_dir, t="info")) |
|---|
| 726 | d.addCallback(lambda ign: self.web(self.tiny_lit_dir, t="info")) |
|---|
| 727 | |
|---|
| 728 | return d |
|---|
| 729 | |
|---|
| 730 | def do_test_cli_good(self, ignored): |
|---|
| 731 | d = defer.succeed(None) |
|---|
| 732 | d.addCallback(lambda ign: self.do_cli_manifest_stream1()) |
|---|
| 733 | d.addCallback(lambda ign: self.do_cli_manifest_stream2()) |
|---|
| 734 | d.addCallback(lambda ign: self.do_cli_manifest_stream3()) |
|---|
| 735 | d.addCallback(lambda ign: self.do_cli_manifest_stream4()) |
|---|
| 736 | d.addCallback(lambda ign: self.do_cli_manifest_stream5()) |
|---|
| 737 | d.addCallback(lambda ign: self.do_cli_stats1()) |
|---|
| 738 | d.addCallback(lambda ign: self.do_cli_stats2()) |
|---|
| 739 | return d |
|---|
| 740 | |
|---|
| 741 | def _check_manifest_storage_index(self, out): |
|---|
| 742 | lines = [l.encode("utf-8") for l in out.split("\n") if l] |
|---|
| 743 | self.failUnlessEqual(len(lines), 3) |
|---|
| 744 | self.failUnless(base32.b2a(self.root.get_storage_index()) in lines) |
|---|
| 745 | self.failUnless(base32.b2a(self.mutable.get_storage_index()) in lines) |
|---|
| 746 | self.failUnless(base32.b2a(self.large.get_storage_index()) in lines) |
|---|
| 747 | |
|---|
| 748 | def do_cli_manifest_stream1(self): |
|---|
| 749 | d = self.do_cli("manifest", self.root_uri) |
|---|
| 750 | def _check(args): |
|---|
| 751 | (rc, out, err) = args |
|---|
| 752 | self.failUnlessEqual(err, "") |
|---|
| 753 | lines = [l for l in out.split("\n") if l] |
|---|
| 754 | self.failUnlessEqual(len(lines), 8) |
|---|
| 755 | caps = {} |
|---|
| 756 | for l in lines: |
|---|
| 757 | try: |
|---|
| 758 | cap, path = l.split(None, 1) |
|---|
| 759 | except ValueError: |
|---|
| 760 | cap = l.strip() |
|---|
| 761 | path = "" |
|---|
| 762 | caps[cap.encode("ascii")] = path |
|---|
| 763 | self.failUnless(self.root.get_uri() in caps) |
|---|
| 764 | self.failUnlessEqual(caps[self.root.get_uri()], "") |
|---|
| 765 | self.failUnlessEqual(caps[self.mutable.get_uri()], "mutable") |
|---|
| 766 | self.failUnlessEqual(caps[self.large.get_uri()], "large") |
|---|
| 767 | self.failUnlessEqual(caps[self.small.get_uri()], "small") |
|---|
| 768 | self.failUnlessEqual(caps[self.small2.get_uri()], "small2") |
|---|
| 769 | self.failUnlessEqual(caps[self.empty_lit_dir.get_uri()], "empty_lit_dir") |
|---|
| 770 | self.failUnlessEqual(caps[self.tiny_lit_dir.get_uri()], "tiny_lit_dir") |
|---|
| 771 | d.addCallback(_check) |
|---|
| 772 | return d |
|---|
| 773 | |
|---|
| 774 | def do_cli_manifest_stream2(self): |
|---|
| 775 | d = self.do_cli("manifest", "--raw", self.root_uri) |
|---|
| 776 | def _check(args): |
|---|
| 777 | (rc, out, err) = args |
|---|
| 778 | self.failUnlessEqual(err, "") |
|---|
| 779 | # this should be the same as the POST t=stream-manifest output |
|---|
| 780 | self._check_streamed_manifest(out) |
|---|
| 781 | d.addCallback(_check) |
|---|
| 782 | return d |
|---|
| 783 | |
|---|
| 784 | def do_cli_manifest_stream3(self): |
|---|
| 785 | d = self.do_cli("manifest", "--storage-index", self.root_uri) |
|---|
| 786 | def _check(args): |
|---|
| 787 | (rc, out, err) = args |
|---|
| 788 | self.failUnlessEqual(err, "") |
|---|
| 789 | self._check_manifest_storage_index(out) |
|---|
| 790 | d.addCallback(_check) |
|---|
| 791 | return d |
|---|
| 792 | |
|---|
| 793 | def do_cli_manifest_stream4(self): |
|---|
| 794 | d = self.do_cli("manifest", "--verify-cap", self.root_uri) |
|---|
| 795 | def _check(args): |
|---|
| 796 | (rc, out, err) = args |
|---|
| 797 | self.failUnlessEqual(err, "") |
|---|
| 798 | lines = [l.encode("utf-8") for l in out.split("\n") if l] |
|---|
| 799 | self.failUnlessEqual(len(lines), 3) |
|---|
| 800 | self.failUnless(self.root.get_verify_cap().to_string() in lines) |
|---|
| 801 | self.failUnless(self.mutable.get_verify_cap().to_string() in lines) |
|---|
| 802 | self.failUnless(self.large.get_verify_cap().to_string() in lines) |
|---|
| 803 | d.addCallback(_check) |
|---|
| 804 | return d |
|---|
| 805 | |
|---|
| 806 | def do_cli_manifest_stream5(self): |
|---|
| 807 | d = self.do_cli("manifest", "--repair-cap", self.root_uri) |
|---|
| 808 | def _check(args): |
|---|
| 809 | (rc, out, err) = args |
|---|
| 810 | self.failUnlessEqual(err, "") |
|---|
| 811 | lines = [l.encode("utf-8") for l in out.split("\n") if l] |
|---|
| 812 | self.failUnlessEqual(len(lines), 3) |
|---|
| 813 | self.failUnless(self.root.get_repair_cap().to_string() in lines) |
|---|
| 814 | self.failUnless(self.mutable.get_repair_cap().to_string() in lines) |
|---|
| 815 | self.failUnless(self.large.get_repair_cap().to_string() in lines) |
|---|
| 816 | d.addCallback(_check) |
|---|
| 817 | return d |
|---|
| 818 | |
|---|
| 819 | def do_cli_stats1(self): |
|---|
| 820 | d = self.do_cli("stats", self.root_uri) |
|---|
| 821 | def _check3(args): |
|---|
| 822 | (rc, out, err) = args |
|---|
| 823 | lines = [l.strip() for l in out.split("\n") if l] |
|---|
| 824 | self.failUnless("count-immutable-files: 1" in lines) |
|---|
| 825 | self.failUnless("count-mutable-files: 1" in lines) |
|---|
| 826 | self.failUnless("count-literal-files: 3" in lines) |
|---|
| 827 | self.failUnless("count-files: 5" in lines) |
|---|
| 828 | self.failUnless("count-directories: 3" in lines) |
|---|
| 829 | self.failUnless("size-immutable-files: 13000 (13.00 kB, 12.70 kiB)" in lines, lines) |
|---|
| 830 | self.failUnless("size-literal-files: 56" in lines, lines) |
|---|
| 831 | self.failUnless(" 4-10 : 1 (10 B, 10 B)".strip() in lines, lines) |
|---|
| 832 | self.failUnless(" 11-31 : 2 (31 B, 31 B)".strip() in lines, lines) |
|---|
| 833 | self.failUnless("10001-31622 : 1 (31.62 kB, 30.88 kiB)".strip() in lines, lines) |
|---|
| 834 | d.addCallback(_check3) |
|---|
| 835 | return d |
|---|
| 836 | |
|---|
| 837 | def do_cli_stats2(self): |
|---|
| 838 | d = self.do_cli("stats", "--raw", self.root_uri) |
|---|
| 839 | def _check4(args): |
|---|
| 840 | (rc, out, err) = args |
|---|
| 841 | data = json.loads(out) |
|---|
| 842 | self.failUnlessEqual(data["count-immutable-files"], 1) |
|---|
| 843 | self.failUnlessEqual(data["count-immutable-files"], 1) |
|---|
| 844 | self.failUnlessEqual(data["count-mutable-files"], 1) |
|---|
| 845 | self.failUnlessEqual(data["count-literal-files"], 3) |
|---|
| 846 | self.failUnlessEqual(data["count-files"], 5) |
|---|
| 847 | self.failUnlessEqual(data["count-directories"], 3) |
|---|
| 848 | self.failUnlessEqual(data["size-immutable-files"], 13000) |
|---|
| 849 | self.failUnlessEqual(data["size-literal-files"], 56) |
|---|
| 850 | self.failUnless([4,10,1] in data["size-files-histogram"]) |
|---|
| 851 | self.failUnless([11,31,2] in data["size-files-histogram"]) |
|---|
| 852 | self.failUnless([10001,31622,1] in data["size-files-histogram"]) |
|---|
| 853 | d.addCallback(_check4) |
|---|
| 854 | return d |
|---|
| 855 | |
|---|
| 856 | |
|---|
| 857 | class DeepCheckWebBad(DeepCheckBase, unittest.TestCase): |
|---|
| 858 | def test_bad(self): |
|---|
| 859 | self.basedir = "deepcheck/DeepCheckWebBad/bad" |
|---|
| 860 | self.set_up_grid() |
|---|
| 861 | d = self.set_up_damaged_tree() |
|---|
| 862 | d.addCallback(self.do_check) |
|---|
| 863 | d.addCallback(self.do_deepcheck) |
|---|
| 864 | d.addCallback(self.do_deepcheck_broken) |
|---|
| 865 | d.addCallback(self.do_test_web_bad) |
|---|
| 866 | d.addErrback(self.explain_web_error) |
|---|
| 867 | d.addErrback(self.explain_error) |
|---|
| 868 | return d |
|---|
| 869 | |
|---|
| 870 | |
|---|
| 871 | |
|---|
| 872 | def set_up_damaged_tree(self): |
|---|
| 873 | # 6.4s |
|---|
| 874 | |
|---|
| 875 | # root |
|---|
| 876 | # mutable-good |
|---|
| 877 | # mutable-missing-shares |
|---|
| 878 | # mutable-corrupt-shares |
|---|
| 879 | # mutable-unrecoverable |
|---|
| 880 | # large-good |
|---|
| 881 | # large-missing-shares |
|---|
| 882 | # large-corrupt-shares |
|---|
| 883 | # large-unrecoverable |
|---|
| 884 | # broken |
|---|
| 885 | # large1-good |
|---|
| 886 | # subdir-good |
|---|
| 887 | # large2-good |
|---|
| 888 | # subdir-unrecoverable |
|---|
| 889 | # large3-good |
|---|
| 890 | |
|---|
| 891 | self.nodes = {} |
|---|
| 892 | |
|---|
| 893 | c0 = self.g.clients[0] |
|---|
| 894 | d = c0.create_dirnode() |
|---|
| 895 | def _created_root(n): |
|---|
| 896 | self.root = n |
|---|
| 897 | self.root_uri = n.get_uri() |
|---|
| 898 | d.addCallback(_created_root) |
|---|
| 899 | d.addCallback(self.create_mangled, "mutable-good") |
|---|
| 900 | d.addCallback(self.create_mangled, "mutable-missing-shares") |
|---|
| 901 | d.addCallback(self.create_mangled, "mutable-corrupt-shares") |
|---|
| 902 | d.addCallback(self.create_mangled, "mutable-unrecoverable") |
|---|
| 903 | d.addCallback(self.create_mangled, "large-good") |
|---|
| 904 | d.addCallback(self.create_mangled, "large-missing-shares") |
|---|
| 905 | d.addCallback(self.create_mangled, "large-corrupt-shares") |
|---|
| 906 | d.addCallback(self.create_mangled, "large-unrecoverable") |
|---|
| 907 | d.addCallback(lambda ignored: c0.create_dirnode()) |
|---|
| 908 | d.addCallback(self._stash_node, "broken") |
|---|
| 909 | large1 = upload.Data(b"Lots of data\n" * 1000 + b"large1" + b"\n", None) |
|---|
| 910 | d.addCallback(lambda ignored: |
|---|
| 911 | self.nodes["broken"].add_file(u"large1", large1)) |
|---|
| 912 | d.addCallback(lambda ignored: |
|---|
| 913 | self.nodes["broken"].create_subdirectory(u"subdir-good")) |
|---|
| 914 | large2 = upload.Data(b"Lots of data\n" * 1000 + b"large2" + b"\n", None) |
|---|
| 915 | d.addCallback(lambda subdir: subdir.add_file(u"large2-good", large2)) |
|---|
| 916 | d.addCallback(lambda ignored: |
|---|
| 917 | self.nodes["broken"].create_subdirectory(u"subdir-unrecoverable")) |
|---|
| 918 | d.addCallback(self._stash_node, "subdir-unrecoverable") |
|---|
| 919 | large3 = upload.Data(b"Lots of data\n" * 1000 + b"large3" + b"\n", None) |
|---|
| 920 | d.addCallback(lambda subdir: subdir.add_file(u"large3-good", large3)) |
|---|
| 921 | d.addCallback(lambda ignored: |
|---|
| 922 | self._delete_most_shares(self.nodes["broken"])) |
|---|
| 923 | return d |
|---|
| 924 | |
|---|
| 925 | def _stash_node(self, node, name): |
|---|
| 926 | self.nodes[name] = node |
|---|
| 927 | return node |
|---|
| 928 | |
|---|
| 929 | def create_mangled(self, ignored, name): |
|---|
| 930 | nodetype, mangletype = name.split("-", 1) |
|---|
| 931 | if nodetype == "mutable": |
|---|
| 932 | mutable_uploadable = MutableData(b"mutable file contents") |
|---|
| 933 | d = self.g.clients[0].create_mutable_file(mutable_uploadable) |
|---|
| 934 | d.addCallback(lambda n: self.root.set_node(str(name), n)) # TODO drop str() once strings are unicode |
|---|
| 935 | elif nodetype == "large": |
|---|
| 936 | large = upload.Data(b"Lots of data\n" * 1000 + name.encode("ascii") + b"\n", None) |
|---|
| 937 | d = self.root.add_file(str(name), large) |
|---|
| 938 | elif nodetype == "small": |
|---|
| 939 | small = upload.Data(b"Small enough for a LIT", None) |
|---|
| 940 | d = self.root.add_file(str(name), small) |
|---|
| 941 | |
|---|
| 942 | d.addCallback(self._stash_node, name) |
|---|
| 943 | |
|---|
| 944 | if mangletype == "good": |
|---|
| 945 | pass |
|---|
| 946 | elif mangletype == "missing-shares": |
|---|
| 947 | d.addCallback(self._delete_some_shares) |
|---|
| 948 | elif mangletype == "corrupt-shares": |
|---|
| 949 | d.addCallback(self._corrupt_some_shares) |
|---|
| 950 | else: |
|---|
| 951 | assert mangletype == "unrecoverable" |
|---|
| 952 | d.addCallback(self._delete_most_shares) |
|---|
| 953 | |
|---|
| 954 | return d |
|---|
| 955 | |
|---|
| 956 | def _delete_some_shares(self, node): |
|---|
| 957 | self.delete_shares_numbered(node.get_uri(), [0,1]) |
|---|
| 958 | |
|---|
| 959 | @defer.inlineCallbacks |
|---|
| 960 | def _corrupt_some_shares(self, node): |
|---|
| 961 | for (shnum, serverid, sharefile) in self.find_uri_shares(node.get_uri()): |
|---|
| 962 | if shnum in (0,1): |
|---|
| 963 | yield run_cli("debug", "corrupt-share", sharefile) |
|---|
| 964 | |
|---|
| 965 | def _delete_most_shares(self, node): |
|---|
| 966 | self.delete_shares_numbered(node.get_uri(), list(range(1,10))) |
|---|
| 967 | |
|---|
| 968 | |
|---|
| 969 | def check_is_healthy(self, cr, where): |
|---|
| 970 | try: |
|---|
| 971 | self.failUnless(ICheckResults.providedBy(cr), (cr, type(cr), where)) |
|---|
| 972 | self.failUnless(cr.is_healthy(), (cr.get_report(), cr.is_healthy(), cr.get_summary(), where)) |
|---|
| 973 | self.failUnless(cr.is_recoverable(), where) |
|---|
| 974 | self.failUnlessEqual(cr.get_version_counter_recoverable(), 1, where) |
|---|
| 975 | self.failUnlessEqual(cr.get_version_counter_unrecoverable(), 0, where) |
|---|
| 976 | return cr |
|---|
| 977 | except Exception as le: |
|---|
| 978 | le.args = tuple(le.args + (where,)) |
|---|
| 979 | raise |
|---|
| 980 | |
|---|
| 981 | def check_is_missing_shares(self, cr, where): |
|---|
| 982 | self.failUnless(ICheckResults.providedBy(cr), where) |
|---|
| 983 | self.failIf(cr.is_healthy(), where) |
|---|
| 984 | self.failUnless(cr.is_recoverable(), where) |
|---|
| 985 | self.failUnlessEqual(cr.get_version_counter_recoverable(), 1, where) |
|---|
| 986 | self.failUnlessEqual(cr.get_version_counter_unrecoverable(), 0, where) |
|---|
| 987 | return cr |
|---|
| 988 | |
|---|
| 989 | def check_has_corrupt_shares(self, cr, where): |
|---|
| 990 | # by "corrupt-shares" we mean the file is still recoverable |
|---|
| 991 | self.failUnless(ICheckResults.providedBy(cr), where) |
|---|
| 992 | self.failIf(cr.is_healthy(), (where, cr)) |
|---|
| 993 | self.failUnless(cr.is_recoverable(), where) |
|---|
| 994 | self.failUnless(cr.get_share_counter_good() < 10, where) |
|---|
| 995 | self.failUnless(cr.get_corrupt_shares(), where) |
|---|
| 996 | return cr |
|---|
| 997 | |
|---|
| 998 | def check_is_unrecoverable(self, cr, where): |
|---|
| 999 | self.failUnless(ICheckResults.providedBy(cr), where) |
|---|
| 1000 | self.failIf(cr.is_healthy(), where) |
|---|
| 1001 | self.failIf(cr.is_recoverable(), where) |
|---|
| 1002 | self.failUnless(cr.get_share_counter_good() < cr.get_encoding_needed(), |
|---|
| 1003 | (cr.get_share_counter_good(), cr.get_encoding_needed(), |
|---|
| 1004 | where)) |
|---|
| 1005 | self.failUnlessEqual(cr.get_version_counter_recoverable(), 0, where) |
|---|
| 1006 | self.failUnlessEqual(cr.get_version_counter_unrecoverable(), 1, where) |
|---|
| 1007 | return cr |
|---|
| 1008 | |
|---|
| 1009 | def do_check(self, ignored): |
|---|
| 1010 | d = defer.succeed(None) |
|---|
| 1011 | |
|---|
| 1012 | # check the individual items, without verification. This will not |
|---|
| 1013 | # detect corrupt shares. |
|---|
| 1014 | def _check(which, checker): |
|---|
| 1015 | d = self.nodes[which].check(Monitor()) |
|---|
| 1016 | d.addCallback(checker, which + "--check") |
|---|
| 1017 | return d |
|---|
| 1018 | |
|---|
| 1019 | d.addCallback(lambda ign: _check("mutable-good", self.check_is_healthy)) |
|---|
| 1020 | d.addCallback(lambda ign: _check("mutable-missing-shares", |
|---|
| 1021 | self.check_is_missing_shares)) |
|---|
| 1022 | d.addCallback(lambda ign: _check("mutable-corrupt-shares", |
|---|
| 1023 | self.check_is_healthy)) |
|---|
| 1024 | d.addCallback(lambda ign: _check("mutable-unrecoverable", |
|---|
| 1025 | self.check_is_unrecoverable)) |
|---|
| 1026 | d.addCallback(lambda ign: _check("large-good", self.check_is_healthy)) |
|---|
| 1027 | d.addCallback(lambda ign: _check("large-missing-shares", |
|---|
| 1028 | self.check_is_missing_shares)) |
|---|
| 1029 | d.addCallback(lambda ign: _check("large-corrupt-shares", |
|---|
| 1030 | self.check_is_healthy)) |
|---|
| 1031 | d.addCallback(lambda ign: _check("large-unrecoverable", |
|---|
| 1032 | self.check_is_unrecoverable)) |
|---|
| 1033 | |
|---|
| 1034 | # and again with verify=True, which *does* detect corrupt shares. |
|---|
| 1035 | def _checkv(which, checker): |
|---|
| 1036 | d = self.nodes[which].check(Monitor(), verify=True) |
|---|
| 1037 | d.addCallback(checker, which + "--check-and-verify") |
|---|
| 1038 | return d |
|---|
| 1039 | |
|---|
| 1040 | d.addCallback(lambda ign: _checkv("mutable-good", self.check_is_healthy)) |
|---|
| 1041 | d.addCallback(lambda ign: _checkv("mutable-missing-shares", |
|---|
| 1042 | self.check_is_missing_shares)) |
|---|
| 1043 | d.addCallback(lambda ign: _checkv("mutable-corrupt-shares", |
|---|
| 1044 | self.check_has_corrupt_shares)) |
|---|
| 1045 | d.addCallback(lambda ign: _checkv("mutable-unrecoverable", |
|---|
| 1046 | self.check_is_unrecoverable)) |
|---|
| 1047 | d.addCallback(lambda ign: _checkv("large-good", self.check_is_healthy)) |
|---|
| 1048 | d.addCallback(lambda ign: _checkv("large-missing-shares", self.check_is_missing_shares)) |
|---|
| 1049 | d.addCallback(lambda ign: _checkv("large-corrupt-shares", self.check_has_corrupt_shares)) |
|---|
| 1050 | d.addCallback(lambda ign: _checkv("large-unrecoverable", |
|---|
| 1051 | self.check_is_unrecoverable)) |
|---|
| 1052 | |
|---|
| 1053 | return d |
|---|
| 1054 | |
|---|
| 1055 | def do_deepcheck(self, ignored): |
|---|
| 1056 | d = defer.succeed(None) |
|---|
| 1057 | |
|---|
| 1058 | # now deep-check the root, with various verify= and repair= options |
|---|
| 1059 | d.addCallback(lambda ign: |
|---|
| 1060 | self.root.start_deep_check().when_done()) |
|---|
| 1061 | def _check1(cr): |
|---|
| 1062 | self.failUnless(IDeepCheckResults.providedBy(cr)) |
|---|
| 1063 | c = cr.get_counters() |
|---|
| 1064 | self.failUnlessEqual(c["count-objects-checked"], 9) |
|---|
| 1065 | self.failUnlessEqual(c["count-objects-healthy"], 5) |
|---|
| 1066 | self.failUnlessEqual(c["count-objects-unhealthy"], 4) |
|---|
| 1067 | self.failUnlessEqual(c["count-objects-unrecoverable"], 2) |
|---|
| 1068 | d.addCallback(_check1) |
|---|
| 1069 | |
|---|
| 1070 | d.addCallback(lambda ign: |
|---|
| 1071 | self.root.start_deep_check(verify=True).when_done()) |
|---|
| 1072 | def _check2(cr): |
|---|
| 1073 | self.failUnless(IDeepCheckResults.providedBy(cr)) |
|---|
| 1074 | c = cr.get_counters() |
|---|
| 1075 | self.failUnlessEqual(c["count-objects-checked"], 9) |
|---|
| 1076 | self.failUnlessEqual(c["count-objects-healthy"], 3) |
|---|
| 1077 | self.failUnlessEqual(c["count-objects-unhealthy"], 6) |
|---|
| 1078 | self.failUnlessEqual(c["count-objects-healthy"], 3) # root, mutable good, large good |
|---|
| 1079 | self.failUnlessEqual(c["count-objects-unrecoverable"], 2) # mutable unrecoverable, large unrecoverable |
|---|
| 1080 | d.addCallback(_check2) |
|---|
| 1081 | |
|---|
| 1082 | return d |
|---|
| 1083 | |
|---|
| 1084 | def do_deepcheck_broken(self, ignored): |
|---|
| 1085 | # deep-check on the broken directory should fail, because of the |
|---|
| 1086 | # untraversable subdir |
|---|
| 1087 | def _do_deep_check(): |
|---|
| 1088 | return self.nodes["broken"].start_deep_check().when_done() |
|---|
| 1089 | d = self.shouldFail(UnrecoverableFileError, "do_deep_check", |
|---|
| 1090 | "no recoverable versions", |
|---|
| 1091 | _do_deep_check) |
|---|
| 1092 | return d |
|---|
| 1093 | |
|---|
| 1094 | def json_is_healthy(self, data, where): |
|---|
| 1095 | r = data["results"] |
|---|
| 1096 | self.failUnless(r["healthy"], where) |
|---|
| 1097 | self.failUnless(r["recoverable"], where) |
|---|
| 1098 | self.failUnlessEqual(r["count-recoverable-versions"], 1, where) |
|---|
| 1099 | self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where) |
|---|
| 1100 | |
|---|
| 1101 | def json_is_missing_shares(self, data, where): |
|---|
| 1102 | r = data["results"] |
|---|
| 1103 | self.failIf(r["healthy"], where) |
|---|
| 1104 | self.failUnless(r["recoverable"], where) |
|---|
| 1105 | self.failUnlessEqual(r["count-recoverable-versions"], 1, where) |
|---|
| 1106 | self.failUnlessEqual(r["count-unrecoverable-versions"], 0, where) |
|---|
| 1107 | |
|---|
| 1108 | def json_has_corrupt_shares(self, data, where): |
|---|
| 1109 | # by "corrupt-shares" we mean the file is still recoverable |
|---|
| 1110 | r = data["results"] |
|---|
| 1111 | self.failIf(r["healthy"], where) |
|---|
| 1112 | self.failUnless(r["recoverable"], where) |
|---|
| 1113 | self.failUnless(r["count-shares-good"] < 10, where) |
|---|
| 1114 | self.failUnless(r["count-corrupt-shares"], where) |
|---|
| 1115 | self.failUnless(r["list-corrupt-shares"], where) |
|---|
| 1116 | |
|---|
| 1117 | def json_is_unrecoverable(self, data, where): |
|---|
| 1118 | r = data["results"] |
|---|
| 1119 | self.failIf(r["healthy"], where) |
|---|
| 1120 | self.failIf(r["recoverable"], where) |
|---|
| 1121 | self.failUnless(r["count-shares-good"] < r["count-shares-needed"], |
|---|
| 1122 | where) |
|---|
| 1123 | self.failUnlessEqual(r["count-recoverable-versions"], 0, where) |
|---|
| 1124 | self.failUnlessEqual(r["count-unrecoverable-versions"], 1, where) |
|---|
| 1125 | |
|---|
| 1126 | def do_test_web_bad(self, ignored): |
|---|
| 1127 | d = defer.succeed(None) |
|---|
| 1128 | |
|---|
| 1129 | # check, no verify |
|---|
| 1130 | def _check(which, checker): |
|---|
| 1131 | d = self.web_json(self.nodes[which], t="check") |
|---|
| 1132 | d.addCallback(checker, which + "--webcheck") |
|---|
| 1133 | return d |
|---|
| 1134 | |
|---|
| 1135 | d.addCallback(lambda ign: _check("mutable-good", |
|---|
| 1136 | self.json_is_healthy)) |
|---|
| 1137 | d.addCallback(lambda ign: _check("mutable-missing-shares", |
|---|
| 1138 | self.json_is_missing_shares)) |
|---|
| 1139 | d.addCallback(lambda ign: _check("mutable-corrupt-shares", |
|---|
| 1140 | self.json_is_healthy)) |
|---|
| 1141 | d.addCallback(lambda ign: _check("mutable-unrecoverable", |
|---|
| 1142 | self.json_is_unrecoverable)) |
|---|
| 1143 | d.addCallback(lambda ign: _check("large-good", |
|---|
| 1144 | self.json_is_healthy)) |
|---|
| 1145 | d.addCallback(lambda ign: _check("large-missing-shares", |
|---|
| 1146 | self.json_is_missing_shares)) |
|---|
| 1147 | d.addCallback(lambda ign: _check("large-corrupt-shares", |
|---|
| 1148 | self.json_is_healthy)) |
|---|
| 1149 | d.addCallback(lambda ign: _check("large-unrecoverable", |
|---|
| 1150 | self.json_is_unrecoverable)) |
|---|
| 1151 | |
|---|
| 1152 | # check and verify |
|---|
| 1153 | def _checkv(which, checker): |
|---|
| 1154 | d = self.web_json(self.nodes[which], t="check", verify="true") |
|---|
| 1155 | d.addCallback(checker, which + "--webcheck-and-verify") |
|---|
| 1156 | return d |
|---|
| 1157 | |
|---|
| 1158 | d.addCallback(lambda ign: _checkv("mutable-good", |
|---|
| 1159 | self.json_is_healthy)) |
|---|
| 1160 | d.addCallback(lambda ign: _checkv("mutable-missing-shares", |
|---|
| 1161 | self.json_is_missing_shares)) |
|---|
| 1162 | d.addCallback(lambda ign: _checkv("mutable-corrupt-shares", |
|---|
| 1163 | self.json_has_corrupt_shares)) |
|---|
| 1164 | d.addCallback(lambda ign: _checkv("mutable-unrecoverable", |
|---|
| 1165 | self.json_is_unrecoverable)) |
|---|
| 1166 | d.addCallback(lambda ign: _checkv("large-good", |
|---|
| 1167 | self.json_is_healthy)) |
|---|
| 1168 | d.addCallback(lambda ign: _checkv("large-missing-shares", self.json_is_missing_shares)) |
|---|
| 1169 | d.addCallback(lambda ign: _checkv("large-corrupt-shares", self.json_has_corrupt_shares)) |
|---|
| 1170 | d.addCallback(lambda ign: _checkv("large-unrecoverable", |
|---|
| 1171 | self.json_is_unrecoverable)) |
|---|
| 1172 | |
|---|
| 1173 | return d |
|---|
| 1174 | |
|---|
| 1175 | class Large(DeepCheckBase, unittest.TestCase): |
|---|
| 1176 | def test_lots_of_lits(self): |
|---|
| 1177 | self.basedir = "deepcheck/Large/lots_of_lits" |
|---|
| 1178 | self.set_up_grid() |
|---|
| 1179 | # create the following directory structure: |
|---|
| 1180 | # root/ |
|---|
| 1181 | # subdir/ |
|---|
| 1182 | # 000-large (CHK) |
|---|
| 1183 | # 001-small (LIT) |
|---|
| 1184 | # 002-small |
|---|
| 1185 | # ... |
|---|
| 1186 | # 399-small |
|---|
| 1187 | # then do a deepcheck and make sure it doesn't cause a |
|---|
| 1188 | # Deferred-tail-recursion stack overflow |
|---|
| 1189 | |
|---|
| 1190 | COUNT = 400 |
|---|
| 1191 | c0 = self.g.clients[0] |
|---|
| 1192 | d = c0.create_dirnode() |
|---|
| 1193 | self.stash = {} |
|---|
| 1194 | def _created_root(n): |
|---|
| 1195 | self.root = n |
|---|
| 1196 | return n |
|---|
| 1197 | d.addCallback(_created_root) |
|---|
| 1198 | d.addCallback(lambda root: root.create_subdirectory(u"subdir")) |
|---|
| 1199 | def _add_children(subdir_node): |
|---|
| 1200 | self.subdir_node = subdir_node |
|---|
| 1201 | kids = {} |
|---|
| 1202 | for i in range(1, COUNT): |
|---|
| 1203 | litcap = LiteralFileURI(b"%03d-data" % i).to_string() |
|---|
| 1204 | kids[u"%03d-small" % i] = (litcap, litcap) |
|---|
| 1205 | return subdir_node.set_children(kids) |
|---|
| 1206 | d.addCallback(_add_children) |
|---|
| 1207 | up = upload.Data(b"large enough for CHK" * 100, b"") |
|---|
| 1208 | d.addCallback(lambda ign: self.subdir_node.add_file(u"0000-large", up)) |
|---|
| 1209 | |
|---|
| 1210 | def _start_deepcheck(ignored): |
|---|
| 1211 | return self.web(self.root, method="POST", t="stream-deep-check") |
|---|
| 1212 | d.addCallback(_start_deepcheck) |
|---|
| 1213 | def _check(output_and_url): |
|---|
| 1214 | (output, url) = output_and_url |
|---|
| 1215 | units = list(self.parse_streamed_json(output)) |
|---|
| 1216 | self.failUnlessEqual(len(units), 2+COUNT+1) |
|---|
| 1217 | d.addCallback(_check) |
|---|
| 1218 | |
|---|
| 1219 | return d |
|---|