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.scripts.default_nodedir import _default_nodedir
7 from allmydata.util import fileutil
8 from allmydata.util.encodingutil import listdir_unicode, quote_local_unicode_path
11 class StartOptions(BasedirOptions):
12 subcommand_name = "start"
14 ("basedir", "C", None,
15 "Specify which Tahoe base directory should be used."
16 " This has the same effect as the global --node-directory option."
17 " [default: %s]" % quote_local_unicode_path(_default_nodedir)),
20 def parseArgs(self, basedir=None, *twistd_args):
21 # This can't handle e.g. 'tahoe start --nodaemon', since '--nodaemon'
22 # looks like an option to the tahoe subcommand, not to twistd. So you
23 # can either use 'tahoe start' or 'tahoe start NODEDIR
24 # --TWISTD-OPTIONS'. Note that 'tahoe --node-directory=NODEDIR start
25 # --TWISTD-OPTIONS' also isn't allowed, unfortunately.
27 BasedirOptions.parseArgs(self, basedir)
28 self.twistd_args = twistd_args
30 def getSynopsis(self):
31 return ("Usage: %s [global-options] %s [options]"
32 " [NODEDIR [twistd-options]]"
33 % (self.command_name, self.subcommand_name))
35 def getUsage(self, width=None):
36 t = BasedirOptions.getUsage(self, width) + "\n"
37 twistd_options = str(MyTwistdConfig()).partition("\n")[2].partition("\n\n")[0]
38 t += twistd_options.replace("Options:", "twistd-options:", 1)
41 Note that if any twistd-options are used, NODEDIR must be specified explicitly
42 (not by default or using -C/--basedir or -d/--node-directory), and followed by
47 class StopOptions(BasedirOptions):
48 def parseArgs(self, basedir=None):
49 BasedirOptions.parseArgs(self, basedir)
51 def getSynopsis(self):
52 return ("Usage: %s [global-options] stop [options] [NODEDIR]"
53 % (self.command_name,))
55 class RestartOptions(StartOptions):
56 subcommand_name = "restart"
58 class RunOptions(StartOptions):
59 subcommand_name = "run"
62 class MyTwistdConfig(twistd.ServerOptions):
63 subCommands = [("StartTahoeNode", None, usage.Options, "node")]
65 class StartTahoeNodePlugin:
67 def __init__(self, nodetype, basedir):
68 self.nodetype = nodetype
69 self.basedir = basedir
70 def makeService(self, so):
71 # delay this import as late as possible, to allow twistd's code to
72 # accept --reactor= selection. N.B.: this can't actually work until
73 # this file, and all the __init__.py files above it, also respect the
74 # prohibition on importing anything that transitively imports
75 # twisted.internet.reactor . That will take a lot of work.
76 if self.nodetype == "client":
77 from allmydata.client import Client
78 return Client(self.basedir)
79 if self.nodetype == "introducer":
80 from allmydata.introducer.server import IntroducerNode
81 return IntroducerNode(self.basedir)
82 if self.nodetype == "key-generator":
83 from allmydata.key_generator import KeyGeneratorService
84 return KeyGeneratorService(default_key_size=2048)
85 if self.nodetype == "stats-gatherer":
86 from allmydata.stats import StatsGathererService
87 return StatsGathererService(verbose=True)
88 raise ValueError("unknown nodetype %s" % self.nodetype)
90 def identify_node_type(basedir):
91 for fn in listdir_unicode(basedir):
92 if fn.endswith(u".tac"):
98 for t in ("client", "introducer", "key-generator", "stats-gatherer"):
103 def start(config, out=sys.stdout, err=sys.stderr):
104 basedir = config['basedir']
105 quoted_basedir = quote_local_unicode_path(basedir)
106 print >>out, "STARTING", quoted_basedir
107 if not os.path.isdir(basedir):
108 print >>err, "%s does not look like a directory at all" % quoted_basedir
110 nodetype = identify_node_type(basedir)
112 print >>err, "%s is not a recognizable node directory" % quoted_basedir
114 # Now prepare to turn into a twistd process. This os.chdir is the point
118 if (nodetype in ("client", "introducer")
119 and "--nodaemon" not in config.twistd_args
120 and "--syslog" not in config.twistd_args
121 and "--logfile" not in config.twistd_args):
122 fileutil.make_dirs(os.path.join(basedir, u"logs"))
123 twistd_args.extend(["--logfile", os.path.join("logs", "twistd.log")])
124 twistd_args.extend(config.twistd_args)
125 twistd_args.append("StartTahoeNode") # point at our StartTahoeNodePlugin
127 twistd_config = MyTwistdConfig()
129 twistd_config.parseOptions(twistd_args)
130 except usage.error, ue:
131 # these arguments were unsuitable for 'twistd'
133 print >>err, "tahoe %s: usage error from twistd: %s\n" % (config.subcommand_name, ue)
135 twistd_config.loadedPlugins = {"StartTahoeNode": StartTahoeNodePlugin(nodetype, basedir)}
137 # On Unix-like platforms:
138 # Unless --nodaemon was provided, the twistd.runApp() below spawns off a
139 # child process, and the parent calls os._exit(0), so there's no way for
140 # us to get control afterwards, even with 'except SystemExit'. If
141 # application setup fails (e.g. ImportError), runApp() will raise an
144 # So if we wanted to do anything with the running child, we'd have two
147 # * fork first, and have our child wait for the runApp() child to get
148 # running. (note: just fork(). This is easier than fork+exec, since we
149 # don't have to get PATH and PYTHONPATH set up, since we're not
150 # starting a *different* process, just cloning a new instance of the
152 # * or have the user run a separate command some time after this one
155 # For Tahoe, we don't need to do anything with the child, so we can just
159 # twistd does not fork; it just runs in the current process whether or not
160 # --nodaemon is specified. (As on Unix, --nodaemon does have the side effect
161 # of causing us to log to stdout/stderr.)
163 if "--nodaemon" in twistd_args or sys.platform == "win32":
168 print >>out, "%s node in %s" % (verb, quoted_basedir)
169 twistd.runApp(twistd_config)
170 # we should only reach here if --nodaemon or equivalent was used
173 def stop(config, out=sys.stdout, err=sys.stderr):
174 basedir = config['basedir']
175 quoted_basedir = quote_local_unicode_path(basedir)
176 print >>out, "STOPPING", quoted_basedir
177 pidfile = os.path.join(basedir, u"twistd.pid")
178 if not os.path.exists(pidfile):
179 print >>err, "%s does not look like a running node directory (no twistd.pid)" % quoted_basedir
180 # we define rc=2 to mean "nothing is running, but it wasn't me who
183 pid = open(pidfile, "r").read()
186 # kill it hard (SIGKILL), delete the twistd.pid file, then wait for the
187 # process itself to go away. If it hasn't gone away after 20 seconds, warn
188 # the user but keep waiting until they give up.
190 os.kill(pid, signal.SIGKILL)
191 except OSError, oserr:
194 # the process didn't exist, so wipe the pid file
201 except EnvironmentError:
208 # poll once per second until we see the process is no longer running
212 print >>out, "process %d is dead" % pid
217 print >>err, ("It looks like pid %d is still running "
218 "after %d seconds" % (pid,
219 (time.time() - start)))
220 print >>err, "I will keep watching it until you interrupt me."
224 print >>err, "pid %d still running after %d seconds" % \
225 (pid, (time.time() - start))
228 # we define rc=1 to mean "I think something is still running, sorry"
231 def restart(config, stdout, stderr):
232 rc = stop(config, stdout, stderr)
234 print >>stderr, "ignoring couldn't-stop"
237 print >>stderr, "not restarting"
239 return start(config, stdout, stderr)
241 def run(config, stdout, stderr):
242 config.twistd_args = config.twistd_args + ("--nodaemon",)
243 # Previously we would do the equivalent of adding ("--logfile",
244 # "tahoesvc.log"), but that redirects stdout/stderr which is often
245 # unhelpful, and the user can add that option explicitly if they want.
247 return start(config, stdout, stderr)
251 ["start", None, StartOptions, "Start a node (of any type)."],
252 ["stop", None, StopOptions, "Stop a node."],
253 ["restart", None, RestartOptions, "Restart a node."],
254 ["run", None, RunOptions, "Run a node synchronously."],