]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/startstop_node.py
3cfc0845f2c16222af34591b81b332523ef6f5a6
[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         ["profile", "p", "whether to run under the Python profiler, putting results in \"profiling_results.prof\""],
27         ["syslog", None, "tell the node to log to syslog, not a file"],
28         ]
29
30 class RunOptions(usage.Options):
31     optParameters = [
32         ["basedir", "C", None, "which directory to run the node in, CWD by default"],
33         ]
34
35 def do_start(basedir, opts, out=sys.stdout, err=sys.stderr):
36     print >>out, "STARTING", basedir
37     if not os.path.isdir(basedir):
38         print >>err, "%s does not look like a directory at all" % basedir
39         return 1
40     for fn in os.listdir(basedir):
41         if fn.endswith(".tac"):
42             tac = fn
43             break
44     else:
45         print >>err, "%s does not look like a node directory (no .tac file)" % basedir
46         return 1
47     if "client" in tac:
48         nodetype = "client"
49     elif "introducer" in tac:
50         nodetype = "introducer"
51     else:
52         nodetype = "unknown (%s)" % tac
53
54     cmd = find_exe.find_exe('twistd')
55     if not cmd:
56         # If 'twistd' wasn't on $PATH, maybe we're running from source and
57         # Twisted was built as one of our dependencies. If so, we're at
58         # BASEDIR/src/allmydata/scripts/startstop_node.py, and it's at
59         # BASEDIR/support/$BINDIR/twistd
60         up = os.path.dirname
61         TAHOEDIR = up(up(up(up(os.path.abspath(__file__)))))
62         if sys.platform == "win32":
63             bin_dir = "Scripts"
64         else:
65             bin_dir = "bin"
66         bindir = os.path.join(TAHOEDIR, "support", bin_dir)
67
68         maybe = os.path.join(bindir, "twistd")
69         if os.path.exists(maybe):
70             cmd = [maybe]
71             oldpath = os.environ.get("PATH", "").split(os.pathsep)
72             os.environ["PATH"] = os.pathsep.join(oldpath + [bindir])
73             # sys.path and $PYTHONPATH are taken care of by the extra code in
74             # 'setup.py trial'
75         else:
76             maybe = maybe+'.py'
77             if os.path.exists(maybe):
78                 cmd = [sys.executable, maybe]
79                 oldpath = os.environ.get("PATH", "").split(os.pathsep)
80                 os.environ["PATH"] = os.pathsep.join(oldpath + [bindir])
81                 # sys.path and $PYTHONPATH are taken care of by the extra code in
82                 # 'setup.py trial'
83
84     if not cmd:
85         print "Can't find twistd (it comes with Twisted).  Aborting."
86         sys.exit(1)
87
88     cmd.extend(["-y", tac])
89     if opts["syslog"]:
90         cmd.append("--syslog")
91     elif nodetype in ("client", "introducer"):
92         fileutil.make_dirs(os.path.join(basedir, "logs"))
93         cmd.extend(["--logfile", os.path.join("logs", "twistd.log")])
94     if opts["profile"]:
95         cmd.extend(["--profile=profiling_results.prof", "--savestats",])
96     curdir = os.getcwd()
97     try:
98         os.chdir(basedir)
99         rc = os.system(' '.join(cmd))
100     finally:
101         os.chdir(curdir)
102     if rc == 0:
103         print >>out, "%s node probably started" % nodetype
104         return 0
105     else:
106         print >>err, "%s node probably not started" % nodetype
107         return 1
108
109 def do_stop(basedir, out=sys.stdout, err=sys.stderr):
110     print >>out, "STOPPING", basedir
111     pidfile = os.path.join(basedir, "twistd.pid")
112     if not os.path.exists(pidfile):
113         print >>err, "%s does not look like a running node directory (no twistd.pid)" % basedir
114         # we define rc=2 to mean "nothing is running, but it wasn't me who
115         # stopped it"
116         return 2
117     pid = open(pidfile, "r").read()
118     pid = int(pid)
119
120     # kill it hard (SIGKILL), delete the twistd.pid file, then wait for the
121     # process itself to go away. If it hasn't gone away after 20 seconds, warn
122     # the user but keep waiting until they give up.
123     try:
124         os.kill(pid, signal.SIGKILL)
125     except OSError, oserr:
126         if oserr.errno == 3:
127             print oserr.strerror
128             # the process didn't exist, so wipe the pid file
129             os.remove(pidfile)
130             return 2
131         else:
132             raise
133     try:
134         os.remove(pidfile)
135     except EnvironmentError:
136         pass
137     start = time.time()
138     time.sleep(0.1)
139     wait = 40
140     first_time = True
141     while True:
142         # poll once per second until we see the process is no longer running
143         try:
144             os.kill(pid, 0)
145         except OSError:
146             print >>out, "process %d is dead" % pid
147             return
148         wait -= 1
149         if wait < 0:
150             if first_time:
151                 print >>err, ("It looks like pid %d is still running "
152                               "after %d seconds" % (pid,
153                                                     (time.time() - start)))
154                 print >>err, "I will keep watching it until you interrupt me."
155                 wait = 10
156                 first_time = False
157             else:
158                 print >>err, "pid %d still running after %d seconds" % \
159                       (pid, (time.time() - start))
160                 wait = 10
161         time.sleep(1)
162     # we define rc=1 to mean "I think something is still running, sorry"
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:
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     }