]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/check_grid.py
remove interpreter shbang lines from non-executables
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / check_grid.py
1 """
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.
5
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
15 functional.
16
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.
20
21 To set up the client node, do the following:
22
23   tahoe create-client DIR
24   populate DIR/introducer.furl
25   tahoe start DIR
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
33
34 This script will perform the following steps (the kind of compatibility that
35 is being tested is in [brackets]):
36
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)
43  mkdir recentdir
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]
48  delete recentlog
49  upload small header to new mutable recentlog [create mutable files]
50
51 This script will also keep track of speeds and latencies and will write them
52 in a machine-readable logfile.
53
54 """
55
56 import time, subprocess, md5, os.path, random
57 from twisted.python import usage
58
59 class GridTesterOptions(usage.Options):
60
61     optFlags = [
62         ("no", "n", "Dry run: do not run any commands, just print them."),
63         ]
64
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))
69
70 class CommandFailed(Exception):
71     pass
72
73 class GridTester:
74     def __init__(self, config):
75         self.config = config
76         self.tahoe = config.tahoe
77         self.nodedir = config.nodedir
78
79     def command(self, *cmd, **kwargs):
80         expected_rc = kwargs.get("expected_rc", 0)
81         stdin = kwargs.get("stdin", None)
82         if self.config["no"]:
83             return
84         if stdin is not None:
85             p = subprocess.Popen(cmd,
86                                  stdin=subprocess.PIPE,
87                                  stdout=subprocess.PIPE,
88                                  stderr=subprocess.PIPE)
89             (stdout,stderr) = p.communicate(stdin)
90         else:
91             p = subprocess.Popen(cmd,
92                                  stdout=subprocess.PIPE,
93                                  stderr=subprocess.PIPE)
94             (stdout,stderr) = p.communicate()
95         rc = p.returncode
96         if expected_rc != None and rc != expected_rc:
97             if stderr:
98                 print "STDERR:"
99                 print stderr
100             raise CommandFailed("command '%s' failed: rc=%d" % (cmd, rc))
101         return stdout, stderr
102
103     def cli(self, cmd, *args, **kwargs):
104         print "tahoe", cmd, " ".join(args)
105         stdout, stderr = self.command(self.tahoe, cmd, "-d", self.nodedir,
106                                       *args, **kwargs)
107         if not kwargs.get("ignore_stderr", False) and stderr != "":
108             raise CommandFailed("command '%s' had stderr: %s" % (" ".join(args),
109                                                                  stderr))
110         return stdout
111
112     def stop_old_node(self):
113         print "tahoe stop", self.nodedir, "(force)"
114         self.command(self.tahoe, "stop", self.nodedir, expected_rc=None)
115
116     def start_node(self):
117         print "tahoe start", self.nodedir
118         self.command(self.tahoe, "start", self.nodedir)
119         time.sleep(5)
120
121     def stop_node(self):
122         print "tahoe stop", self.nodedir
123         self.command(self.tahoe, "stop", self.nodedir)
124
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))
131
132     def delete_and_check(self, dirname, f):
133         oldfiles = self.listdir(dirname)
134         if dirname:
135             absfilename = "testgrid:" + dirname + "/" + f
136         else:
137             absfilename = "testgrid:" + f
138         if f not in oldfiles:
139             raise CommandFailed("um, '%s' was supposed to already be in %s"
140                                 % (f, dirname))
141         self.cli("rm", absfilename)
142         newfiles = self.listdir(dirname)
143         if f in newfiles:
144             raise CommandFailed("failed to remove '%s' from %s" % (f, dirname))
145
146     def listdir(self, dirname):
147         out = self.cli("ls", "testgrid:"+dirname).strip().split("\n")
148         files = [f.strip() for f in out]
149         print " ", files
150         return files
151
152     def do_test(self):
153         files = self.listdir("")
154         for f in files:
155             if f.startswith("old.") or f.startswith("recent."):
156                 self.read_and_check("" + f)
157         for f in files:
158             if f.startswith("recent."):
159                 self.delete_and_check("", f)
160         files = self.listdir("recentdir")
161         for f in files:
162             if f.startswith("old.") or f.startswith("recent."):
163                 self.read_and_check("recentdir/" + f)
164         for f in files:
165             if f.startswith("recent."):
166                 self.delete_and_check("recentdir", f)
167         self.delete_and_check("", "recentdir")
168
169         self.cli("mkdir", "testgrid:recentdir")
170         fn, data = self.makefile("recent")
171         self.put("recentdir/"+fn, data)
172         files = self.listdir("recentdir")
173         if fn not in files:
174             raise CommandFailed("failed to put %s in recentdir/" % fn)
175         fn, data = self.makefile("recent")
176         self.put(fn, data)
177         files = self.listdir("")
178         if fn not in files:
179             raise CommandFailed("failed to put %s in testgrid:" % fn)
180
181         self.update("log")
182         self.update("recentlog")
183         self.delete_and_check("", "recentlog")
184         self.put_mutable("recentlog", "Recent Mutable Log Header\n\n")
185
186     def put(self, fn, data):
187         self.cli("put", "-", "testgrid:"+fn, stdin=data, ignore_stderr=True)
188
189     def put_mutable(self, fn, data):
190         self.cli("put", "--mutable", "-", "testgrid:"+fn,
191                  stdin=data, ignore_stderr=True)
192
193     def update(self, fn):
194         old = self.cli("get", "testgrid:"+fn)
195         new = old + time.ctime() + "\n"
196         self.put(fn, new)
197
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
203         return fn, data
204
205     def run(self):
206         self.stop_old_node()
207         self.start_node()
208         try:
209             self.do_test()
210         finally:
211             self.stop_node()
212
213 def main():
214     config = GridTesterOptions()
215     config.parseOptions()
216     gt = GridTester(config)
217     gt.run()
218
219 if __name__ == "__main__":
220     main()