]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/startstop_node.py
5045bd6136d58f698a4b7312436c098ebde58ec2
[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
5 from allmydata.util.assertutil import precondition
6 from allmydata.util.encodingutil import listdir_unicode, quote_output
7
8
9 class StartOptions(BasedirMixin, BaseOptions):
10     optFlags = [
11         ["profile", "p", "Run under the Python profiler, putting results in 'profiling_results.prof'."],
12         ["syslog", None, "Tell the node to log to syslog, not a file."],
13         ]
14
15     def getSynopsis(self):
16         return "Usage:  %s start [options] [NODEDIR]" % (self.command_name,)
17
18
19 class StopOptions(BasedirMixin, BaseOptions):
20     def getSynopsis(self):
21         return "Usage:  %s stop [options] [NODEDIR]" % (self.command_name,)
22
23
24 class RestartOptions(BasedirMixin, BaseOptions):
25     optFlags = [
26         ["profile", "p", "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     def getSynopsis(self):
31         return "Usage:  %s restart [options] [NODEDIR]" % (self.command_name,)
32
33
34 class RunOptions(BasedirMixin, BaseOptions):
35     default_nodedir = u"."
36
37     optParameters = [
38         ["node-directory", "d", None, "Specify the directory of the node to be run. [default, for 'tahoe run' only: current directory]"],
39     ]
40
41     def getSynopsis(self):
42         return "Usage:  %s run [options] [NODEDIR]" % (self.command_name,)
43
44
45 def start(opts, out=sys.stdout, err=sys.stderr):
46     basedir = opts['basedir']
47     print >>out, "STARTING", quote_output(basedir)
48     if not os.path.isdir(basedir):
49         print >>err, "%s does not look like a directory at all" % quote_output(basedir)
50         return 1
51     for fn in listdir_unicode(basedir):
52         if fn.endswith(u".tac"):
53             tac = str(fn)
54             break
55     else:
56         print >>err, "%s does not look like a node directory (no .tac file)" % quote_output(basedir)
57         return 1
58     if "client" in tac:
59         nodetype = "client"
60     elif "introducer" in tac:
61         nodetype = "introducer"
62     else:
63         nodetype = "unknown (%s)" % tac
64
65     args = ["twistd", "-y", tac]
66     if opts["syslog"]:
67         args.append("--syslog")
68     elif nodetype in ("client", "introducer"):
69         fileutil.make_dirs(os.path.join(basedir, "logs"))
70         args.extend(["--logfile", os.path.join("logs", "twistd.log")])
71     if opts["profile"]:
72         args.extend(["--profile=profiling_results.prof", "--savestats",])
73     # now we're committed
74     os.chdir(basedir)
75     from twisted.scripts import twistd
76     sys.argv = args
77     twistd.run()
78     # run() doesn't return: the parent does os._exit(0) in daemonize(), so
79     # we'll never get here. If application setup fails (e.g. ImportError),
80     # run() will raise an exception.
81
82 def stop(config, out=sys.stdout, err=sys.stderr):
83     basedir = config['basedir']
84     print >>out, "STOPPING", quote_output(basedir)
85     pidfile = os.path.join(basedir, "twistd.pid")
86     if not os.path.exists(pidfile):
87         print >>err, "%s does not look like a running node directory (no twistd.pid)" % quote_output(basedir)
88         # we define rc=2 to mean "nothing is running, but it wasn't me who
89         # stopped it"
90         return 2
91     pid = open(pidfile, "r").read()
92     pid = int(pid)
93
94     # kill it hard (SIGKILL), delete the twistd.pid file, then wait for the
95     # process itself to go away. If it hasn't gone away after 20 seconds, warn
96     # the user but keep waiting until they give up.
97     try:
98         os.kill(pid, signal.SIGKILL)
99     except OSError, oserr:
100         if oserr.errno == 3:
101             print oserr.strerror
102             # the process didn't exist, so wipe the pid file
103             os.remove(pidfile)
104             return 2
105         else:
106             raise
107     try:
108         os.remove(pidfile)
109     except EnvironmentError:
110         pass
111     start = time.time()
112     time.sleep(0.1)
113     wait = 40
114     first_time = True
115     while True:
116         # poll once per second until we see the process is no longer running
117         try:
118             os.kill(pid, 0)
119         except OSError:
120             print >>out, "process %d is dead" % pid
121             return
122         wait -= 1
123         if wait < 0:
124             if first_time:
125                 print >>err, ("It looks like pid %d is still running "
126                               "after %d seconds" % (pid,
127                                                     (time.time() - start)))
128                 print >>err, "I will keep watching it until you interrupt me."
129                 wait = 10
130                 first_time = False
131             else:
132                 print >>err, "pid %d still running after %d seconds" % \
133                       (pid, (time.time() - start))
134                 wait = 10
135         time.sleep(1)
136     # we define rc=1 to mean "I think something is still running, sorry"
137     return 1
138
139 def restart(config, stdout, stderr):
140     rc = stop(config, stdout, stderr)
141     if rc == 2:
142         print >>stderr, "ignoring couldn't-stop"
143         rc = 0
144     if rc:
145         print >>stderr, "not restarting"
146         return rc
147     return start(config, stdout, stderr)
148
149 def run(config, stdout, stderr):
150     from twisted.internet import reactor
151     from twisted.python import log, logfile
152     from allmydata import client
153
154     basedir = config['basedir']
155     precondition(isinstance(basedir, unicode), basedir)
156
157     if not os.path.isdir(basedir):
158         print >>stderr, "%s does not look like a directory at all" % quote_output(basedir)
159         return 1
160     for fn in listdir_unicode(basedir):
161         if fn.endswith(u".tac"):
162             tac = str(fn)
163             break
164     else:
165         print >>stderr, "%s does not look like a node directory (no .tac file)" % quote_output(basedir)
166         return 1
167     if "client" not in tac:
168         print >>stderr, ("%s looks like it contains a non-client node (%s).\n"
169                          "Use 'tahoe start' instead of 'tahoe run'."
170                          % (quote_output(basedir), tac))
171         return 1
172
173     os.chdir(basedir)
174
175     # set up twisted logging. this will become part of the node rsn.
176     logdir = os.path.join(basedir, 'logs')
177     if not os.path.exists(logdir):
178         os.makedirs(logdir)
179     lf = logfile.LogFile('tahoesvc.log', logdir)
180     log.startLogging(lf)
181
182     # run the node itself
183     c = client.Client(basedir)
184     reactor.callLater(0, c.startService) # after reactor startup
185     reactor.run()
186
187     return 0
188
189
190 subCommands = [
191     ["start", None, StartOptions, "Start a node (of any type)."],
192     ["stop", None, StopOptions, "Stop a node."],
193     ["restart", None, RestartOptions, "Restart a node."],
194     ["run", None, RunOptions, "Run a node synchronously."],
195 ]
196
197 dispatch = {
198     "start": start,
199     "stop": stop,
200     "restart": restart,
201     "run": run,
202     }