]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
Add and document "tahoe admin create-container" command (rebased). refs #1971
authorDaira Hopwood <daira@jacaranda.org>
Fri, 10 Jul 2015 05:10:25 +0000 (06:10 +0100)
committerDaira Hopwood <daira@jacaranda.org>
Tue, 4 Aug 2015 18:10:28 +0000 (19:10 +0100)
Signed-off-by: Daira Hopwood <daira@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 3a93cc86f0f8839fd7ec32374c39a721cc796f5b..385dccc81160a2ef194453aad9689a3458bcad59 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 6d52a22fc547f421e9b72c12f3b32388acaf845d..2d103bf2b7dd330acc7b6bd903707e46fe0a3485 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 af6a93f5540674665df5949384ee42d2dd924a34..e0676d5eadc39a12ff158e15b49879c4a84527e7 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):
 
@@ -42,12 +44,64 @@ def derive_pubkey(options):
     print >>out, "public:", pubkey_vs
     return 0
 
+
+class CreateContainerOptions(BasedirOptions):
+    def getSynopsis(self):
+        return "Usage: tahoe [global-options] admin create-container [NODEDIR]"
+
+    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'):
@@ -65,6 +119,7 @@ each subcommand.
 subDispatch = {
     "generate-keypair": print_keypair,
     "derive-pubkey": derive_pubkey,
+    "create-container": create_container,
     }
 
 def do_admin(options):
index d6246fc05f6198621080fd1d0e4389a03c26ff74..666176d72b37af6d9e91853477e75351384ae14e 100644 (file)
@@ -57,9 +57,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.")
@@ -68,8 +73,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 12413881d2e86c33b13350a95e3c71b35e06d58a..2f2c1e610dbd463e584241785c3fedad65f26932 100644 (file)
@@ -617,15 +617,19 @@ class Help(unittest.TestCase):
 
     def test_create_admin(self):
         help = str(admin.AdminCommand())
-        self.failUnlessIn(" [global-opts] admin SUBCOMMAND", help)
+        self.failUnlessIn(" [global-options] admin SUBCOMMAND", help)
 
     def test_create_admin_generate_keypair(self):
         help = str(admin.GenerateKeypairOptions())
-        self.failUnlessIn(" [global-opts] admin generate-keypair", help)
+        self.failUnlessIn(" [global-options] admin generate-keypair", help)
 
     def test_create_admin_derive_pubkey(self):
         help = str(admin.DerivePubkeyOptions())
-        self.failUnlessIn(" [global-opts] admin derive-pubkey", help)
+        self.failUnlessIn(" [global-options] admin derive-pubkey", help)
+
+    def test_create_admin_create_container(self):
+        help = str(admin.CreateContainerOptions())
+        self.failUnlessIn(" [global-options] admin create-container [NODEDIR]", help)
 
 
 class Ln(GridTestMixin, CLITestMixin, unittest.TestCase):