From 9ed9eb2048c5cc3809be7f8ccaf98b55fc66fad5 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 19 Jun 2008 12:39:52 -0700 Subject: [PATCH] check_grid.py: make it work, move node start/stop responsibility from Makefile to script --- Makefile | 7 +- src/allmydata/test/check_grid.py | 185 +++++++++++++++++++++++++++++-- 2 files changed, 177 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index d2962d4b..44bbd979 100644 --- a/Makefile +++ b/Makefile @@ -258,12 +258,7 @@ check-speed: .built # in src/allmydata/test/check_grid.py to see how to set this up. check-grid: .built if [ -z '$(TESTCLIENTDIR)' ]; then exit 1; fi - @echo "stopping any leftover client code" - -$(PYTHON) bin/tahoe stop $(TESTCLIENTDIR) - $(PYTHON) bin/tahoe start $(TESTCLIENTDIR) - sleep 5 - $(PYTHON) src/allmydata/test/check_grid.py $(TESTCLIENTDIR) - $(PYTHON) bin/tahoe stop $(TESTCLIENTDIR) + $(PYTHON) src/allmydata/test/check_grid.py $(TESTCLIENTDIR) bin/tahoe # 'make repl' is a simple-to-type command to get a Python interpreter loop # from which you can type 'import allmydata' diff --git a/src/allmydata/test/check_grid.py b/src/allmydata/test/check_grid.py index 2fb42985..8b2fc9fc 100644 --- a/src/allmydata/test/check_grid.py +++ b/src/allmydata/test/check_grid.py @@ -9,10 +9,16 @@ This script uses a pre-established client node (configured to connect to the grid being tested) and a pre-established directory (stored as the 'testgrid:' alias in that client node's aliases file). It then performs a number of uploads and downloads to exercise compatibility in various directions (new -client vs old data). +client vs old data). All operations are performed by invoking various CLI +commands through bin/tahoe . The script must be given two arguments: the +client node directory, and the location of the bin/tahoe executable. Note +that this script does not import anything from tahoe directly, so it doesn't +matter what its PYTHONPATH is, as long as the bin/tahoe that it uses is +functional. -This script expects that the client node will be running before the script -starts. +This script expects that the client node will be not running when the script +starts, but it will forcibly shut down the node just to be sure. It will shut +down the node after the test finishes. To set up the client node, do the following: @@ -20,13 +26,13 @@ To set up the client node, do the following: touch DIR/no_storage populate DIR/introducer.furl tahoe start DIR - tahoe -d DIR add-alias testgrid `tahoe -d DIR mkdir` + tahoe add-alias -d DIR testgrid `tahoe mkdir -d DIR` pick a 10kB-ish test file, compute its md5sum - tahoe -d DIR put FILE testgrid:old.MD5SUM - tahoe -d DIR put FILE testgrid:recent.MD5SUM - tahoe -d DIR put FILE testgrid:recentdir/recent.MD5SUM - echo "" | tahoe -d DIR put --mutable testgrid:log - echo "" | tahoe -d DIR put --mutable testgrid:recentlog + tahoe put -d DIR FILE testgrid:old.MD5SUM + tahoe put -d DIR FILE testgrid:recent.MD5SUM + tahoe put -d DIR FILE testgrid:recentdir/recent.MD5SUM + echo "" | tahoe put -d DIR --mutable testgrid:log + echo "" | tahoe put -d DIR --mutable testgrid:recentlog This script will perform the following steps (the kind of compatibility that is being tested is in [brackets]): @@ -50,3 +56,164 @@ in a machine-readable logfile. """ +import time, subprocess, md5, os.path, random +from twisted.python import usage + +class GridTesterOptions(usage.Options): + + optFlags = [ + ("no", "n", "Dry run: do not run any commands, just print them."), + ] + + def parseArgs(self, nodedir, tahoe): + self.nodedir = nodedir + self.tahoe = os.path.abspath(tahoe) + +class CommandFailed(Exception): + pass + +class GridTester: + def __init__(self, config): + self.config = config + self.tahoe = config.tahoe + self.nodedir = config.nodedir + + def command(self, *cmd, **kwargs): + expected_rc = kwargs.get("expected_rc", None) + stdin = kwargs.get("stdin", None) + if self.config["no"]: + return + if stdin is not None: + p = subprocess.Popen(cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout,stderr) = p.communicate(stdin) + else: + p = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout,stderr) = p.communicate() + rc = p.returncode + if expected_rc != None and rc != expected_rc: + raise CommandFailed("command '%s' failed: rc=%d" % (cmd, rc)) + return stdout, stderr + + def cli(self, cmd, *args, **kwargs): + print "tahoe", cmd, " ".join(args) + stdout, stderr = self.command(self.tahoe, cmd, "-d", self.nodedir, + *args, **kwargs) + if not kwargs.get("ignore_stderr", False) and stderr != "": + raise CommandFailed("command '%s' had stderr: %s" % (" ".join(args), + stderr)) + return stdout + + def stop_old_node(self): + print "tahoe stop", self.nodedir, "(force)" + self.command(self.tahoe, "stop", self.nodedir, expected_rc=None) + + def start_node(self): + print "tahoe start", self.nodedir + self.command(self.tahoe, "start", self.nodedir) + time.sleep(5) + + def stop_node(self): + print "tahoe stop", self.nodedir + self.command(self.tahoe, "stop", self.nodedir) + + def read_and_check(self, f): + expected_md5_s = f[f.find(".")+1:] + out = self.cli("get", "testgrid:" + f) + got_md5_s = md5.new(out).hexdigest() + if got_md5_s != expected_md5_s: + raise CommandFailed("%s had md5sum of %s" % (f, got_md5_s)) + + def delete_and_check(self, dirname, f): + oldfiles = self.listdir(dirname) + if dirname: + absfilename = "testgrid:" + dirname + "/" + f + else: + absfilename = "testgrid:" + f + if f not in oldfiles: + raise CommandFailed("um, '%s' was supposed to already be in %s" + % (f, dirname)) + self.cli("rm", absfilename) + newfiles = self.listdir(dirname) + if f in newfiles: + raise CommandFailed("failed to remove '%s' from %s" % (f, dirname)) + + def listdir(self, dirname): + out = self.cli("ls", "testgrid:"+dirname).strip().split("\n") + files = [f.strip() for f in out] + print " ", files + return files + + def do_test(self): + files = self.listdir("") + for f in files: + if f.startswith("old.") or f.startswith("recent."): + self.read_and_check("" + f) + for f in files: + if f.startswith("recent."): + self.delete_and_check("", f) + files = self.listdir("recentdir") + for f in files: + if f.startswith("old.") or f.startswith("recent."): + self.read_and_check("recentdir/" + f) + for f in files: + if f.startswith("recent."): + self.delete_and_check("recentdir", f) + self.delete_and_check("", "recentdir") + + self.cli("mkdir", "testgrid:recentdir") + fn, data = self.makefile("recent") + self.put("recentdir/"+fn, data) + files = self.listdir("recentdir") + if fn not in files: + raise CommandFailed("failed to put %s in recentdir/" % fn) + fn, data = self.makefile("recent") + self.put(fn, data) + files = self.listdir("") + if fn not in files: + raise CommandFailed("failed to put %s in testgrid:" % fn) + + self.update("log") + self.update("recentlog") + self.delete_and_check("", "recentlog") + self.put_mutable("recentlog", "Recent Mutable Log Header\n\n") + + def put(self, fn, data): + self.cli("put", "testgrid:"+fn, stdin=data, ignore_stderr=True) + + def put_mutable(self, fn, data): + self.cli("put", "--mutable", "testgrid:"+fn, + stdin=data, ignore_stderr=True) + + def update(self, fn): + old = self.cli("get", "testgrid:"+fn) + new = old + time.ctime() + "\n" + self.put(fn, new) + + def makefile(self, prefix): + size = random.randint(10001, 10100) + data = os.urandom(size) + md5sum = md5.new(data).hexdigest() + fn = prefix + "." + md5sum + return fn, data + + def run(self): + self.stop_old_node() + self.start_node() + try: + self.do_test() + finally: + self.stop_node() + +def main(): + config = GridTesterOptions() + config.parseOptions() + gt = GridTester(config) + gt.run() + +if __name__ == "__main__": + main() -- 2.45.2