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