]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/runner.py
add 'allmydata-tahoe dump-uri-extension' utility command
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / runner.py
1 #! /usr/bin/env python
2
3 import os, subprocess, sys, signal, time
4 from twisted.python import usage
5
6 from twisted.python.procutils import which
7
8 def testtwistd(loc):
9     try:
10         return subprocess.call(["python", loc,], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
11     except:
12         return -1
13     
14 twistd = None
15 if not twistd:
16     for maybetwistd in which("twistd"):
17         ret = testtwistd(maybetwistd)
18         if ret == 0:
19             twistd = maybetwistd
20             break
21
22 if not twistd:
23     for maybetwistd in which("twistd.py"):
24         ret = testtwistd(maybetwistd)
25         if ret == 0:
26             twistd = maybetwistd
27             break
28
29 if not twistd:
30     maybetwistd = os.path.join(sys.prefix, 'Scripts', 'twistd')
31     ret = testtwistd(maybetwistd)
32     if ret == 0:
33         twistd = maybetwistd
34
35 if not twistd:
36     maybetwistd = os.path.join(sys.prefix, 'Scripts', 'twistd.py')
37     ret = testtwistd(maybetwistd)
38     if ret == 0:
39         twistd = maybetwistd
40
41 if not twistd:
42     print "Can't find twistd (it comes with Twisted).  Aborting."
43     sys.exit(1)
44
45 class BasedirMixin:
46     optFlags = [
47         ["multiple", "m", "allow multiple basedirs to be specified at once"],
48         ]
49
50     def postOptions(self):
51         if not self.basedirs:
52             raise usage.UsageError("<basedir> parameter is required")
53         if self['basedir']:
54             del self['basedir']
55         self['basedirs'] = [os.path.abspath(os.path.expanduser(b))
56                             for b in self.basedirs]
57
58     def parseArgs(self, *args):
59         self.basedirs = []
60         if self['basedir']:
61             self.basedirs.append(self['basedir'])
62         if self['multiple']:
63             self.basedirs.extend(args)
64         else:
65             if len(args) == 0 and not self.basedirs:
66                 self.basedirs.append(".")
67             if len(args) > 0:
68                 self.basedirs.append(args[0])
69             if len(args) > 1:
70                 raise usage.UsageError("I wasn't expecting so many arguments")
71
72 class NoDefaultBasedirMixin(BasedirMixin):
73     def parseArgs(self, *args):
74         # create-client won't default to --basedir=.
75         self.basedirs = []
76         if self['basedir']:
77             self.basedirs.append(self['basedir'])
78         if self['multiple']:
79             self.basedirs.extend(args)
80         else:
81             if len(args) > 0:
82                 self.basedirs.append(args[0])
83             if len(args) > 1:
84                 raise usage.UsageError("I wasn't expecting so many arguments")
85         if not self.basedirs:
86             raise usage.UsageError("--basedir must be provided")
87
88 class StartOptions(BasedirMixin, usage.Options):
89     optParameters = [
90         ["basedir", "C", None, "which directory to start the node in"],
91         ]
92
93 class StopOptions(BasedirMixin, usage.Options):
94     optParameters = [
95         ["basedir", "C", None, "which directory to stop the node in"],
96         ]
97
98 class RestartOptions(BasedirMixin, usage.Options):
99     optParameters = [
100         ["basedir", "C", None, "which directory to restart the node in"],
101         ]
102
103 class CreateClientOptions(NoDefaultBasedirMixin, usage.Options):
104     optParameters = [
105         ["basedir", "C", None, "which directory to create the client in"],
106         ]
107     optFlags = [
108         ["quiet", "q", "operate silently"],
109         ]
110
111 class CreateIntroducerOptions(NoDefaultBasedirMixin, usage.Options):
112     optParameters = [
113         ["basedir", "C", None, "which directory to create the introducer in"],
114         ]
115     optFlags = [
116         ["quiet", "q", "operate silently"],
117         ]
118
119 class DumpOptions(usage.Options):
120     optParameters = [
121         ["filename", "f", None, "which file to dump"],
122         ]
123
124     def parseArgs(self, filename=None):
125         if filename:
126             self['filename'] = filename
127
128     def postOptions(self):
129         if not self['filename']:
130             raise usage.UsageError("<filename> parameter is required")
131
132 client_tac = """
133 # -*- python -*-
134
135 from allmydata import client
136 from twisted.application import service
137
138 c = client.Client()
139
140 application = service.Application("allmydata_client")
141 c.setServiceParent(application)
142 """
143
144 introducer_tac = """
145 # -*- python -*-
146
147 from allmydata import introducer_and_vdrive
148 from twisted.application import service
149
150 c = introducer_and_vdrive.IntroducerAndVdrive()
151
152 application = service.Application("allmydata_introducer")
153 c.setServiceParent(application)
154 """
155
156 class Options(usage.Options):
157     synopsis = "Usage:  allmydata <command> [command options]"
158
159     subCommands = [
160         ["create-client", None, CreateClientOptions, "Create a client node."],
161         ["create-introducer", None, CreateIntroducerOptions, "Create a introducer node."],
162         ["start", None, StartOptions, "Start a node (of any type)."],
163         ["stop", None, StopOptions, "Stop a node."],
164         ["restart", None, RestartOptions, "Restart a node."],
165         ["dump-uri-extension", None, DumpOptions,
166          "Unpack and display the contents of a uri_extension file."],
167         ]
168
169     def postOptions(self):
170         if not hasattr(self, 'subOptions'):
171             raise usage.UsageError("must specify a command")
172
173 def runner(argv, run_by_human=True):
174     config = Options()
175     try:
176         config.parseOptions(argv)
177     except usage.error, e:
178         if not run_by_human:
179             raise
180         print "%s:  %s" % (sys.argv[0], e)
181         print
182         c = getattr(config, 'subOptions', config)
183         print str(c)
184         return 1
185
186     command = config.subCommand
187     so = config.subOptions
188
189     rc = 0
190     if command == "create-client":
191         for basedir in so.basedirs:
192             rc = create_client(basedir, so) or rc
193     elif command == "create-introducer":
194         for basedir in so.basedirs:
195             rc = create_introducer(basedir, so) or rc
196     elif command == "start":
197         for basedir in so.basedirs:
198             rc = start(basedir, so) or rc
199     elif command == "stop":
200         for basedir in so.basedirs:
201             rc = stop(basedir, so) or rc
202     elif command == "restart":
203         for basedir in so.basedirs:
204             rc = stop(basedir, so) or rc
205         if rc:
206             print "not restarting"
207             return rc
208         for basedir in so.basedirs:
209             rc = start(basedir, so) or rc
210     elif command == "dump-uri-extension":
211         rc = dump_uri_extension(so)
212     return rc
213
214 def run():
215     rc = runner(sys.argv[1:])
216     sys.exit(rc)
217
218 def create_client(basedir, config):
219     if os.path.exists(basedir):
220         if os.listdir(basedir):
221             print "The base directory already exists: %s" % basedir
222             print "To avoid clobbering anything, I am going to quit now"
223             print "Please use a different directory, or delete this one"
224             return -1
225         # we're willing to use an empty directory
226     else:
227         os.mkdir(basedir)
228     f = open(os.path.join(basedir, "client.tac"), "w")
229     f.write(client_tac)
230     f.close()
231     if not config['quiet']:
232         print "client created in %s" % basedir
233         print " please copy introducer.furl and vdrive.furl into the directory"
234
235 def create_introducer(basedir, config):
236     if os.path.exists(basedir):
237         if os.listdir(basedir):
238             print "The base directory already exists: %s" % basedir
239             print "To avoid clobbering anything, I am going to quit now"
240             print "Please use a different directory, or delete this one"
241             return -1
242         # we're willing to use an empty directory
243     else:
244         os.mkdir(basedir)
245     f = open(os.path.join(basedir, "introducer.tac"), "w")
246     f.write(introducer_tac)
247     f.close()
248     if not config['quiet']:
249         print "introducer created in %s" % basedir
250
251 def start(basedir, config):
252     print "STARTING", basedir
253     if os.path.exists(os.path.join(basedir, "client.tac")):
254         tac = "client.tac"
255         type = "client"
256     elif os.path.exists(os.path.join(basedir, "introducer.tac")):
257         tac = "introducer.tac"
258         type = "introducer"
259     else:
260         print "%s does not look like a node directory" % basedir
261         if not os.path.isdir(basedir):
262             print " in fact, it doesn't look like a directory at all!"
263         sys.exit(1)
264     rc = subprocess.call(["python", twistd, "-y", tac,], cwd=basedir)
265     if rc == 0:
266         print "%s node probably started" % type
267         return 0
268     else:
269         print "%s node probably not started" % type
270         return 1
271
272 def stop(basedir, config):
273     print "STOPPING", basedir
274     pidfile = os.path.join(basedir, "twistd.pid")
275     if not os.path.exists(pidfile):
276         print "%s does not look like a running node directory (no twistd.pid)" % basedir
277         return 1
278     pid = open(pidfile, "r").read()
279     pid = int(pid)
280
281     timer = 0
282     os.kill(pid, signal.SIGTERM)
283     time.sleep(0.1)
284     while timer < 5:
285         # poll once per second until twistd.pid goes away, up to 5 seconds
286         try:
287             os.kill(pid, 0)
288         except OSError:
289             print "process %d is dead" % pid
290             return
291         timer += 1
292         time.sleep(1)
293     print "never saw process go away"
294     return 1
295
296 def dump_uri_extension(config):
297     from allmydata import uri
298
299     filename = config['filename']
300     unpacked = uri.unpack_extension_readable(open(filename,"rb").read())
301     keys1 = ("size", "num_segments", "segment_size",
302              "needed_shares", "total_shares")
303     keys2 = ("codec_name", "codec_params", "tail_codec_params")
304     keys3 = ("plaintext_hash", "plaintext_root_hash",
305              "crypttext_hash", "crypttext_root_hash",
306              "share_root_hash")
307     for k in keys1:
308         if k in unpacked:
309             print "%19s: %s" % (k, unpacked[k])
310     print
311     for k in keys2:
312         if k in unpacked:
313             print "%19s: %s" % (k, unpacked[k])
314     print
315     for k in keys3:
316         if k in unpacked:
317             print "%19s: %s" % (k, unpacked[k])
318
319     leftover = set(unpacked.keys()) - set(keys1 + keys2 + keys3)
320     if leftover:
321         print
322         for k in sorted(leftover):
323             print "%s: %s" % (k, unpacked[k])
324
325     print
326     return 0