]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/startstop_node.py
cli: make startstop_node wait 20 seconds instead of 5 for a process to go away after...
[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/bin/twistd
63         up = os.path.dirname
64         TAHOEDIR = up(up(up(up(os.path.abspath(__file__)))))
65         bindir = os.path.join(TAHOEDIR, "support/bin")
66         maybe = os.path.join(bindir, "twistd")
67         if os.path.exists(maybe):
68             cmd = [maybe]
69             oldpath = os.environ.get("PATH", "").split(os.pathsep)
70             os.environ["PATH"] = os.pathsep.join(oldpath + [bindir])
71             # sys.path and $PYTHONPATH are taken care of by the extra code in
72             # 'setup.py trial'
73     if not cmd:
74         print "Can't find twistd (it comes with Twisted).  Aborting."
75         sys.exit(1)
76
77     cmd.extend(["-y", tac])
78     if opts["syslog"]:
79         cmd.append("--syslog")
80     elif nodetype in ("client", "introducer"):
81         fileutil.make_dirs(os.path.join(basedir, "logs"))
82         cmd.extend(["--logfile", os.path.join("logs", "twistd.log")])
83     if opts["profile"]:
84         cmd.extend(["--profile=profiling_results.prof", "--savestats",])
85     curdir = os.getcwd()
86     try:
87         os.chdir(basedir)
88         rc = os.system(' '.join(cmd))
89     finally:
90         os.chdir(curdir)
91     if rc == 0:
92         print >>out, "%s node probably started" % nodetype
93         return 0
94     else:
95         print >>err, "%s node probably not started" % nodetype
96         return 1
97
98 def do_stop(basedir, out=sys.stdout, err=sys.stderr):
99     print >>out, "STOPPING", basedir
100     pidfile = os.path.join(basedir, "twistd.pid")
101     if not os.path.exists(pidfile):
102         print >>err, "%s does not look like a running node directory (no twistd.pid)" % basedir
103         return 2
104     pid = open(pidfile, "r").read()
105     pid = int(pid)
106
107     # kill it hard (SIGKILL), delete the twistd.pid file, then wait for the
108     # process itself to go away. If it hasn't gone away after 20 seconds, warn
109     # the user but keep waiting until they give up.
110     try:
111         os.kill(pid, signal.SIGKILL)
112     except OSError, oserr:
113         if oserr.errno == 3:
114             print oserr.strerror
115             # the process didn't exist, so wipe the pid file
116             os.remove(pidfile)
117             return 1
118         else:
119             raise
120     try:
121         os.remove(pidfile)
122     except EnvironmentError:
123         pass
124     start = time.time()
125     time.sleep(0.1)
126     wait = 20
127     first_time = True
128     while True:
129         # poll once per second until we see the process is no longer running
130         try:
131             os.kill(pid, 0)
132         except OSError:
133             print >>out, "process %d is dead" % pid
134             return
135         wait -= 1
136         if wait < 0:
137             if first_time:
138                 print >>err, ("It looks like pid %d is still running "
139                               "after %d seconds" % (pid,
140                                                     (time.time() - start)))
141                 print >>err, "I will keep watching it until you interrupt me."
142                 wait = 10
143                 first_time = False
144             else:
145                 print >>err, "pid %d still running after %d seconds" % \
146                       (pid, (time.time() - start))
147                 wait = 10
148         time.sleep(1)
149     return 1
150
151 def start(config, stdout, stderr):
152     rc = 0
153     for basedir in config['basedirs']:
154         rc = do_start(basedir, config, stdout, stderr) or rc
155     return rc
156
157 def stop(config, stdout, stderr):
158     rc = 0
159     for basedir in config['basedirs']:
160         rc = do_stop(basedir, stdout, stderr) or rc
161     return rc
162
163 def restart(config, stdout, stderr):
164     rc = 0
165     for basedir in config['basedirs']:
166         rc = do_stop(basedir, stdout, stderr) or rc
167     if rc == 2 and config['force']:
168         print >>stderr, "ignoring couldn't-stop"
169         rc = 0
170     if rc:
171         print >>stderr, "not restarting"
172         return rc
173     for basedir in config['basedirs']:
174         rc = do_start(basedir, config, stdout, stderr) or rc
175     return rc
176
177 def run(config, stdout, stderr):
178     from twisted.internet import reactor
179     from twisted.python import log, logfile
180     from allmydata import client
181
182     basedir = config['basedir']
183     if basedir is None:
184         basedir = '.'
185     else:
186         os.chdir(basedir)
187
188     # set up twisted logging. this will become part of the node rsn.
189     logdir = os.path.join(basedir, 'logs')
190     if not os.path.exists(logdir):
191         os.makedirs(logdir)
192     lf = logfile.LogFile('tahoesvc.log', logdir)
193     log.startLogging(lf)
194
195     # run the node itself
196     c = client.Client(basedir)
197     reactor.callLater(0, c.startService) # after reactor startup
198     reactor.run()
199
200     return 0
201
202
203 subCommands = [
204     ["start", None, StartOptions, "Start a node (of any type)."],
205     ["stop", None, StopOptions, "Stop a node."],
206     ["restart", None, RestartOptions, "Restart a node."],
207     ["run", None, RunOptions, "Run a node synchronously."],
208 ]
209
210 dispatch = {
211     "start": start,
212     "stop": stop,
213     "restart": restart,
214     "run": run,
215     }