Add and document "tahoe admin create-container" command (rebased). refs #1971
authorDaira Hopwood <david-sarah@jacaranda.org>
Fri, 17 May 2013 19:39:59 +0000 (20:39 +0100)
committerDaira Hopwood <daira@jacaranda.org>
Fri, 17 Apr 2015 21:31:40 +0000 (22:31 +0100)
Signed-off-by: Daira Hopwood <david-sarah@jacaranda.org>
docs/backends/cloud.rst
docs/frontends/CLI.rst
src/allmydata/interfaces.py
src/allmydata/scripts/admin.py
src/allmydata/scripts/common.py
src/allmydata/storage/backends/base.py
src/allmydata/storage/backends/cloud/cloud_backend.py
src/allmydata/test/test_cli.py

index 6e0cc15fda2a5496978e345211011b029da153bf..d49d59d6b85a681e6cbc1399ad5c21401207ca4f 100644 (file)
@@ -5,6 +5,12 @@ Storing Shares on Cloud Services
 The Tahoe-LAFS storage server can be configured to store its shares on a
 cloud storage service, rather than on the local filesystem.
 
+All cloud storage services store the data in a particular container (also
+called a "bucket" in some storage services). You can create this container
+using the "tahoe admin create-container" command, once you have a correctly
+configured Tahoe-LAFS node. That is, configure the node with the container
+name you decided to use (e.g. "tahoedata"), then run the command.
+
 
 Amazon Simple Storage Service (S3)
 ==================================
index e91632f579b5843e408c4228c88225ce0ddce3ed..f7d291b0cbbdca6875c4af7ec86c07019e661fb2 100644 (file)
@@ -138,6 +138,11 @@ same way as "``tahoe run``".
 is most often used by developers who have just modified the code and want to
 start using their changes.
 
+Some less frequently used administration commands, for key generation/derivation
+and for creating a cloud backend container, are grouped as subcommands of
+"``tahoe admin``". For a list of these use "``tahoe admin --help``", or for
+more detailed help on a particular command, use "``tahoe admin COMMAND --help``".
+
 
 Filesystem Manipulation
 =======================
index 6b437a6df177abf49729f12fa11dbfdf13327413..27e8c0b59b5734a90e5763ebb1294258cf680f5b 100644 (file)
@@ -358,6 +358,14 @@ class IStorageBackend(Interface):
         permutation-seed.
         """
 
+    def create_container():
+        """
+        Create a container for the configured backend, if necessary. Return a
+        Deferred that fires with False if no container is needed for this backend
+        type, or something other than False if a container has been successfully
+        created. It is an error to attempt to create a container that already exists.
+        """
+
 
 class IShareSet(Interface):
     def get_storage_index():
index 627792f72f1c03a5eb71525abfe2096a092fcd18..7973a72cfbeb86eef2eec6f652e0fe7bf1b70a14 100644 (file)
@@ -1,6 +1,8 @@
 
+import os
+
 from twisted.python import usage
-from allmydata.scripts.common import BaseOptions
+from allmydata.scripts.common import BaseOptions, BasedirOptions
 
 class GenerateKeypairOptions(BaseOptions):
     def getSynopsis(self):
@@ -44,18 +46,70 @@ def derive_pubkey(options):
     print >>out, "public:", pubkey_vs
     return 0
 
+
+class CreateContainerOptions(BasedirOptions):
+    def getSynopsis(self):
+        return "Usage: %s [global-opts] admin create-container [NODEDIR]" % (self.command_name,)
+
+    def getUsage(self, width=None):
+        t = BasedirOptions.getUsage(self, width)
+        t += """
+Create a storage container, using the name and credentials configured in
+tahoe.cfg. This is needed only for the cloud backend, and only if the
+container has not already been created. See <docs/backends/cloud.rst>
+for more details.
+"""
+        return t
+
+def create_container(options):
+    from twisted.internet import reactor, defer
+
+    err = options.stderr
+
+    d = defer.maybeDeferred(do_create_container, options)
+    def _failed(f):
+        print >>err, "Container creation failed."
+        print >>err, "%s: %s" % (f.value.__class__.__name__, f.value)
+        print >>err
+        return f
+    d.addErrback(_failed)
+    d.addCallbacks(lambda ign: os._exit(0), lambda ign: os._exit(1))
+    reactor.run()
+
+def do_create_container(options):
+    from allmydata.node import ConfigOnly
+    from allmydata.client import Client
+
+    out = options.stderr
+    config = ConfigOnly(options['basedir'])
+    (backend, _) = Client.configure_backend(config)
+
+    d = backend.create_container()
+    def _done(res):
+        if res is False:
+            print >>out, ("It is not necessary to create a container for this backend type (%s)."
+                          % (backend.__class__.__name__,))
+        else:
+            print >>out, "The container was successfully created."
+        print >>out
+    d.addCallback(_done)
+    return d
+
+
 class AdminCommand(BaseOptions):
     subCommands = [
         ("generate-keypair", None, GenerateKeypairOptions,
          "Generate a public/private keypair, write to stdout."),
         ("derive-pubkey", None, DerivePubkeyOptions,
          "Derive a public key from a private key."),
+        ("create-container", None, CreateContainerOptions,
+         "Create a container for the configured cloud backend."),
         ]
     def postOptions(self):
         if not hasattr(self, 'subOptions'):
             raise usage.UsageError("must specify a subcommand")
     def getSynopsis(self):
-        return "Usage: tahoe [global-opts] admin SUBCOMMAND"
+        return "Usage: %s [global-opts] admin SUBCOMMAND" % (self.command_name,)
     def getUsage(self, width=None):
         t = BaseOptions.getUsage(self, width)
         t += """
@@ -67,6 +121,7 @@ each subcommand.
 subDispatch = {
     "generate-keypair": print_keypair,
     "derive-pubkey": derive_pubkey,
+    "create-container": create_container,
     }
 
 def do_admin(options):
index 55cb3f08c031e090fa96688849128b5f78d763eb..cef93b1178b65067ab3cc5c4e18250de79027a8e 100644 (file)
@@ -45,9 +45,14 @@ class BasedirOptions(BaseOptions):
     ]
 
     def parseArgs(self, basedir=None):
-        if self.parent['node-directory'] and self['basedir']:
+        # This finds the node-directory option correctly even if we are in a subcommand.
+        root = self.parent
+        while root.parent is not None:
+            root = root.parent
+
+        if root['node-directory'] and self['basedir']:
             raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) options cannot both be used.")
-        if self.parent['node-directory'] and basedir:
+        if root['node-directory'] and basedir:
             raise usage.UsageError("The --node-directory (or -d) option and a basedir argument cannot both be used.")
         if self['basedir'] and basedir:
             raise usage.UsageError("The --basedir (or -C) option and a basedir argument cannot both be used.")
@@ -56,8 +61,8 @@ class BasedirOptions(BaseOptions):
             b = argv_to_abspath(basedir)
         elif self['basedir']:
             b = argv_to_abspath(self['basedir'])
-        elif self.parent['node-directory']:
-            b = argv_to_abspath(self.parent['node-directory'])
+        elif root['node-directory']:
+            b = argv_to_abspath(root['node-directory'])
         elif self.default_nodedir:
             b = self.default_nodedir
         else:
index 570da2ce17bcc782a2cf40125928026cbcd115f2..7513dba7229eb54a793127c6a0daca337413d969 100644 (file)
@@ -31,6 +31,12 @@ class Backend(service.MultiService):
         # compatibility requirements for permutation seeds. The disk backend overrides this.
         return False
 
+    def create_container(self):
+        # Backends for which it is necessary to create a container, should override this
+        # and return a Deferred that fires with something other than False when the
+        # container has been created.
+        return defer.succeed(False)
+
 
 class ShareSet(object):
     """
index 247aa2287c2b0f50730b5c08b46715116a9b3225..dbf9146122e3b868a70cf6aae318298ee86224c7 100644 (file)
@@ -97,6 +97,9 @@ class CloudBackend(Backend):
         # TODO: query space usage of container if supported.
         return 2**64
 
+    def create_container(self):
+        return self._container.create()
+
 
 class CloudShareSet(ShareSet):
     implements(IShareSet)
index 4cf754007638be55ed92ad93295774cb07cf2938..36c5bba5a542b3d66f5050dc6f8adfda4c2c1dfd 100644 (file)
@@ -614,6 +614,10 @@ class Help(unittest.TestCase):
         help = str(admin.DerivePubkeyOptions())
         self.failUnlessIn(" [global-opts] admin derive-pubkey", help)
 
+    def test_create_admin_create_container(self):
+        help = str(admin.CreateContainerOptions())
+        self.failUnlessIn(" [global-opts] admin create-container [NODEDIR]", help)
+
 
 class Ln(GridTestMixin, CLITestMixin, unittest.TestCase):
     def _create_test_file(self):