import os, sys, signal, time
-from twisted.python import usage
-from allmydata.scripts.common import BasedirMixin
+from allmydata.scripts.common import BasedirOptions
from allmydata.util import fileutil
-from twisted.python.procutils import which
+from allmydata.util.assertutil import precondition
+from allmydata.util.encodingutil import listdir_unicode, quote_output
-class StartOptions(BasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "which directory to start the node in"],
- ]
+
+class StartOptions(BasedirOptions):
optFlags = [
- ["profile", "p", "whether to run under the Python profiler, putting results in \"profiling_results.prof\""],
+ ["profile", "p", "Run under the Python profiler, putting results in 'profiling_results.prof'."],
+ ["syslog", None, "Tell the node to log to syslog, not a file."],
]
-class StopOptions(BasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "which directory to stop the node in"],
- ]
+ def getSynopsis(self):
+ return "Usage: %s [global-opts] start [options] [NODEDIR]" % (self.command_name,)
-class RestartOptions(BasedirMixin, usage.Options):
- optParameters = [
- ["basedir", "C", None, "which directory to restart the node in"],
- ]
+
+class StopOptions(BasedirOptions):
+ def getSynopsis(self):
+ return "Usage: %s [global-opts] stop [options] [NODEDIR]" % (self.command_name,)
+
+
+class RestartOptions(BasedirOptions):
optFlags = [
- ["force", "f", "if the node is not already running, start it "
- "instead of complaining that you should have used 'start' instead "
- "of 'restart'"],
+ ["profile", "p", "Run under the Python profiler, putting results in 'profiling_results.prof'."],
+ ["syslog", None, "Tell the node to log to syslog, not a file."],
]
-def do_start(basedir, profile=False, out=sys.stdout, err=sys.stderr):
- print >>out, "STARTING", basedir
- if os.path.exists(os.path.join(basedir, "client.tac")):
- tac = "client.tac"
- type = "client"
- elif os.path.exists(os.path.join(basedir, "introducer.tac")):
- tac = "introducer.tac"
- type = "introducer"
- else:
- print >>err, "%s does not look like a node directory" % basedir
- if not os.path.isdir(basedir):
- print >>err, " in fact, it doesn't look like a directory at all!"
+ def getSynopsis(self):
+ return "Usage: %s [global-opts] restart [options] [NODEDIR]" % (self.command_name,)
+
+
+class RunOptions(BasedirOptions):
+ default_nodedir = u"."
+
+ def getSynopsis(self):
+ return "Usage: %s [global-opts] run [options] [NODEDIR]" % (self.command_name,)
+
+
+def start(opts, out=sys.stdout, err=sys.stderr):
+ basedir = opts['basedir']
+ print >>out, "STARTING", quote_output(basedir)
+ if not os.path.isdir(basedir):
+ print >>err, "%s does not look like a directory at all" % quote_output(basedir)
return 1
- twistds = which("twistd")
- twistd = twistds and twistds[0]
- if not twistd:
- twistd = os.path.join(sys.prefix, 'Scripts', 'twistd.py')
- if not os.path.exists(twistd):
- print "Can't find twistd (it comes with Twisted). Aborting."
- sys.exit(1)
- path, ext = os.path.splitext(twistd)
- if ext.lower() in [".exe", ".bat",]:
- cmd = [twistd,]
- else:
- cmd = [sys.executable, twistd,]
-
- fileutil.make_dirs(os.path.join(basedir, "logs"))
- cmd.extend(["-y", tac, "--logfile", os.path.join("logs", "twistd.log")])
- if profile:
- cmd.extend(["--profile=profiling_results.prof", "--savestats",])
- curdir = os.getcwd()
- try:
- os.chdir(basedir)
- rc = os.system(' '.join(cmd))
- finally:
- os.chdir(curdir)
- if rc == 0:
- print >>out, "%s node probably started" % type
- return 0
+ for fn in listdir_unicode(basedir):
+ if fn.endswith(u".tac"):
+ tac = str(fn)
+ break
else:
- print >>err, "%s node probably not started" % type
+ print >>err, "%s does not look like a node directory (no .tac file)" % quote_output(basedir)
return 1
+ if "client" in tac:
+ nodetype = "client"
+ elif "introducer" in tac:
+ nodetype = "introducer"
+ else:
+ nodetype = "unknown (%s)" % tac
-def do_stop(basedir, out=sys.stdout, err=sys.stderr):
- print >>out, "STOPPING", basedir
+ args = ["twistd", "-y", tac]
+ if opts["syslog"]:
+ args.append("--syslog")
+ elif nodetype in ("client", "introducer"):
+ fileutil.make_dirs(os.path.join(basedir, "logs"))
+ args.extend(["--logfile", os.path.join("logs", "twistd.log")])
+ if opts["profile"]:
+ args.extend(["--profile=profiling_results.prof", "--savestats",])
+ # now we're committed
+ os.chdir(basedir)
+ from twisted.scripts import twistd
+ sys.argv = args
+ twistd.run()
+ # run() doesn't return: the parent does os._exit(0) in daemonize(), so
+ # we'll never get here. If application setup fails (e.g. ImportError),
+ # run() will raise an exception.
+
+def stop(config, out=sys.stdout, err=sys.stderr):
+ basedir = config['basedir']
+ print >>out, "STOPPING", quote_output(basedir)
pidfile = os.path.join(basedir, "twistd.pid")
if not os.path.exists(pidfile):
- print >>err, "%s does not look like a running node directory (no twistd.pid)" % basedir
+ print >>err, "%s does not look like a running node directory (no twistd.pid)" % quote_output(basedir)
+ # we define rc=2 to mean "nothing is running, but it wasn't me who
+ # stopped it"
return 2
pid = open(pidfile, "r").read()
pid = int(pid)
- timer = 0
+ # kill it hard (SIGKILL), delete the twistd.pid file, then wait for the
+ # process itself to go away. If it hasn't gone away after 20 seconds, warn
+ # the user but keep waiting until they give up.
try:
- os.kill(pid, signal.SIGINT)
+ os.kill(pid, signal.SIGKILL)
except OSError, oserr:
if oserr.errno == 3:
print oserr.strerror
- return 1
+ # the process didn't exist, so wipe the pid file
+ os.remove(pidfile)
+ return 2
else:
raise
+ try:
+ os.remove(pidfile)
+ except EnvironmentError:
+ pass
+ start = time.time()
time.sleep(0.1)
- while timer < 5:
- # poll once per second until twistd.pid goes away, up to 5 seconds
+ wait = 40
+ first_time = True
+ while True:
+ # poll once per second until we see the process is no longer running
try:
os.kill(pid, 0)
except OSError:
print >>out, "process %d is dead" % pid
return
- timer += 1
+ wait -= 1
+ if wait < 0:
+ if first_time:
+ print >>err, ("It looks like pid %d is still running "
+ "after %d seconds" % (pid,
+ (time.time() - start)))
+ print >>err, "I will keep watching it until you interrupt me."
+ wait = 10
+ first_time = False
+ else:
+ print >>err, "pid %d still running after %d seconds" % \
+ (pid, (time.time() - start))
+ wait = 10
time.sleep(1)
- print >>err, "never saw process go away"
+ # we define rc=1 to mean "I think something is still running, sorry"
return 1
-def start(config, stdout, stderr):
- rc = 0
- for basedir in config['basedirs']:
- rc = do_start(basedir, config['profile'], stdout, stderr) or rc
- return rc
-
-def stop(config, stdout, stderr):
- rc = 0
- for basedir in config['basedirs']:
- rc = do_stop(basedir, stdout, stderr) or rc
- return rc
-
def restart(config, stdout, stderr):
- rc = 0
- for basedir in config['basedirs']:
- rc = do_stop(basedir, stdout, stderr) or rc
- if rc == 2 and config['force']:
+ rc = stop(config, stdout, stderr)
+ if rc == 2:
print >>stderr, "ignoring couldn't-stop"
rc = 0
if rc:
print >>stderr, "not restarting"
return rc
- for basedir in config['basedirs']:
- rc = do_start(basedir, stdout, stderr) or rc
- return rc
+ return start(config, stdout, stderr)
+
+def run(config, stdout, stderr):
+ from twisted.internet import reactor
+ from twisted.python import log, logfile
+ from allmydata import client
+
+ basedir = config['basedir']
+ precondition(isinstance(basedir, unicode), basedir)
+
+ if not os.path.isdir(basedir):
+ print >>stderr, "%s does not look like a directory at all" % quote_output(basedir)
+ return 1
+ for fn in listdir_unicode(basedir):
+ if fn.endswith(u".tac"):
+ tac = str(fn)
+ break
+ else:
+ print >>stderr, "%s does not look like a node directory (no .tac file)" % quote_output(basedir)
+ return 1
+ if "client" not in tac:
+ print >>stderr, ("%s looks like it contains a non-client node (%s).\n"
+ "Use 'tahoe start' instead of 'tahoe run'."
+ % (quote_output(basedir), tac))
+ return 1
+
+ os.chdir(basedir)
+
+ # set up twisted logging. this will become part of the node rsn.
+ logdir = os.path.join(basedir, 'logs')
+ if not os.path.exists(logdir):
+ os.makedirs(logdir)
+ lf = logfile.LogFile('tahoesvc.log', logdir)
+ log.startLogging(lf)
+
+ # run the node itself
+ c = client.Client(basedir)
+ reactor.callLater(0, c.startService) # after reactor startup
+ reactor.run()
+
+ return 0
subCommands = [
["start", None, StartOptions, "Start a node (of any type)."],
["stop", None, StopOptions, "Stop a node."],
["restart", None, RestartOptions, "Restart a node."],
+ ["run", None, RunOptions, "Run a node synchronously."],
]
dispatch = {
"start": start,
"stop": stop,
"restart": restart,
+ "run": run,
}