From ba0ff39e0f55b66f8a64503b7081fdb4abcd2cd2 Mon Sep 17 00:00:00 2001
From: Itamar Turner-Trauring <itamar@futurefoundries.com>
Date: Tue, 5 Mar 2013 11:38:40 -0500
Subject: [PATCH] Configuration support for Google Cloud Storage backend.

---
 .../storage/backends/cloud/cloud_backend.py   |  2 +-
 .../googlestorage/googlestorage_container.py  | 35 ++++------
 src/allmydata/test/test_client.py             | 68 +++++++++++++++++++
 3 files changed, 82 insertions(+), 23 deletions(-)

diff --git a/src/allmydata/storage/backends/cloud/cloud_backend.py b/src/allmydata/storage/backends/cloud/cloud_backend.py
index 3d92525f..14dee749 100644
--- a/src/allmydata/storage/backends/cloud/cloud_backend.py
+++ b/src/allmydata/storage/backends/cloud/cloud_backend.py
@@ -20,7 +20,7 @@ from allmydata.storage.backends.cloud.cloud_common import get_share_key, delete_
 from allmydata.mutable.layout import MUTABLE_MAGIC
 
 
-CLOUD_INTERFACES = ("cloud.s3", "cloud.openstack")
+CLOUD_INTERFACES = ("cloud.s3", "cloud.openstack", "cloud.googlestorage")
 
 
 def get_cloud_share(container, storage_index, shnum, total_size):
diff --git a/src/allmydata/storage/backends/cloud/googlestorage/googlestorage_container.py b/src/allmydata/storage/backends/cloud/googlestorage/googlestorage_container.py
index 75264e23..af3a82a9 100644
--- a/src/allmydata/storage/backends/cloud/googlestorage/googlestorage_container.py
+++ b/src/allmydata/storage/backends/cloud/googlestorage/googlestorage_container.py
@@ -26,12 +26,6 @@ from allmydata.storage.backends.cloud.cloud_common import IContainer, \
      ContainerItem, ContainerListing, CommonContainerMixin
 
 
-def configure_googlestorage_container(*args):
-    """
-    Configure the Google Cloud Storage container.
-    """
-
-
 class AuthenticationClient(object):
     """
     Retrieve access tokens for the Google Storage API, using OAuth 2.0.
@@ -227,19 +221,16 @@ class GoogleStorageContainer(CommonContainerMixin):
         return d
 
 
-if __name__ == '__main__':
-    from twisted.internet import reactor
-    from twisted.web.client import getPage
-    import sys
-    auth = AuthenticationClient(sys.argv[1], file(sys.argv[2]).read())
-    def println(result):
-        print result
-        reactor.stop()
-    def gotAuth(value):
-        return getPage("https://storage.googleapis.com/",
-                       headers={"Authorization": value,
-                                "x-goog-api-version": "2",
-                                "x-goog-project-id": sys.argv[3]}).addCallback(println)
-    auth.get_authorization_header().addCallback(gotAuth)
-    reactor.run()
-
+def configure_googlestorage_container(storedir, config):
+    """
+    Configure the Google Cloud Storage container.
+    """
+    account_email = config.get_config("storage", "googlestorage.account_email")
+    private_key = config.get_private_config("googlestorage_private_key")
+    bucket_name = config.get_config("storage", "googlestorage.bucket_name")
+    # Only necessary if we do bucket creation/deletion, otherwise can be
+    # removed:
+    project_id = config.get_config("storage", "googlestorage.project_id")
+
+    authclient = AuthenticationClient(account_email, private_key)
+    return GoogleStorageContainer(authclient, project_id, bucket_name)
diff --git a/src/allmydata/test/test_client.py b/src/allmydata/test/test_client.py
index 2c0eed7a..199520f1 100644
--- a/src/allmydata/test/test_client.py
+++ b/src/allmydata/test/test_client.py
@@ -435,6 +435,74 @@ class Basic(testutil.ReallyEqualMixin, unittest.TestCase):
                                     "openstack.container = test\n")
         self.failUnlessRaises(MissingConfigEntry, client.Client, basedir)
 
+    def test_googlestorage_config_required(self):
+        """
+        account_email, bucket_name and project_id are all required by
+        googlestorage configuration.
+        """
+        configs = ["googlestorage.account_email = u@example.com",
+                   "googlestorage.bucket_name = bucket",
+                   "googlestorage.project_id = 456"]
+        for i in range(len(configs)):
+            basedir = self.mktemp()
+            os.mkdir(basedir)
+            bad_config = configs[:]
+            del bad_config[i]
+            self._write_secret(basedir, "googlestorage_private_key")
+            fileutil.write(os.path.join(basedir, "tahoe.cfg"),
+                           BASECONFIG +
+                           "[storage]\n" +
+                           "enabled = true\n" +
+                           "backend = cloud.googlestorage\n" +
+                           "\n".join(bad_config) + "\n")
+            self.failUnlessRaises(MissingConfigEntry, client.Client, basedir)
+
+    def test_googlestorage_config_required_private_key(self):
+        """
+        googlestorage_private_key secret is required by googlestorage
+        configuration.
+        """
+        basedir = self.mktemp()
+        os.mkdir(basedir)
+        fileutil.write(os.path.join(basedir, "tahoe.cfg"),
+                       BASECONFIG +
+                       "[storage]\n" +
+                       "enabled = true\n" +
+                       "backend = cloud.googlestorage\n" +
+                       "googlestorage.account_email = u@example.com\n" +
+                       "googlestorage.bucket_name = bucket\n" +
+                       "googlestorage.project_id = 456\n")
+        self.failUnlessRaises(MissingConfigEntry, client.Client, basedir)
+
+    @mock.patch('allmydata.storage.backends.cloud.googlestorage.googlestorage_container.AuthenticationClient')
+    @mock.patch('allmydata.storage.backends.cloud.googlestorage.googlestorage_container.GoogleStorageContainer')
+    def test_googlestorage_config(self, mock_OpenStackContainer, mock_AuthenticationClient):
+        """
+        Given good configuration, we correctly configure a good GoogleStorageContainer.
+        """
+        basedir = self.mktemp()
+        os.mkdir(basedir)
+        self._write_secret(basedir, "googlestorage_private_key", "sekrit")
+        fileutil.write(os.path.join(basedir, "tahoe.cfg"),
+                       BASECONFIG +
+                       "[storage]\n" +
+                       "enabled = true\n" +
+                       "backend = cloud.googlestorage\n" +
+                       "googlestorage.account_email = u@example.com\n" +
+                       "googlestorage.bucket_name = bucket\n" +
+                       "googlestorage.project_id = 456\n")
+        c = client.Client(basedir)
+        server = c.getServiceNamed("storage")
+        self.failUnless(isinstance(server.backend, CloudBackend), server.backend)
+        # Protect against typos with isinstance(), because mock is dangerous.
+        self.assertFalse(isinstance(mock_AuthenticationClient.assert_called_once_with,
+                                    mock.Mock))
+        mock_AuthenticationClient.assert_called_once_with("u@example.com", "sekrit")
+        self.assertFalse(isinstance(mock_OpenStackContainer.assert_called_once_with,
+                                    mock.Mock))
+        mock_OpenStackContainer.assert_called_once_with(mock_AuthenticationClient.return_value,
+                                                        "456", "bucket")
+
     def test_expire_mutable_false_unsupported(self):
         basedir = "client.Basic.test_expire_mutable_false_unsupported"
         os.mkdir(basedir)
-- 
2.45.2