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