2 Test an existing Tahoe grid, both to see if the grid is still running and to
3 see if the client is still compatible with it. This script is suitable for
4 running from a periodic monitoring script, perhaps by an hourly cronjob.
6 This script uses a pre-established client node (configured to connect to the
7 grid being tested) and a pre-established directory (stored as the 'testgrid:'
8 alias in that client node's aliases file). It then performs a number of
9 uploads and downloads to exercise compatibility in various directions (new
10 client vs old data). All operations are performed by invoking various CLI
11 commands through bin/tahoe . The script must be given two arguments: the
12 client node directory, and the location of the bin/tahoe executable. Note
13 that this script does not import anything from tahoe directly, so it doesn't
14 matter what its PYTHONPATH is, as long as the bin/tahoe that it uses is
17 This script expects that the client node will be not running when the script
18 starts, but it will forcibly shut down the node just to be sure. It will shut
19 down the node after the test finishes.
21 To set up the client node, do the following:
23 tahoe create-client DIR
24 populate DIR/introducer.furl
26 tahoe add-alias -d DIR testgrid `tahoe mkdir -d DIR`
27 pick a 10kB-ish test file, compute its md5sum
28 tahoe put -d DIR FILE testgrid:old.MD5SUM
29 tahoe put -d DIR FILE testgrid:recent.MD5SUM
30 tahoe put -d DIR FILE testgrid:recentdir/recent.MD5SUM
31 echo "" | tahoe put -d DIR --mutable testgrid:log
32 echo "" | tahoe put -d DIR --mutable testgrid:recentlog
34 This script will perform the following steps (the kind of compatibility that
35 is being tested is in [brackets]):
37 read old.* and check the md5sums [confirm that new code can read old files]
38 read all recent.* files and check md5sums [read recent files]
39 delete all recent.* files and verify they're gone [modify an old directory]
40 read recentdir/recent.* files and check [read recent directory]
41 delete recentdir/recent.* and verify [modify recent directory]
42 delete recentdir and verify (keep the directory from growing unboundedly)
44 upload random 10kB file to recentdir/recent.MD5SUM (prepare for next time)
45 upload random 10kB file to recent.MD5SUM [new code can upload to old servers]
46 append one-line timestamp to log [read/write old mutable files]
47 append one-line timestamp to recentlog [read/write recent mutable files]
49 upload small header to new mutable recentlog [create mutable files]
51 This script will also keep track of speeds and latencies and will write them
52 in a machine-readable logfile.
56 import time, subprocess, md5, os.path, random
57 from twisted.python import usage
59 class GridTesterOptions(usage.Options):
62 ("no", "n", "Dry run: do not run any commands, just print them."),
65 def parseArgs(self, nodedir, tahoe):
66 # Note: does not support Unicode arguments.
67 self.nodedir = os.path.expanduser(nodedir)
68 self.tahoe = os.path.abspath(os.path.expanduser(tahoe))
70 class CommandFailed(Exception):
74 def __init__(self, config):
76 self.tahoe = config.tahoe
77 self.nodedir = config.nodedir
79 def command(self, *cmd, **kwargs):
80 expected_rc = kwargs.get("expected_rc", 0)
81 stdin = kwargs.get("stdin", None)
85 p = subprocess.Popen(cmd,
86 stdin=subprocess.PIPE,
87 stdout=subprocess.PIPE,
88 stderr=subprocess.PIPE)
89 (stdout,stderr) = p.communicate(stdin)
91 p = subprocess.Popen(cmd,
92 stdout=subprocess.PIPE,
93 stderr=subprocess.PIPE)
94 (stdout,stderr) = p.communicate()
96 if expected_rc != None and rc != expected_rc:
100 raise CommandFailed("command '%s' failed: rc=%d" % (cmd, rc))
101 return stdout, stderr
103 def cli(self, cmd, *args, **kwargs):
104 print "tahoe", cmd, " ".join(args)
105 stdout, stderr = self.command(self.tahoe, cmd, "-d", self.nodedir,
107 if not kwargs.get("ignore_stderr", False) and stderr != "":
108 raise CommandFailed("command '%s' had stderr: %s" % (" ".join(args),
112 def stop_old_node(self):
113 print "tahoe stop", self.nodedir, "(force)"
114 self.command(self.tahoe, "stop", self.nodedir, expected_rc=None)
116 def start_node(self):
117 print "tahoe start", self.nodedir
118 self.command(self.tahoe, "start", self.nodedir)
122 print "tahoe stop", self.nodedir
123 self.command(self.tahoe, "stop", self.nodedir)
125 def read_and_check(self, f):
126 expected_md5_s = f[f.find(".")+1:]
127 out = self.cli("get", "testgrid:" + f)
128 got_md5_s = md5.new(out).hexdigest()
129 if got_md5_s != expected_md5_s:
130 raise CommandFailed("%s had md5sum of %s" % (f, got_md5_s))
132 def delete_and_check(self, dirname, f):
133 oldfiles = self.listdir(dirname)
135 absfilename = "testgrid:" + dirname + "/" + f
137 absfilename = "testgrid:" + f
138 if f not in oldfiles:
139 raise CommandFailed("um, '%s' was supposed to already be in %s"
141 self.cli("rm", absfilename)
142 newfiles = self.listdir(dirname)
144 raise CommandFailed("failed to remove '%s' from %s" % (f, dirname))
146 def listdir(self, dirname):
147 out = self.cli("ls", "testgrid:"+dirname).strip().split("\n")
148 files = [f.strip() for f in out]
153 files = self.listdir("")
155 if f.startswith("old.") or f.startswith("recent."):
156 self.read_and_check("" + f)
158 if f.startswith("recent."):
159 self.delete_and_check("", f)
160 files = self.listdir("recentdir")
162 if f.startswith("old.") or f.startswith("recent."):
163 self.read_and_check("recentdir/" + f)
165 if f.startswith("recent."):
166 self.delete_and_check("recentdir", f)
167 self.delete_and_check("", "recentdir")
169 self.cli("mkdir", "testgrid:recentdir")
170 fn, data = self.makefile("recent")
171 self.put("recentdir/"+fn, data)
172 files = self.listdir("recentdir")
174 raise CommandFailed("failed to put %s in recentdir/" % fn)
175 fn, data = self.makefile("recent")
177 files = self.listdir("")
179 raise CommandFailed("failed to put %s in testgrid:" % fn)
182 self.update("recentlog")
183 self.delete_and_check("", "recentlog")
184 self.put_mutable("recentlog", "Recent Mutable Log Header\n\n")
186 def put(self, fn, data):
187 self.cli("put", "-", "testgrid:"+fn, stdin=data, ignore_stderr=True)
189 def put_mutable(self, fn, data):
190 self.cli("put", "--mutable", "-", "testgrid:"+fn,
191 stdin=data, ignore_stderr=True)
193 def update(self, fn):
194 old = self.cli("get", "testgrid:"+fn)
195 new = old + time.ctime() + "\n"
198 def makefile(self, prefix):
199 size = random.randint(10001, 10100)
200 data = os.urandom(size)
201 md5sum = md5.new(data).hexdigest()
202 fn = prefix + "." + md5sum
214 config = GridTesterOptions()
215 config.parseOptions()
216 gt = GridTester(config)
219 if __name__ == "__main__":