From 3f9df7c36b77eba4a8e6df46dc9cd1faa423d51d Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Fri, 17 May 2013 20:39:59 +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 | 59 ++++++++++++++++++- 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 | 4 ++ 8 files changed, 98 insertions(+), 6 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 e91632f5..f7d291b0 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 6b437a6d..27e8c0b5 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 627792f7..7973a72c 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): 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 +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): diff --git a/src/allmydata/scripts/common.py b/src/allmydata/scripts/common.py index 55cb3f08..cef93b11 100644 --- a/src/allmydata/scripts/common.py +++ b/src/allmydata/scripts/common.py @@ -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: 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 4cf75400..36c5bba5 100644 --- a/src/allmydata/test/test_cli.py +++ b/src/allmydata/test/test_cli.py @@ -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): -- 2.45.2