]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/scripts/runner.py
CLI: put "[global-opts]" in all command synopses
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / runner.py
index d1e33fd50e79e5833a050cba9fbb0ef966f958a0..085967079d7842102a2ca3151ab22af3455549a9 100644 (file)
-#! /usr/bin/env python
 
-import os, subprocess, sys, signal, time
-from twisted.python import usage
-
-from twisted.python.procutils import which
-
-def testtwistd(loc):
-    try:
-        return subprocess.call(["python", loc,], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    except:
-        return -1
-    
-twistd = None
-if not twistd:
-    for maybetwistd in which("twistd"):
-        ret = testtwistd(maybetwistd)
-        if ret == 0:
-            twistd = maybetwistd
-            break
-
-if not twistd:
-    for maybetwistd in which("twistd.py"):
-        ret = testtwistd(maybetwistd)
-        if ret == 0:
-            twistd = maybetwistd
-            break
+import os, sys
+from cStringIO import StringIO
 
-if not twistd:
-    maybetwistd = os.path.join(sys.prefix, 'Scripts', 'twistd')
-    ret = testtwistd(maybetwistd)
-    if ret == 0:
-        twistd = maybetwistd
-
-if not twistd:
-    maybetwistd = os.path.join(sys.prefix, 'Scripts', 'twistd.py')
-    ret = testtwistd(maybetwistd)
-    if ret == 0:
-        twistd = maybetwistd
-
-if not twistd:
-    print "Can't find twistd (it comes with Twisted).  Aborting."
-    sys.exit(1)
-
-class BasedirMixin:
-    optFlags = [
-        ["multiple", "m", "allow multiple basedirs to be specified at once"],
-        ]
+from twisted.python import usage
 
-    def postOptions(self):
-        if not self.basedirs:
-            raise usage.UsageError("<basedir> parameter is required")
-        if self['basedir']:
-            del self['basedir']
-        self['basedirs'] = [os.path.abspath(os.path.expanduser(b))
-                            for b in self.basedirs]
+from allmydata.scripts.common import get_default_nodedir
+from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer, admin
+from allmydata.util.encodingutil import quote_output, get_io_encoding
 
-    def parseArgs(self, *args):
-        self.basedirs = []
-        if self['basedir']:
-            self.basedirs.append(self['basedir'])
-        if self['multiple']:
-            self.basedirs.extend(args)
-        else:
-            if len(args) == 0 and not self.basedirs:
-                self.basedirs.append(".")
-            if len(args) > 0:
-                self.basedirs.append(args[0])
-            if len(args) > 1:
-                raise usage.UsageError("I wasn't expecting so many arguments")
+def GROUP(s):
+    # Usage.parseOptions compares argv[1] against command[0], so it will
+    # effectively ignore any "subcommand" that starts with a newline. We use
+    # these to insert section headers into the --help output.
+    return [("\n" + s, None, None, None)]
 
-class NoDefaultBasedirMixin(BasedirMixin):
-    def parseArgs(self, *args):
-        # create-client won't default to --basedir=.
-        self.basedirs = []
-        if self['basedir']:
-            self.basedirs.append(self['basedir'])
-        if self['multiple']:
-            self.basedirs.extend(args)
-        else:
-            if len(args) > 0:
-                self.basedirs.append(args[0])
-            if len(args) > 1:
-                raise usage.UsageError("I wasn't expecting so many arguments")
-        if not self.basedirs:
-            raise usage.UsageError("--basedir must be provided")
 
-class StartOptions(BasedirMixin, usage.Options):
-    optParameters = [
-        ["basedir", "C", None, "which directory to start the node in"],
-        ]
+_default_nodedir = get_default_nodedir()
 
-class StopOptions(BasedirMixin, usage.Options):
-    optParameters = [
-        ["basedir", "C", None, "which directory to stop the node in"],
-        ]
+NODEDIR_HELP = ("Specify which Tahoe node directory should be used. The "
+                "directory should either contain a full Tahoe node, or a "
+                "file named node.url that points to some other Tahoe node. "
+                "It should also contain a file named '"
+                + os.path.join('private', 'aliases') +
+                "' which contains the mapping from alias name to root "
+                "dirnode URI.")
+if _default_nodedir:
+    NODEDIR_HELP += " [default for most commands: " + quote_output(_default_nodedir) + "]"
 
-class RestartOptions(BasedirMixin, usage.Options):
-    optParameters = [
-        ["basedir", "C", None, "which directory to restart the node in"],
-        ]
+class Options(usage.Options):
+    # unit tests can override these to point at StringIO instances
+    stdin = sys.stdin
+    stdout = sys.stdout
+    stderr = sys.stderr
+
+    synopsis = "\nUsage:  tahoe <command> [command options]"
+    subCommands = ( GROUP("Administration")
+                    +   create_node.subCommands
+                    +   keygen.subCommands
+                    +   stats_gatherer.subCommands
+                    +   admin.subCommands
+                    + GROUP("Controlling a node")
+                    +   startstop_node.subCommands
+                    + GROUP("Debugging")
+                    +   debug.subCommands
+                    + GROUP("Using the filesystem")
+                    +   cli.subCommands
+                    )
 
-class CreateClientOptions(NoDefaultBasedirMixin, usage.Options):
-    optParameters = [
-        ["basedir", "C", None, "which directory to create the client in"],
-        ]
     optFlags = [
-        ["quiet", "q", "operate silently"],
-        ]
-
-class CreateIntroducerOptions(NoDefaultBasedirMixin, usage.Options):
+        ["quiet", "q", "Operate silently."],
+        ["version", "V", "Display version numbers."],
+        ["version-and-path", None, "Display version numbers and paths to their locations."],
+    ]
     optParameters = [
-        ["basedir", "C", None, "which directory to create the introducer in"],
-        ]
-    optFlags = [
-        ["quiet", "q", "operate silently"],
-        ]
+        ["node-directory", "d", None, NODEDIR_HELP],
+    ]
 
-client_tac = """
-# -*- python -*-
+    def opt_version(self):
+        import allmydata
+        print >>self.stdout, allmydata.get_package_versions_string(debug=True)
+        self.no_command_needed = True
 
-from allmydata import client
-from twisted.application import service
+    def opt_version_and_path(self):
+        import allmydata
+        print >>self.stdout, allmydata.get_package_versions_string(show_paths=True, debug=True)
+        self.no_command_needed = True
 
-c = client.Client()
+    def getSynopsis(self):
+        return "\nUsage: tahoe [global-opts] <command> [command-options]"
 
-application = service.Application("allmydata_client")
-c.setServiceParent(application)
-"""
+    def getUsage(self, **kwargs):
+        t = usage.Options.getUsage(self, **kwargs)
+        return t + "\nPlease run 'tahoe <command> --help' for more details on each command.\n"
 
-introducer_tac = """
-# -*- python -*-
+    def postOptions(self):
+        if not hasattr(self, 'subOptions'):
+            if not hasattr(self, 'no_command_needed'):
+                raise usage.UsageError("must specify a command")
+            sys.exit(0)
 
-from allmydata import introducer_and_vdrive
-from twisted.application import service
 
-c = introducer_and_vdrive.IntroducerAndVdrive()
+create_dispatch = {}
+for module in (create_node, keygen, stats_gatherer):
+    create_dispatch.update(module.dispatch)
 
-application = service.Application("allmydata_introducer")
-c.setServiceParent(application)
-"""
+def runner(argv,
+           run_by_human=True,
+           stdin=None, stdout=None, stderr=None,
+           install_node_control=True, additional_commands=None):
 
-class Options(usage.Options):
-    synopsis = "Usage:  allmydata <command> [command options]"
+    stdin  = stdin  or sys.stdin
+    stdout = stdout or sys.stdout
+    stderr = stderr or sys.stderr
 
-    subCommands = [
-        ["create-client", None, CreateClientOptions, "Create a client node."],
-        ["create-introducer", None, CreateIntroducerOptions, "Create a introducer node."],
-        ["start", None, StartOptions, "Start a node (of any type)."],
-        ["stop", None, StopOptions, "Stop a node."],
-        ["restart", None, RestartOptions, "Restart a node."],
-        ]
+    config = Options()
+    if install_node_control:
+        config.subCommands.extend(startstop_node.subCommands)
 
-    def postOptions(self):
-        if not hasattr(self, 'subOptions'):
-            raise usage.UsageError("must specify a command")
+    ac_dispatch = {}
+    if additional_commands:
+        for ac in additional_commands:
+            config.subCommands.extend(ac.subCommands)
+            ac_dispatch.update(ac.dispatch)
 
-def runner(argv, run_by_human=True):
-    config = Options()
     try:
         config.parseOptions(argv)
     except usage.error, e:
         if not run_by_human:
             raise
-        print "%s:  %s" % (sys.argv[0], e)
-        print
-        c = getattr(config, 'subOptions', config)
-        print str(c)
+        c = config
+        while hasattr(c, 'subOptions'):
+            c = c.subOptions
+        print >>stdout, str(c)
+        try:
+            msg = e.args[0].decode(get_io_encoding())
+        except Exception:
+            msg = repr(e)
+        print >>stdout, "%s:  %s\n" % (sys.argv[0], quote_output(msg, quotemarks=False))
         return 1
 
     command = config.subCommand
     so = config.subOptions
 
-    rc = 0
-    if command == "create-client":
-        for basedir in so.basedirs:
-            rc = create_client(basedir, so) or rc
-    elif command == "create-introducer":
-        for basedir in so.basedirs:
-            rc = create_introducer(basedir, so) or rc
-    elif command == "start":
-        for basedir in so.basedirs:
-            rc = start(basedir, so) or rc
-    elif command == "stop":
-        for basedir in so.basedirs:
-            rc = stop(basedir, so) or rc
-    elif command == "restart":
-        for basedir in so.basedirs:
-            rc = stop(basedir, so) or rc
-        if rc:
-            print "not restarting"
-            return rc
-        for basedir in so.basedirs:
-            rc = start(basedir, so) or rc
-    return rc
-
-def run():
-    rc = runner(sys.argv[1:])
-    sys.exit(rc)
-
-def create_client(basedir, config):
-    if os.path.exists(basedir):
-        if os.listdir(basedir):
-            print "The base directory already exists: %s" % basedir
-            print "To avoid clobbering anything, I am going to quit now"
-            print "Please use a different directory, or delete this one"
-            return -1
-        # we're willing to use an empty directory
+    if config['quiet']:
+        stdout = StringIO()
+
+    so.stdout = stdout
+    so.stderr = stderr
+    so.stdin = stdin
+
+    if command in create_dispatch:
+        rc = create_dispatch[command](so, stdout, stderr)
+    elif command in startstop_node.dispatch:
+        rc = startstop_node.dispatch[command](so, stdout, stderr)
+    elif command in debug.dispatch:
+        rc = debug.dispatch[command](so)
+    elif command in admin.dispatch:
+        rc = admin.dispatch[command](so)
+    elif command in cli.dispatch:
+        rc = cli.dispatch[command](so)
+    elif command in ac_dispatch:
+        rc = ac_dispatch[command](so, stdout, stderr)
     else:
-        os.mkdir(basedir)
-    f = open(os.path.join(basedir, "client.tac"), "w")
-    f.write(client_tac)
-    f.close()
-    if not config['quiet']:
-        print "client created in %s" % basedir
-        print " please copy introducer.furl and vdrive.furl into the directory"
+        raise usage.UsageError()
 
-def create_introducer(basedir, config):
-    if os.path.exists(basedir):
-        if os.listdir(basedir):
-            print "The base directory already exists: %s" % basedir
-            print "To avoid clobbering anything, I am going to quit now"
-            print "Please use a different directory, or delete this one"
-            return -1
-        # we're willing to use an empty directory
-    else:
-        os.mkdir(basedir)
-    f = open(os.path.join(basedir, "introducer.tac"), "w")
-    f.write(introducer_tac)
-    f.close()
-    if not config['quiet']:
-        print "introducer created in %s" % basedir
+    return rc
 
-def start(basedir, config):
-    print "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 "%s does not look like a node directory" % basedir
-        if not os.path.isdir(basedir):
-            print " in fact, it doesn't look like a directory at all!"
-        sys.exit(1)
-    rc = subprocess.call(["python", twistd, "-y", tac,], cwd=basedir)
-    if rc == 0:
-        print "%s node probably started" % type
-        return 0
-    else:
-        print "%s node probably not started" % type
-        return 1
 
-def stop(basedir, config):
-    print "STOPPING", basedir
-    pidfile = os.path.join(basedir, "twistd.pid")
-    if not os.path.exists(pidfile):
-        print "%s does not look like a running node directory (no twistd.pid)" % basedir
-        return 1
-    pid = open(pidfile, "r").read()
-    pid = int(pid)
+def run(install_node_control=True):
+    try:
+        if sys.platform == "win32":
+            from allmydata.windows.fixups import initialize
+            initialize()
 
-    timer = 0
-    os.kill(pid, signal.SIGTERM)
-    time.sleep(0.1)
-    while timer < 5:
-        # poll once per second until twistd.pid goes away, up to 5 seconds
-        try:
-            os.kill(pid, 0)
-        except OSError:
-            print "process %d is dead" % pid
-            return
-        timer += 1
-        time.sleep(1)
-    print "never saw process go away"
-    return 1
+        rc = runner(sys.argv[1:], install_node_control=install_node_control)
+    except Exception:
+        import traceback
+        traceback.print_exc()
+        rc = 1
+
+    sys.exit(rc)