mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-24 15:16:41 +00:00
CLI: modify 'tahoe manifest' and 'tahoe deep-check' to report ERROR: properly. For #590.
This commit is contained in:
parent
fd4ceb6a87
commit
a3c1fe35d9
@ -89,16 +89,28 @@ class FakeTransport:
|
|||||||
|
|
||||||
class DeepCheckOutput(LineOnlyReceiver):
|
class DeepCheckOutput(LineOnlyReceiver):
|
||||||
delimiter = "\n"
|
delimiter = "\n"
|
||||||
def __init__(self, options):
|
def __init__(self, streamer, options):
|
||||||
|
self.streamer = streamer
|
||||||
self.transport = FakeTransport()
|
self.transport = FakeTransport()
|
||||||
|
|
||||||
self.verbose = bool(options["verbose"])
|
self.verbose = bool(options["verbose"])
|
||||||
self.stdout = options.stdout
|
self.stdout = options.stdout
|
||||||
|
self.stderr = options.stderr
|
||||||
self.num_objects = 0
|
self.num_objects = 0
|
||||||
self.files_healthy = 0
|
self.files_healthy = 0
|
||||||
self.files_unhealthy = 0
|
self.files_unhealthy = 0
|
||||||
|
self.in_error = False
|
||||||
|
|
||||||
def lineReceived(self, line):
|
def lineReceived(self, line):
|
||||||
|
if self.in_error:
|
||||||
|
print >>self.stderr, line
|
||||||
|
return
|
||||||
|
if line.startswith("ERROR:"):
|
||||||
|
self.in_error = True
|
||||||
|
self.streamer.rc = 1
|
||||||
|
print >>self.stderr, line
|
||||||
|
return
|
||||||
|
|
||||||
d = simplejson.loads(line)
|
d = simplejson.loads(line)
|
||||||
stdout = self.stdout
|
stdout = self.stdout
|
||||||
if d["type"] not in ("file", "directory"):
|
if d["type"] not in ("file", "directory"):
|
||||||
@ -131,17 +143,21 @@ class DeepCheckOutput(LineOnlyReceiver):
|
|||||||
(serverid, storage_index, sharenum)
|
(serverid, storage_index, sharenum)
|
||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
|
if self.in_error:
|
||||||
|
return
|
||||||
stdout = self.stdout
|
stdout = self.stdout
|
||||||
print >>stdout, "done: %d objects checked, %d healthy, %d unhealthy" \
|
print >>stdout, "done: %d objects checked, %d healthy, %d unhealthy" \
|
||||||
% (self.num_objects, self.files_healthy, self.files_unhealthy)
|
% (self.num_objects, self.files_healthy, self.files_unhealthy)
|
||||||
|
|
||||||
class DeepCheckAndRepairOutput(LineOnlyReceiver):
|
class DeepCheckAndRepairOutput(LineOnlyReceiver):
|
||||||
delimiter = "\n"
|
delimiter = "\n"
|
||||||
def __init__(self, options):
|
def __init__(self, streamer, options):
|
||||||
|
self.streamer = streamer
|
||||||
self.transport = FakeTransport()
|
self.transport = FakeTransport()
|
||||||
|
|
||||||
self.verbose = bool(options["verbose"])
|
self.verbose = bool(options["verbose"])
|
||||||
self.stdout = options.stdout
|
self.stdout = options.stdout
|
||||||
|
self.stderr = options.stderr
|
||||||
self.num_objects = 0
|
self.num_objects = 0
|
||||||
self.pre_repair_files_healthy = 0
|
self.pre_repair_files_healthy = 0
|
||||||
self.pre_repair_files_unhealthy = 0
|
self.pre_repair_files_unhealthy = 0
|
||||||
@ -149,8 +165,18 @@ class DeepCheckAndRepairOutput(LineOnlyReceiver):
|
|||||||
self.repairs_successful = 0
|
self.repairs_successful = 0
|
||||||
self.post_repair_files_healthy = 0
|
self.post_repair_files_healthy = 0
|
||||||
self.post_repair_files_unhealthy = 0
|
self.post_repair_files_unhealthy = 0
|
||||||
|
self.in_error = False
|
||||||
|
|
||||||
def lineReceived(self, line):
|
def lineReceived(self, line):
|
||||||
|
if self.in_error:
|
||||||
|
print >>self.stderr, line
|
||||||
|
return
|
||||||
|
if line.startswith("ERROR:"):
|
||||||
|
self.in_error = True
|
||||||
|
self.streamer.rc = 1
|
||||||
|
print >>self.stderr, line
|
||||||
|
return
|
||||||
|
|
||||||
d = simplejson.loads(line)
|
d = simplejson.loads(line)
|
||||||
stdout = self.stdout
|
stdout = self.stdout
|
||||||
if d["type"] not in ("file", "directory"):
|
if d["type"] not in ("file", "directory"):
|
||||||
@ -211,6 +237,8 @@ class DeepCheckAndRepairOutput(LineOnlyReceiver):
|
|||||||
print >>stdout, " repair failed"
|
print >>stdout, " repair failed"
|
||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
|
if self.in_error:
|
||||||
|
return
|
||||||
stdout = self.stdout
|
stdout = self.stdout
|
||||||
print >>stdout, "done: %d objects checked" % self.num_objects
|
print >>stdout, "done: %d objects checked" % self.num_objects
|
||||||
print >>stdout, " pre-repair: %d healthy, %d unhealthy" \
|
print >>stdout, " pre-repair: %d healthy, %d unhealthy" \
|
||||||
@ -229,6 +257,7 @@ class DeepCheckStreamer(LineOnlyReceiver):
|
|||||||
def run(self, options):
|
def run(self, options):
|
||||||
stdout = options.stdout
|
stdout = options.stdout
|
||||||
stderr = options.stderr
|
stderr = options.stderr
|
||||||
|
self.rc = 0
|
||||||
self.options = options
|
self.options = options
|
||||||
nodeurl = options['node-url']
|
nodeurl = options['node-url']
|
||||||
if not nodeurl.endswith("/"):
|
if not nodeurl.endswith("/"):
|
||||||
@ -247,9 +276,9 @@ class DeepCheckStreamer(LineOnlyReceiver):
|
|||||||
url += "&verify=true"
|
url += "&verify=true"
|
||||||
if options["repair"]:
|
if options["repair"]:
|
||||||
url += "&repair=true"
|
url += "&repair=true"
|
||||||
output = DeepCheckAndRepairOutput(options)
|
output = DeepCheckAndRepairOutput(self, options)
|
||||||
else:
|
else:
|
||||||
output = DeepCheckOutput(options)
|
output = DeepCheckOutput(self, options)
|
||||||
if options["add-lease"]:
|
if options["add-lease"]:
|
||||||
url += "&add-lease=true"
|
url += "&add-lease=true"
|
||||||
resp = do_http("POST", url)
|
resp = do_http("POST", url)
|
||||||
@ -268,7 +297,7 @@ class DeepCheckStreamer(LineOnlyReceiver):
|
|||||||
output.dataReceived(chunk)
|
output.dataReceived(chunk)
|
||||||
if not self.options["raw"]:
|
if not self.options["raw"]:
|
||||||
output.done()
|
output.done()
|
||||||
return 0
|
return self.rc
|
||||||
|
|
||||||
def deepcheck(options):
|
def deepcheck(options):
|
||||||
return DeepCheckStreamer().run(options)
|
return DeepCheckStreamer().run(options)
|
||||||
|
@ -16,6 +16,7 @@ class ManifestStreamer(LineOnlyReceiver):
|
|||||||
self.transport = FakeTransport()
|
self.transport = FakeTransport()
|
||||||
|
|
||||||
def run(self, options):
|
def run(self, options):
|
||||||
|
self.rc = 0
|
||||||
stdout = options.stdout
|
stdout = options.stdout
|
||||||
stderr = options.stderr
|
stderr = options.stderr
|
||||||
self.options = options
|
self.options = options
|
||||||
@ -38,6 +39,7 @@ class ManifestStreamer(LineOnlyReceiver):
|
|||||||
return 1
|
return 1
|
||||||
#print "RESP", dir(resp)
|
#print "RESP", dir(resp)
|
||||||
# use Twisted to split this into lines
|
# use Twisted to split this into lines
|
||||||
|
self.in_error = False
|
||||||
while True:
|
while True:
|
||||||
chunk = resp.read(100)
|
chunk = resp.read(100)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
@ -46,11 +48,21 @@ class ManifestStreamer(LineOnlyReceiver):
|
|||||||
stdout.write(chunk)
|
stdout.write(chunk)
|
||||||
else:
|
else:
|
||||||
self.dataReceived(chunk)
|
self.dataReceived(chunk)
|
||||||
return 0
|
return self.rc
|
||||||
|
|
||||||
def lineReceived(self, line):
|
def lineReceived(self, line):
|
||||||
d = simplejson.loads(line)
|
|
||||||
stdout = self.options.stdout
|
stdout = self.options.stdout
|
||||||
|
stderr = self.options.stderr
|
||||||
|
if self.in_error:
|
||||||
|
print >>stderr, line
|
||||||
|
return
|
||||||
|
if line.startswith("ERROR:"):
|
||||||
|
self.in_error = True
|
||||||
|
self.rc = 1
|
||||||
|
print >>stderr, line
|
||||||
|
return
|
||||||
|
|
||||||
|
d = simplejson.loads(line)
|
||||||
if d["type"] in ("file", "directory"):
|
if d["type"] in ("file", "directory"):
|
||||||
if self.options["storage-index"]:
|
if self.options["storage-index"]:
|
||||||
si = d["storage-index"]
|
si = d["storage-index"]
|
||||||
|
@ -1198,6 +1198,7 @@ class Check(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
d.addCallback(_stash_root_and_create_file)
|
d.addCallback(_stash_root_and_create_file)
|
||||||
def _stash_uri(fn, which):
|
def _stash_uri(fn, which):
|
||||||
self.uris[which] = fn.get_uri()
|
self.uris[which] = fn.get_uri()
|
||||||
|
return fn
|
||||||
d.addCallback(_stash_uri, "good")
|
d.addCallback(_stash_uri, "good")
|
||||||
d.addCallback(lambda ign:
|
d.addCallback(lambda ign:
|
||||||
self.rootnode.add_file(u"small",
|
self.rootnode.add_file(u"small",
|
||||||
@ -1217,6 +1218,11 @@ class Check(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
in lines, out)
|
in lines, out)
|
||||||
d.addCallback(_check1)
|
d.addCallback(_check1)
|
||||||
|
|
||||||
|
# root
|
||||||
|
# root/good
|
||||||
|
# root/small
|
||||||
|
# root/mutable
|
||||||
|
|
||||||
d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
|
d.addCallback(lambda ign: self.do_cli("deep-check", "--verbose",
|
||||||
self.rooturi))
|
self.rooturi))
|
||||||
def _check2((rc, out, err)):
|
def _check2((rc, out, err)):
|
||||||
@ -1248,6 +1254,11 @@ class Check(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
debug.corrupt_share(cso)
|
debug.corrupt_share(cso)
|
||||||
d.addCallback(_clobber_shares)
|
d.addCallback(_clobber_shares)
|
||||||
|
|
||||||
|
# root
|
||||||
|
# root/good [9 shares]
|
||||||
|
# root/small
|
||||||
|
# root/mutable [1 corrupt share]
|
||||||
|
|
||||||
d.addCallback(lambda ign:
|
d.addCallback(lambda ign:
|
||||||
self.do_cli("deep-check", "--verbose", self.rooturi))
|
self.do_cli("deep-check", "--verbose", self.rooturi))
|
||||||
def _check3((rc, out, err)):
|
def _check3((rc, out, err)):
|
||||||
@ -1315,5 +1326,55 @@ class Check(GridTestMixin, CLITestMixin, unittest.TestCase):
|
|||||||
self.failUnless(" post-repair: 4 healthy, 0 unhealthy" in lines,out)
|
self.failUnless(" post-repair: 4 healthy, 0 unhealthy" in lines,out)
|
||||||
d.addCallback(_check6)
|
d.addCallback(_check6)
|
||||||
|
|
||||||
|
# now add a subdir, and a file below that, then make the subdir
|
||||||
|
# unrecoverable
|
||||||
|
|
||||||
|
d.addCallback(lambda ign:
|
||||||
|
self.rootnode.create_empty_directory(u"subdir"))
|
||||||
|
d.addCallback(_stash_uri, "subdir")
|
||||||
|
d.addCallback(lambda fn:
|
||||||
|
fn.add_file(u"subfile", upload.Data(DATA+"2", "")))
|
||||||
|
d.addCallback(lambda ign:
|
||||||
|
self.delete_shares_numbered(self.uris["subdir"],
|
||||||
|
range(10)))
|
||||||
|
|
||||||
|
# root
|
||||||
|
# root/good
|
||||||
|
# root/small
|
||||||
|
# root/mutable
|
||||||
|
# root/subdir [unrecoverable: 0 shares]
|
||||||
|
# root/subfile
|
||||||
|
|
||||||
|
d.addCallback(lambda ign: self.do_cli("manifest", self.rooturi))
|
||||||
|
def _manifest_failed((rc, out, err)):
|
||||||
|
self.failIfEqual(rc, 0)
|
||||||
|
self.failUnlessIn("ERROR: UnrecoverableFileError", err)
|
||||||
|
# the fatal directory should still show up, as the last line
|
||||||
|
self.failUnlessIn(" subdir\n", out)
|
||||||
|
d.addCallback(_manifest_failed)
|
||||||
|
|
||||||
|
d.addCallback(lambda ign: self.do_cli("deep-check", self.rooturi))
|
||||||
|
def _deep_check_failed((rc, out, err)):
|
||||||
|
self.failIfEqual(rc, 0)
|
||||||
|
self.failUnlessIn("ERROR: UnrecoverableFileError", err)
|
||||||
|
# we want to make sure that the error indication is the last
|
||||||
|
# thing that gets emitted
|
||||||
|
self.failIf("done:" in out, out)
|
||||||
|
d.addCallback(_deep_check_failed)
|
||||||
|
|
||||||
|
# this test is disabled until the deep-repair response to an
|
||||||
|
# unrepairable directory is fixed. The failure-to-repair should not
|
||||||
|
# throw an exception, but the failure-to-traverse that follows
|
||||||
|
# should throw UnrecoverableFileError.
|
||||||
|
|
||||||
|
#d.addCallback(lambda ign:
|
||||||
|
# self.do_cli("deep-check", "--repair", self.rooturi))
|
||||||
|
#def _deep_check_repair_failed((rc, out, err)):
|
||||||
|
# self.failIfEqual(rc, 0)
|
||||||
|
# print err
|
||||||
|
# self.failUnlessIn("ERROR: UnrecoverableFileError", err)
|
||||||
|
# self.failIf("done:" in out, out)
|
||||||
|
#d.addCallback(_deep_check_repair_failed)
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user