]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/startstop_node.py
setup: add a case to execute "python .../twistd.py" if "twistd" is not found
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / startstop_node.py
1
2 import os, sys, signal, time
3 from twisted.python import usage
4 from allmydata.scripts.common import BasedirMixin
5 from allmydata.util import fileutil, find_exe
6
7 class StartOptions(BasedirMixin, usage.Options):
8     optParameters = [
9         ["basedir", "C", None, "which directory to start the node in"],
10         ]
11     optFlags = [
12         ["profile", "p", "whether to run under the Python profiler, putting results in \"profiling_results.prof\""],
13         ["syslog", None, "tell the node to log to syslog, not a file"],
14         ]
15
16 class StopOptions(BasedirMixin, usage.Options):
17     optParameters = [
18         ["basedir", "C", None, "which directory to stop the node in"],
19         ]
20
21 class RestartOptions(BasedirMixin, usage.Options):
22     optParameters = [
23         ["basedir", "C", None, "which directory to restart the node in"],
24         ]
25     optFlags = [
26         ["force", "f", "if the node is not already running, start it "
27          "instead of complaining that you should have used 'start' instead "
28          "of 'restart'"],
29         ["profile", "p", "whether to run under the Python profiler, putting results in \"profiling_results.prof\""],
30         ["syslog", None, "tell the node to log to syslog, not a file"],
31         ]
32
33 class RunOptions(usage.Options):
34     optParameters = [
35         ["basedir", "C", None, "which directory to run the node in, CWD by default"],
36         ]
37
38 def do_start(basedir, opts, out=sys.stdout, err=sys.stderr):
39     print >>out, "STARTING", basedir
40     if not os.path.isdir(basedir):
41         print >>err, "%s does not look like a directory at all" % basedir
42         return 1
43     for fn in os.listdir(basedir):
44         if fn.endswith(".tac"):
45             tac = fn
46             break
47     else:
48         print >>err, "%s does not look like a node directory (no .tac file)" % basedir
49         return 1
50     if "client" in tac:
51         nodetype = "client"
52     elif "introducer" in tac:
53         nodetype = "introducer"
54     else:
55         nodetype = "unknown (%s)" % tac
56
57     cmd = find_exe.find_exe('twistd')
58     if not cmd:
59         # If 'twistd' wasn't on $PATH, maybe we're running from source and
60         # Twisted was built as one of our dependencies. If so, we're at
61         # BASEDIR/src/allmydata/scripts/startstop_node.py, and it's at
62         # BASEDIR/support/$BINDIR/twistd
63         up = os.path.dirname
64         TAHOEDIR = up(up(up(up(os.path.abspath(__file__)))))
65         if sys.platform == "win32":
66             bin_dir = "Scripts"
67         else:
68             bin_dir = "bin"
69         bindir = os.path.join(TAHOEDIR, "support", bin_dir)
70
71         maybe = os.path.join(bindir, "twistd")
72         if os.path.exists(maybe):
73             cmd = [maybe]
74             oldpath = os.environ.get("PATH", "").split(os.pathsep)
75             os.environ["PATH"] = os.pathsep.join(oldpath + [bindir])
76             # sys.path and $PYTHONPATH are taken care of by the extra code in
77             # 'setup.py trial'
78         else:
79             maybe = maybe+'.py'
80             if os.path.exists(maybe):
81                 cmd = [sys.executable, maybe]
82                 oldpath = os.environ.get("PATH", "").split(os.pathsep)
83                 os.environ["PATH"] = os.pathsep.join(oldpath + [bindir])
84                 # sys.path and $PYTHONPATH are taken care of by the extra code in
85                 # 'setup.py trial'
86
87     if not cmd:
88         print "Can't find twistd (it comes with Twisted).  Aborting."
89         sys.exit(1)
90
91     cmd.extend(["-y", tac])
92     if opts["syslog"]:
93         cmd.append("--syslog")
94     elif nodetype in ("client", "introducer"):
95         fileutil.make_dirs(os.path.join(basedir, "logs"))
96         cmd.extend(["--logfile", os.path.join("logs", "twistd.log")])
97     if opts["profile"]:
98         cmd.extend(["--profile=profiling_results.prof", "--savestats",])
99     curdir = os.getcwd()
100     try:
101         os.chdir(basedir)
102         rc = os.system(' '.join(cmd))
103     finally:
104         os.chdir(curdir)
105     if rc == 0:
106         print >>out, "%s node probably started" % nodetype
107         return 0
108     else:
109         print >>err, "%s node probably not started" % nodetype
110         return 1
111
112 def do_stop(basedir, out=sys.stdout, err=sys.stderr):
113     print >>out, "STOPPING", basedir
114     pidfile = os.path.join(basedir, "twistd.pid")
115     if not os.path.exists(pidfile):
116         print >>err, "%s does not look like a running node directory (no twistd.pid)" % basedir
117         return 2
118     pid = open(pidfile, "r").read()
119     pid = int(pid)
120
121     # kill it hard (SIGKILL), delete the twistd.pid file, then wait for the
122     # process itself to go away. If it hasn't gone away after 20 seconds, warn
123     # the user but keep waiting until they give up.
124     try:
125         os.kill(pid, signal.SIGKILL)
126     except OSError, oserr:
127         if oserr.errno == 3:
128             print oserr.strerror
129             # the process didn't exist, so wipe the pid file
130             os.remove(pidfile)
131             return 1
132         else:
133             raise
134     try:
135         os.remove(pidfile)
136     except EnvironmentError:
137         pass
138     start = time.time()
139     time.sleep(0.1)
140     wait = 40
141     first_time = True
142     while True:
143         # poll once per second until we see the process is no longer running
144         try:
145             os.kill(pid, 0)
146         except OSError:
147             print >>out, "process %d is dead" % pid
148             return
149         wait -= 1
150         if wait < 0:
151             if first_time:
152                 print >>err, ("It looks like pid %d is still running "
153                               "after %d seconds" % (pid,
154                                                     (time.time() - start)))
155                 print >>err, "I will keep watching it until you interrupt me."
156                 wait = 10
157                 first_time = False
158             else:
159                 print >>err, "pid %d still running after %d seconds" % \
160                       (pid, (time.time() - start))
161                 wait = 10
162         time.sleep(1)
163     return 1
164
165 def start(config, stdout, stderr):
166     rc = 0
167     for basedir in config['basedirs']:
168         rc = do_start(basedir, config, stdout, stderr) or rc
169     return rc
170
171 def stop(config, stdout, stderr):
172     rc = 0
173     for basedir in config['basedirs']:
174         rc = do_stop(basedir, stdout, stderr) or rc
175     return rc
176
177 def restart(config, stdout, stderr):
178     rc = 0
179     for basedir in config['basedirs']:
180         rc = do_stop(basedir, stdout, stderr) or rc
181     if rc == 2 and config['force']:
182         print >>stderr, "ignoring couldn't-stop"
183         rc = 0
184     if rc:
185         print >>stderr, "not restarting"
186         return rc
187     for basedir in config['basedirs']:
188         rc = do_start(basedir, config, stdout, stderr) or rc
189     return rc
190
191 def run(config, stdout, stderr):
192     from twisted.internet import reactor
193     from twisted.python import log, logfile
194     from allmydata import client
195
196     basedir = config['basedir']
197     if basedir is None:
198         basedir = '.'
199     else:
200         os.chdir(basedir)
201
202     # set up twisted logging. this will become part of the node rsn.
203     logdir = os.path.join(basedir, 'logs')
204     if not os.path.exists(logdir):
205         os.makedirs(logdir)
206     lf = logfile.LogFile('tahoesvc.log', logdir)
207     log.startLogging(lf)
208
209     # run the node itself
210     c = client.Client(basedir)
211     reactor.callLater(0, c.startService) # after reactor startup
212     reactor.run()
213
214     return 0
215
216
217 subCommands = [
218     ["start", None, StartOptions, "Start a node (of any type)."],
219     ["stop", None, StopOptions, "Stop a node."],
220     ["restart", None, RestartOptions, "Restart a node."],
221     ["run", None, RunOptions, "Run a node synchronously."],
222 ]
223
224 dispatch = {
225     "start": start,
226     "stop": stop,
227     "restart": restart,
228     "run": run,
229     }