2019-03-24 13:09:10 +00:00
|
|
|
from __future__ import print_function
|
2017-04-12 20:52:09 +00:00
|
|
|
|
|
|
|
import os, sys
|
|
|
|
from allmydata.scripts.common import BasedirOptions
|
|
|
|
from twisted.scripts import twistd
|
|
|
|
from twisted.python import usage
|
|
|
|
from twisted.python.reflect import namedAny
|
2018-01-28 01:05:16 +00:00
|
|
|
from twisted.internet.defer import maybeDeferred, fail
|
|
|
|
from twisted.application.service import Service
|
|
|
|
|
2017-04-12 20:52:09 +00:00
|
|
|
from allmydata.scripts.default_nodedir import _default_nodedir
|
|
|
|
from allmydata.util import fileutil
|
2017-09-06 01:08:35 +00:00
|
|
|
from allmydata.node import read_config
|
2017-04-12 20:52:09 +00:00
|
|
|
from allmydata.util.encodingutil import listdir_unicode, quote_local_unicode_path
|
2018-05-29 21:51:59 +00:00
|
|
|
from allmydata.util.configutil import UnknownConfigError
|
2018-01-28 01:05:16 +00:00
|
|
|
from allmydata.util.deferredutil import HookMixin
|
2017-04-12 20:52:09 +00:00
|
|
|
|
|
|
|
|
2017-11-08 20:30:00 +00:00
|
|
|
def get_pidfile(basedir):
|
|
|
|
"""
|
|
|
|
Returns the path to the PID file.
|
|
|
|
:param basedir: the node's base directory
|
|
|
|
:returns: the path to the PID file
|
|
|
|
"""
|
|
|
|
return os.path.join(basedir, u"twistd.pid")
|
|
|
|
|
|
|
|
def get_pid_from_pidfile(pidfile):
|
|
|
|
"""
|
|
|
|
Tries to read and return the PID stored in the node's PID file
|
|
|
|
(twistd.pid).
|
|
|
|
:param pidfile: try to read this PID file
|
|
|
|
:returns: A numeric PID on success, ``None`` if PID file absent or
|
|
|
|
inaccessible, ``-1`` if PID file invalid.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
with open(pidfile, "r") as f:
|
|
|
|
pid = f.read()
|
|
|
|
except EnvironmentError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
pid = int(pid)
|
|
|
|
except ValueError:
|
|
|
|
return -1
|
|
|
|
|
|
|
|
return pid
|
|
|
|
|
2017-04-12 20:52:09 +00:00
|
|
|
def identify_node_type(basedir):
|
|
|
|
"""
|
|
|
|
:return unicode: None or one of: 'client', 'introducer',
|
|
|
|
'key-generator' or 'stats-gatherer'
|
|
|
|
"""
|
|
|
|
tac = u''
|
|
|
|
try:
|
|
|
|
for fn in listdir_unicode(basedir):
|
|
|
|
if fn.endswith(u".tac"):
|
|
|
|
tac = fn
|
|
|
|
break
|
|
|
|
except OSError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
for t in (u"client", u"introducer", u"key-generator", u"stats-gatherer"):
|
|
|
|
if t in tac:
|
|
|
|
return t
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
class DaemonizeOptions(BasedirOptions):
|
|
|
|
subcommand_name = "start"
|
|
|
|
optParameters = [
|
|
|
|
("basedir", "C", None,
|
|
|
|
"Specify which Tahoe base directory should be used."
|
|
|
|
" This has the same effect as the global --node-directory option."
|
|
|
|
" [default: %s]" % quote_local_unicode_path(_default_nodedir)),
|
|
|
|
]
|
|
|
|
|
|
|
|
def parseArgs(self, basedir=None, *twistd_args):
|
|
|
|
# This can't handle e.g. 'tahoe start --nodaemon', since '--nodaemon'
|
|
|
|
# looks like an option to the tahoe subcommand, not to twistd. So you
|
|
|
|
# can either use 'tahoe start' or 'tahoe start NODEDIR
|
|
|
|
# --TWISTD-OPTIONS'. Note that 'tahoe --node-directory=NODEDIR start
|
|
|
|
# --TWISTD-OPTIONS' also isn't allowed, unfortunately.
|
|
|
|
|
|
|
|
BasedirOptions.parseArgs(self, basedir)
|
|
|
|
self.twistd_args = twistd_args
|
|
|
|
|
|
|
|
def getSynopsis(self):
|
|
|
|
return ("Usage: %s [global-options] %s [options]"
|
|
|
|
" [NODEDIR [twistd-options]]"
|
|
|
|
% (self.command_name, self.subcommand_name))
|
|
|
|
|
|
|
|
def getUsage(self, width=None):
|
|
|
|
t = BasedirOptions.getUsage(self, width) + "\n"
|
|
|
|
twistd_options = str(MyTwistdConfig()).partition("\n")[2].partition("\n\n")[0]
|
|
|
|
t += twistd_options.replace("Options:", "twistd-options:", 1)
|
|
|
|
t += """
|
|
|
|
|
|
|
|
Note that if any twistd-options are used, NODEDIR must be specified explicitly
|
|
|
|
(not by default or using -C/--basedir or -d/--node-directory), and followed by
|
|
|
|
the twistd-options.
|
|
|
|
"""
|
|
|
|
return t
|
|
|
|
|
|
|
|
|
|
|
|
class MyTwistdConfig(twistd.ServerOptions):
|
|
|
|
subCommands = [("DaemonizeTahoeNode", None, usage.Options, "node")]
|
|
|
|
|
2019-06-26 14:59:39 +00:00
|
|
|
stderr = sys.stderr
|
|
|
|
|
2017-04-12 20:52:09 +00:00
|
|
|
|
2018-01-28 01:05:16 +00:00
|
|
|
class DaemonizeTheRealService(Service, HookMixin):
|
|
|
|
"""
|
|
|
|
this HookMixin should really be a helper; our hooks:
|
|
|
|
|
|
|
|
- 'running': triggered when startup has completed; it triggers
|
|
|
|
with None of successful or a Failure otherwise.
|
|
|
|
"""
|
2019-06-19 16:40:49 +00:00
|
|
|
stderr = sys.stderr
|
2017-04-12 20:52:09 +00:00
|
|
|
|
|
|
|
def __init__(self, nodetype, basedir, options):
|
2018-01-28 01:05:16 +00:00
|
|
|
super(DaemonizeTheRealService, self).__init__()
|
2017-04-12 20:52:09 +00:00
|
|
|
self.nodetype = nodetype
|
|
|
|
self.basedir = basedir
|
2018-01-28 01:05:16 +00:00
|
|
|
# setup for HookMixin
|
|
|
|
self._hooks = {
|
|
|
|
"running": None,
|
|
|
|
}
|
2019-06-26 14:59:39 +00:00
|
|
|
self.stderr = options.parent.stderr
|
2017-04-12 20:52:09 +00:00
|
|
|
|
|
|
|
def startService(self):
|
|
|
|
|
|
|
|
def key_generator_removed():
|
2018-01-28 01:05:16 +00:00
|
|
|
return fail(ValueError("key-generator support removed, see #2783"))
|
2017-04-12 20:52:09 +00:00
|
|
|
|
|
|
|
def start():
|
|
|
|
node_to_instance = {
|
2018-01-28 01:05:16 +00:00
|
|
|
u"client": lambda: maybeDeferred(namedAny("allmydata.client.create_client"), self.basedir),
|
|
|
|
u"introducer": lambda: maybeDeferred(namedAny("allmydata.introducer.server.create_introducer"), self.basedir),
|
|
|
|
u"stats-gatherer": lambda: maybeDeferred(namedAny("allmydata.stats.StatsGathererService"), read_config(self.basedir, None), self.basedir, verbose=True),
|
2017-04-12 20:52:09 +00:00
|
|
|
u"key-generator": key_generator_removed,
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
service_factory = node_to_instance[self.nodetype]
|
|
|
|
except KeyError:
|
|
|
|
raise ValueError("unknown nodetype %s" % self.nodetype)
|
|
|
|
|
2018-01-28 01:05:16 +00:00
|
|
|
def handle_config_error(fail):
|
2019-06-19 16:40:49 +00:00
|
|
|
if fail.check(UnknownConfigError):
|
|
|
|
self.stderr.write("\nConfiguration error:\n{}\n\n".format(fail.value))
|
|
|
|
else:
|
|
|
|
self.stderr.write("\nUnknown error\n")
|
|
|
|
fail.printTraceback(self.stderr)
|
2018-05-29 21:51:59 +00:00
|
|
|
reactor.stop()
|
2018-01-28 01:05:16 +00:00
|
|
|
|
|
|
|
d = service_factory()
|
|
|
|
|
|
|
|
def created(srv):
|
|
|
|
srv.setServiceParent(self.parent)
|
|
|
|
d.addCallback(created)
|
|
|
|
d.addErrback(handle_config_error)
|
|
|
|
d.addBoth(self._call_hook, 'running')
|
|
|
|
return d
|
2017-04-12 20:52:09 +00:00
|
|
|
|
|
|
|
from twisted.internet import reactor
|
2018-01-30 06:56:19 +00:00
|
|
|
reactor.callWhenRunning(start)
|
2017-04-12 20:52:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
class DaemonizeTahoeNodePlugin(object):
|
|
|
|
tapname = "tahoenode"
|
|
|
|
def __init__(self, nodetype, basedir):
|
|
|
|
self.nodetype = nodetype
|
|
|
|
self.basedir = basedir
|
|
|
|
|
|
|
|
def makeService(self, so):
|
|
|
|
return DaemonizeTheRealService(self.nodetype, self.basedir, so)
|
|
|
|
|
|
|
|
|
|
|
|
def daemonize(config):
|
|
|
|
"""
|
|
|
|
Runs the 'tahoe daemonize' command.
|
|
|
|
|
|
|
|
Sets up the IService instance corresponding to the type of node
|
|
|
|
that's starting and uses Twisted's twistd runner to disconnect our
|
|
|
|
process from the terminal.
|
|
|
|
"""
|
|
|
|
out = config.stdout
|
|
|
|
err = config.stderr
|
|
|
|
basedir = config['basedir']
|
|
|
|
quoted_basedir = quote_local_unicode_path(basedir)
|
2019-03-24 13:09:10 +00:00
|
|
|
print("daemonizing in {}".format(quoted_basedir), file=out)
|
2017-04-12 20:52:09 +00:00
|
|
|
if not os.path.isdir(basedir):
|
2019-03-24 13:09:10 +00:00
|
|
|
print("%s does not look like a directory at all" % quoted_basedir, file=err)
|
2017-04-12 20:52:09 +00:00
|
|
|
return 1
|
|
|
|
nodetype = identify_node_type(basedir)
|
|
|
|
if not nodetype:
|
2019-03-24 13:09:10 +00:00
|
|
|
print("%s is not a recognizable node directory" % quoted_basedir, file=err)
|
2017-04-12 20:52:09 +00:00
|
|
|
return 1
|
|
|
|
# Now prepare to turn into a twistd process. This os.chdir is the point
|
|
|
|
# of no return.
|
|
|
|
os.chdir(basedir)
|
|
|
|
twistd_args = []
|
|
|
|
if (nodetype in (u"client", u"introducer")
|
|
|
|
and "--nodaemon" not in config.twistd_args
|
|
|
|
and "--syslog" not in config.twistd_args
|
|
|
|
and "--logfile" not in config.twistd_args):
|
|
|
|
fileutil.make_dirs(os.path.join(basedir, u"logs"))
|
|
|
|
twistd_args.extend(["--logfile", os.path.join("logs", "twistd.log")])
|
|
|
|
twistd_args.extend(config.twistd_args)
|
|
|
|
twistd_args.append("DaemonizeTahoeNode") # point at our DaemonizeTahoeNodePlugin
|
|
|
|
|
|
|
|
twistd_config = MyTwistdConfig()
|
2019-06-26 14:59:39 +00:00
|
|
|
twistd_config.stdout = out
|
|
|
|
twistd_config.stderr = err
|
2017-04-12 20:52:09 +00:00
|
|
|
try:
|
|
|
|
twistd_config.parseOptions(twistd_args)
|
2019-03-28 11:45:28 +00:00
|
|
|
except usage.error as ue:
|
2017-04-12 20:52:09 +00:00
|
|
|
# these arguments were unsuitable for 'twistd'
|
2019-03-24 13:09:10 +00:00
|
|
|
print(config, file=err)
|
|
|
|
print("tahoe %s: usage error from twistd: %s\n" % (config.subcommand_name, ue), file=err)
|
2017-04-12 20:52:09 +00:00
|
|
|
return 1
|
|
|
|
twistd_config.loadedPlugins = {"DaemonizeTahoeNode": DaemonizeTahoeNodePlugin(nodetype, basedir)}
|
|
|
|
|
2017-08-24 12:56:00 +00:00
|
|
|
# handle invalid PID file (twistd might not start otherwise)
|
|
|
|
pidfile = get_pidfile(basedir)
|
|
|
|
if get_pid_from_pidfile(pidfile) == -1:
|
2019-03-24 13:09:10 +00:00
|
|
|
print("found invalid PID file in %s - deleting it" % basedir, file=err)
|
2017-08-24 12:56:00 +00:00
|
|
|
os.remove(pidfile)
|
|
|
|
|
2017-04-12 20:52:09 +00:00
|
|
|
# On Unix-like platforms:
|
|
|
|
# Unless --nodaemon was provided, the twistd.runApp() below spawns off a
|
|
|
|
# child process, and the parent calls os._exit(0), so there's no way for
|
|
|
|
# us to get control afterwards, even with 'except SystemExit'. If
|
|
|
|
# application setup fails (e.g. ImportError), runApp() will raise an
|
|
|
|
# exception.
|
|
|
|
#
|
|
|
|
# So if we wanted to do anything with the running child, we'd have two
|
|
|
|
# options:
|
|
|
|
#
|
|
|
|
# * fork first, and have our child wait for the runApp() child to get
|
|
|
|
# running. (note: just fork(). This is easier than fork+exec, since we
|
|
|
|
# don't have to get PATH and PYTHONPATH set up, since we're not
|
|
|
|
# starting a *different* process, just cloning a new instance of the
|
|
|
|
# current process)
|
|
|
|
# * or have the user run a separate command some time after this one
|
|
|
|
# exits.
|
|
|
|
#
|
|
|
|
# For Tahoe, we don't need to do anything with the child, so we can just
|
|
|
|
# let it exit.
|
|
|
|
#
|
|
|
|
# On Windows:
|
|
|
|
# twistd does not fork; it just runs in the current process whether or not
|
|
|
|
# --nodaemon is specified. (As on Unix, --nodaemon does have the side effect
|
|
|
|
# of causing us to log to stdout/stderr.)
|
|
|
|
|
|
|
|
if "--nodaemon" in twistd_args or sys.platform == "win32":
|
|
|
|
verb = "running"
|
|
|
|
else:
|
|
|
|
verb = "starting"
|
|
|
|
|
2019-03-24 13:09:10 +00:00
|
|
|
print("%s node in %s" % (verb, quoted_basedir), file=out)
|
2017-04-12 20:52:09 +00:00
|
|
|
twistd.runApp(twistd_config)
|
|
|
|
# we should only reach here if --nodaemon or equivalent was used
|
|
|
|
return 0
|