From 572da283e6201c9f2a3243d0495336a165a045ab Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Fri, 10 Jul 2015 06:10:25 +0100 Subject: [PATCH] Add and document "tahoe admin create-container" command (rebased). refs #1971 Signed-off-by: Daira Hopwood --- docs/backends/cloud.rst | 6 ++ docs/frontends/CLI.rst | 5 ++ src/allmydata/interfaces.py | 8 +++ src/allmydata/scripts/admin.py | 57 ++++++++++++++++++- src/allmydata/scripts/common.py | 13 +++-- src/allmydata/storage/backends/base.py | 6 ++ .../storage/backends/cloud/cloud_backend.py | 3 + src/allmydata/test/test_cli.py | 10 +++- 8 files changed, 100 insertions(+), 8 deletions(-) diff --git a/docs/backends/cloud.rst b/docs/backends/cloud.rst index 6e0cc15f..d49d59d6 100644 --- a/docs/backends/cloud.rst +++ b/docs/backends/cloud.rst @@ -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) ================================== diff --git a/docs/frontends/CLI.rst b/docs/frontends/CLI.rst index 3a93cc86..385dccc8 100644 --- a/docs/frontends/CLI.rst +++ b/docs/frontends/CLI.rst @@ -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 ======================= diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index 6c5f8dd8..dd4f05bb 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -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(): diff --git a/src/allmydata/scripts/admin.py b/src/allmydata/scripts/admin.py index af6a93f5..e0676d5e 100644 --- a/src/allmydata/scripts/admin.py +++ b/src/allmydata/scripts/admin.py @@ -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 +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): diff --git a/src/allmydata/scripts/common.py b/src/allmydata/scripts/common.py index d6246fc0..666176d7 100644 --- a/src/allmydata/scripts/common.py +++ b/src/allmydata/scripts/common.py @@ -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: diff --git a/src/allmydata/storage/backends/base.py b/src/allmydata/storage/backends/base.py index 570da2ce..7513dba7 100644 --- a/src/allmydata/storage/backends/base.py +++ b/src/allmydata/storage/backends/base.py @@ -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): """ diff --git a/src/allmydata/storage/backends/cloud/cloud_backend.py b/src/allmydata/storage/backends/cloud/cloud_backend.py index 247aa228..dbf91461 100644 --- a/src/allmydata/storage/backends/cloud/cloud_backend.py +++ b/src/allmydata/storage/backends/cloud/cloud_backend.py @@ -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) diff --git a/src/allmydata/test/test_cli.py b/src/allmydata/test/test_cli.py index 3a0766b7..b51d5185 100644 --- a/src/allmydata/test/test_cli.py +++ b/src/allmydata/test/test_cli.py @@ -609,15 +609,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): -- 2.45.2