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.
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
["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):
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.
"dump-cap": dump_cap,
"find-shares": find_shares,
"catalog-shares": catalog_shares,
+ "corrupt-share": corrupt_share,
"repl": repl,
}
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
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
+
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
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