2 import os, sys, signal, time
3 from allmydata.scripts.common import BasedirOptions
4 from twisted.scripts import twistd
5 from twisted.python import usage
6 from allmydata.util import fileutil
7 from allmydata.util.encodingutil import listdir_unicode, quote_output
10 class StartOptions(BasedirOptions):
11 def parseArgs(self, basedir=None, *twistd_args):
12 # this can't handle e.g. 'tahoe start --nodaemon', since then
13 # --nodaemon looks like a basedir. So you can either use 'tahoe
14 # start' or 'tahoe start BASEDIR --TWISTD-OPTIONS'.
15 BasedirOptions.parseArgs(self, basedir)
16 self.twistd_args = twistd_args
18 def getSynopsis(self):
19 return "Usage: %s [global-opts] start [options] [NODEDIR]" % (self.command_name,)
22 class StopOptions(BasedirOptions):
23 def getSynopsis(self):
24 return "Usage: %s [global-opts] stop [options] [NODEDIR]" % (self.command_name,)
27 class RestartOptions(StartOptions):
28 def getSynopsis(self):
29 return "Usage: %s [global-opts] restart [options] [NODEDIR]" % (self.command_name,)
32 class RunOptions(StartOptions):
33 def getSynopsis(self):
34 return "Usage: %s [global-opts] run [options] [NODEDIR]" % (self.command_name,)
37 class MyTwistdConfig(twistd.ServerOptions):
38 subCommands = [("XYZ", None, usage.Options, "node")]
40 class NodeStartingPlugin:
42 def __init__(self, nodetype, basedir):
43 self.nodetype = nodetype
44 self.basedir = basedir
45 def makeService(self, so):
46 # delay this import as late as possible, to allow twistd's code to
47 # accept --reactor= selection. N.B.: this can't actually work until
48 # this file, and all the __init__.py files above it, also respect the
49 # prohibition on importing anything that transitively imports
50 # twisted.internet.reactor . That will take a lot of work.
51 if self.nodetype == "client":
52 from allmydata.client import Client
53 return Client(self.basedir)
54 if self.nodetype == "introducer":
55 from allmydata.introducer.server import IntroducerNode
56 return IntroducerNode(self.basedir)
57 if self.nodetype == "key-generator":
58 from allmydata.key_generator import KeyGeneratorService
59 return KeyGeneratorService(default_key_size=2048)
60 if self.nodetype == "stats-gatherer":
61 from allmydata.stats import StatsGathererService
62 return StatsGathererService(verbose=True)
63 raise ValueError("unknown nodetype %s" % self.nodetype)
65 def identify_node_type(basedir):
66 for fn in listdir_unicode(basedir):
67 if fn.endswith(u".tac"):
73 for t in ("client", "introducer", "key-generator", "stats-gatherer"):
78 def start(config, out=sys.stdout, err=sys.stderr):
79 basedir = config['basedir']
80 print >>out, "STARTING", quote_output(basedir)
81 if not os.path.isdir(basedir):
82 print >>err, "%s does not look like a directory at all" % quote_output(basedir)
84 nodetype = identify_node_type(basedir)
86 print >>err, "%s is not a recognizable node directory" % quote_output(basedir)
88 # Now prepare to turn into a twistd process. This os.chdir is the point
92 if (nodetype in ("client", "introducer")
93 and "--nodaemon" not in config.twistd_args
94 and "--syslog" not in config.twistd_args
95 and "--logfile" not in config.twistd_args):
96 fileutil.make_dirs(os.path.join(basedir, "logs"))
97 twistd_args.extend(["--logfile", os.path.join("logs", "twistd.log")])
98 twistd_args.extend(config.twistd_args)
99 twistd_args.append("XYZ") # point at our NodeStartingPlugin
101 twistd_config = MyTwistdConfig()
103 twistd_config.parseOptions(twistd_args)
104 except usage.error, ue:
105 # these arguments were unsuitable for 'twistd'
106 print >>err, twistd_config
107 print >>err, "tahoe start: %s" % (config.subCommand, ue)
109 twistd_config.loadedPlugins = {"XYZ": NodeStartingPlugin(nodetype, basedir)}
111 # On Unix-like platforms:
112 # Unless --nodaemon was provided, the twistd.runApp() below spawns off a
113 # child process, and the parent calls os._exit(0), so there's no way for
114 # us to get control afterwards, even with 'except SystemExit'. If
115 # application setup fails (e.g. ImportError), runApp() will raise an
118 # So if we wanted to do anything with the running child, we'd have two
121 # * fork first, and have our child wait for the runApp() child to get
122 # running. (note: just fork(). This is easier than fork+exec, since we
123 # don't have to get PATH and PYTHONPATH set up, since we're not
124 # starting a *different* process, just cloning a new instance of the
126 # * or have the user run a separate command some time after this one
129 # For Tahoe, we don't need to do anything with the child, so we can just
133 # twistd does not fork; it just runs in the current process whether or not
134 # --nodaemon is specified. (As on Unix, --nodaemon does have the side effect
135 # of causing us to log to stdout/stderr.)
137 if "--nodaemon" in twistd_args or sys.platform == "win32":
142 print >>out, "%s node in %s" % (verb, basedir)
143 twistd.runApp(twistd_config)
144 # we should only reach here if --nodaemon or equivalent was used
147 def stop(config, out=sys.stdout, err=sys.stderr):
148 basedir = config['basedir']
149 print >>out, "STOPPING", quote_output(basedir)
150 pidfile = os.path.join(basedir, "twistd.pid")
151 if not os.path.exists(pidfile):
152 print >>err, "%s does not look like a running node directory (no twistd.pid)" % quote_output(basedir)
153 # we define rc=2 to mean "nothing is running, but it wasn't me who
156 pid = open(pidfile, "r").read()
159 # kill it hard (SIGKILL), delete the twistd.pid file, then wait for the
160 # process itself to go away. If it hasn't gone away after 20 seconds, warn
161 # the user but keep waiting until they give up.
163 os.kill(pid, signal.SIGKILL)
164 except OSError, oserr:
167 # the process didn't exist, so wipe the pid file
174 except EnvironmentError:
181 # poll once per second until we see the process is no longer running
185 print >>out, "process %d is dead" % pid
190 print >>err, ("It looks like pid %d is still running "
191 "after %d seconds" % (pid,
192 (time.time() - start)))
193 print >>err, "I will keep watching it until you interrupt me."
197 print >>err, "pid %d still running after %d seconds" % \
198 (pid, (time.time() - start))
201 # we define rc=1 to mean "I think something is still running, sorry"
204 def restart(config, stdout, stderr):
205 rc = stop(config, stdout, stderr)
207 print >>stderr, "ignoring couldn't-stop"
210 print >>stderr, "not restarting"
212 return start(config, stdout, stderr)
214 def run(config, stdout, stderr):
215 config.twistd_args = config.twistd_args + ("--nodaemon",)
216 # Previously we would do the equivalent of adding ("--logfile", "tahoesvc.log"),
217 # but that redirects stdout/stderr which is often unhelpful, and the user can
218 # add that option explicitly if they want.
220 return start(config, stdout, stderr)
224 ["start", None, StartOptions, "Start a node (of any type)."],
225 ["stop", None, StopOptions, "Stop a node."],
226 ["restart", None, RestartOptions, "Restart a node."],
227 ["run", None, RunOptions, "Run a node synchronously."],