CLI: modify 'tahoe manifest' and 'tahoe deep-check' to report ERROR: properly. For #590.

This commit is contained in:
Brian Warner 2009-02-24 23:44:15 -07:00
parent fd4ceb6a87
commit a3c1fe35d9
3 changed files with 109 additions and 7 deletions

View File

@ -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)

View File

@ -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"]

View File

@ -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