mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-19 13:07:56 +00:00
CLI: add 'tahoe debug corrupt-share', and use it for deep-verify tests, and fix non-deep web checker API to pass verify=true into node
This commit is contained in:
parent
c80e352951
commit
014c9b5969
@ -356,3 +356,7 @@ data for this file.
|
||||
Tahoe packages and modules are available on sys.path (e.g. by using 'import
|
||||
allmydata'). This is most useful from a source tree: it simply sets the
|
||||
PYTHONPATH correctly and runs the 'python' executable.
|
||||
|
||||
"tahoe debug corrupt-share SHAREFILE" will flip a bit in the given sharefile.
|
||||
This can be used to test the client-side verification/repair code. Obviously
|
||||
this command should not be used during normal operation.
|
||||
|
@ -619,6 +619,83 @@ def catalog_shares(options):
|
||||
describe_share(abs_sharefile, si_s, shnum_s, now, out)
|
||||
return 0
|
||||
|
||||
class CorruptShareOptions(usage.Options):
|
||||
def getSynopsis(self):
|
||||
return "Usage: tahoe debug corrupt-share SHARE_FILENAME"
|
||||
|
||||
optParameters = [
|
||||
["offset", "o", "block-random", "Which bit to flip."],
|
||||
]
|
||||
|
||||
def getUsage(self, width=None):
|
||||
t = usage.Options.getUsage(self, width)
|
||||
t += """
|
||||
Corrupt the given share by flipping a bit. This will cause a
|
||||
verifying/downloading client to log an integrity-check failure incident, and
|
||||
downloads will proceed with a different share.
|
||||
|
||||
The --offset parameter controls which bit should be flipped. The default is
|
||||
to flip a single random bit of the block data.
|
||||
|
||||
tahoe debug corrupt-share testgrid/node-3/storage/shares/4v/4vozh77tsrw7mdhnj7qvp5ky74/0
|
||||
|
||||
Obviously, this command should not be used in normal operation.
|
||||
"""
|
||||
return t
|
||||
def parseArgs(self, filename):
|
||||
self['filename'] = filename
|
||||
|
||||
def corrupt_share(options):
|
||||
import random
|
||||
from allmydata import storage
|
||||
from allmydata.mutable.layout import unpack_header
|
||||
out = options.stdout
|
||||
fn = options['filename']
|
||||
assert options["offset"] == "block-random", "other offsets not implemented"
|
||||
# first, what kind of share is it?
|
||||
|
||||
def flip_bit(start, end):
|
||||
offset = random.randrange(start, end)
|
||||
bit = random.randrange(0, 8)
|
||||
print >>out, "[%d..%d): %d.b%d" % (start, end, offset, bit)
|
||||
f = open(fn, "rb+")
|
||||
f.seek(offset)
|
||||
d = f.read(1)
|
||||
d = chr(ord(d) ^ 0x01)
|
||||
f.seek(offset)
|
||||
f.write(d)
|
||||
f.close()
|
||||
|
||||
f = open(fn, "rb")
|
||||
prefix = f.read(32)
|
||||
f.close()
|
||||
if prefix == storage.MutableShareFile.MAGIC:
|
||||
# mutable
|
||||
m = storage.MutableShareFile(fn)
|
||||
f = open(fn, "rb")
|
||||
f.seek(m.DATA_OFFSET)
|
||||
data = f.read(2000)
|
||||
# make sure this slot contains an SMDF share
|
||||
assert data[0] == "\x00", "non-SDMF mutable shares not supported"
|
||||
f.close()
|
||||
|
||||
(version, ig_seqnum, ig_roothash, ig_IV, ig_k, ig_N, ig_segsize,
|
||||
ig_datalen, offsets) = unpack_header(data)
|
||||
|
||||
assert version == 0, "we only handle v0 SDMF files"
|
||||
start = m.DATA_OFFSET + offsets["share_data"]
|
||||
end = m.DATA_OFFSET + offsets["enc_privkey"]
|
||||
flip_bit(start, end)
|
||||
else:
|
||||
# otherwise assume it's immutable
|
||||
f = storage.ShareFile(fn)
|
||||
bp = storage.ReadBucketProxy(None)
|
||||
offsets = bp._parse_offsets(f.read_share_data(0, 0x24))
|
||||
start = f._data_offset + offsets["data"]
|
||||
end = f._data_offset + offsets["plaintext_hash_tree"]
|
||||
flip_bit(start, end)
|
||||
|
||||
|
||||
|
||||
class ReplOptions(usage.Options):
|
||||
pass
|
||||
@ -635,6 +712,7 @@ class DebugCommand(usage.Options):
|
||||
["dump-cap", None, DumpCapOptions, "Unpack a read-cap or write-cap"],
|
||||
["find-shares", None, FindSharesOptions, "Locate sharefiles in node dirs"],
|
||||
["catalog-shares", None, CatalogSharesOptions, "Describe shares in node dirs"],
|
||||
["corrupt-share", None, CorruptShareOptions, "Corrupt a share"],
|
||||
["repl", None, ReplOptions, "Open a python interpreter"],
|
||||
]
|
||||
def postOptions(self):
|
||||
@ -650,6 +728,7 @@ Subcommands:
|
||||
tahoe debug dump-cap Unpack a read-cap or write-cap
|
||||
tahoe debug find-shares Locate sharefiles in node directories
|
||||
tahoe debug catalog-shares Describe all shares in node dirs
|
||||
tahoe debug corrupt-share Corrupt a share by flipping a bit.
|
||||
|
||||
Please run e.g. 'tahoe debug dump-share --help' for more details on each
|
||||
subcommand.
|
||||
@ -661,6 +740,7 @@ subDispatch = {
|
||||
"dump-cap": dump_cap,
|
||||
"find-shares": find_shares,
|
||||
"catalog-shares": catalog_shares,
|
||||
"corrupt-share": corrupt_share,
|
||||
"repl": repl,
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from base64 import b32encode
|
||||
import os, random, struct, sys, time, re, simplejson
|
||||
import os, random, struct, sys, time, re, simplejson, urllib
|
||||
from cStringIO import StringIO
|
||||
from twisted.trial import unittest
|
||||
from twisted.internet import defer
|
||||
@ -1851,3 +1851,71 @@ class ImmutableChecker(ShareManglingMixin, unittest.TestCase):
|
||||
d.addCallback(_check2)
|
||||
return d
|
||||
test_check_with_verify.todo = "We haven't implemented a verifier this thorough yet."
|
||||
|
||||
class MutableChecker(SystemTestMixin, unittest.TestCase):
|
||||
|
||||
def _run_cli(self, argv):
|
||||
stdout, stderr = StringIO(), StringIO()
|
||||
runner.runner(argv, run_by_human=False, stdout=stdout, stderr=stderr)
|
||||
return stdout.getvalue()
|
||||
|
||||
def test_good(self):
|
||||
self.basedir = self.mktemp()
|
||||
d = self.set_up_nodes()
|
||||
CONTENTS = "a little bit of data"
|
||||
d.addCallback(lambda res: self.clients[0].create_mutable_file(CONTENTS))
|
||||
def _created(node):
|
||||
self.node = node
|
||||
si = self.node.get_storage_index()
|
||||
d.addCallback(_created)
|
||||
# now make sure the webapi verifier sees no problems
|
||||
def _do_check(res):
|
||||
url = (self.webish_url +
|
||||
"uri/%s" % urllib.quote(self.node.get_uri()) +
|
||||
"?t=check&verify=true")
|
||||
return getPage(url, method="POST")
|
||||
d.addCallback(_do_check)
|
||||
def _got_results(out):
|
||||
self.failUnless("<pre>Healthy!" in out, out)
|
||||
self.failIf("Not Healthy!" in out, out)
|
||||
self.failIf("Unhealthy" in out, out)
|
||||
self.failIf("Corrupt Shares" in out, out)
|
||||
d.addCallback(_got_results)
|
||||
return d
|
||||
|
||||
def test_corrupt(self):
|
||||
self.basedir = self.mktemp()
|
||||
d = self.set_up_nodes()
|
||||
CONTENTS = "a little bit of data"
|
||||
d.addCallback(lambda res: self.clients[0].create_mutable_file(CONTENTS))
|
||||
def _created(node):
|
||||
self.node = node
|
||||
si = self.node.get_storage_index()
|
||||
out = self._run_cli(["debug", "find-shares", base32.b2a(si),
|
||||
self.clients[1].basedir])
|
||||
files = out.split("\n")
|
||||
# corrupt one of them, using the CLI debug command
|
||||
f = files[0]
|
||||
shnum = os.path.basename(f)
|
||||
nodeid = self.clients[1].nodeid
|
||||
nodeid_prefix = idlib.shortnodeid_b2a(nodeid)
|
||||
self.corrupt_shareid = "%s-sh%s" % (nodeid_prefix, shnum)
|
||||
out = self._run_cli(["debug", "corrupt-share", files[0]])
|
||||
d.addCallback(_created)
|
||||
# now make sure the webapi verifier notices it
|
||||
def _do_check(res):
|
||||
url = (self.webish_url +
|
||||
"uri/%s" % urllib.quote(self.node.get_uri()) +
|
||||
"?t=check&verify=true")
|
||||
return getPage(url, method="POST")
|
||||
d.addCallback(_do_check)
|
||||
def _got_results(out):
|
||||
self.failUnless("Not Healthy!" in out, out)
|
||||
self.failUnless("Unhealthy: best recoverable version has only 9 shares (encoding is 3-of-10)" in out, out)
|
||||
shid_re = (r"Corrupt Shares:\s+%s: block hash tree failure" %
|
||||
self.corrupt_shareid)
|
||||
self.failUnless(re.search(shid_re, out), out)
|
||||
|
||||
d.addCallback(_got_results)
|
||||
return d
|
||||
|
||||
|
@ -339,8 +339,7 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
def _POST_deep_check(self, req):
|
||||
# check this directory and everything reachable from it
|
||||
verify = boolean_of_arg(get_arg(req, "verify", "false"))
|
||||
#repair = boolean_of_arg(get_arg(req, "repair", "false"))
|
||||
repair = False # make sure it works first
|
||||
repair = boolean_of_arg(get_arg(req, "repair", "false"))
|
||||
d = self.node.deep_check(verify, repair)
|
||||
d.addCallback(lambda res: DeepCheckResults(res))
|
||||
return d
|
||||
|
@ -255,7 +255,9 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
return d
|
||||
|
||||
def _POST_check(self, req):
|
||||
d = self.node.check()
|
||||
verify = boolean_of_arg(get_arg(req, "verify", "false"))
|
||||
repair = boolean_of_arg(get_arg(req, "repair", "false"))
|
||||
d = self.node.check(verify, repair)
|
||||
d.addCallback(lambda res: CheckerResults(res))
|
||||
return d
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user