From: Itamar Turner-Trauring Date: Mon, 4 Mar 2013 18:40:05 +0000 (-0500) Subject: Start of tests for the Google Storage container. X-Git-Url: https://git.rkrishnan.org/%5B/frontends/%22file:/?a=commitdiff_plain;h=b3b795498c92c6e0d545840aed7c6195b9fc2a8d;p=tahoe-lafs%2Ftahoe-lafs.git Start of tests for the Google Storage container. --- diff --git a/src/allmydata/storage/backends/cloud/googlestorage/googlestorage_container.py b/src/allmydata/storage/backends/cloud/googlestorage/googlestorage_container.py index df6efe74..e928464c 100644 --- a/src/allmydata/storage/backends/cloud/googlestorage/googlestorage_container.py +++ b/src/allmydata/storage/backends/cloud/googlestorage/googlestorage_container.py @@ -86,7 +86,7 @@ class GoogleStorageContainer(HTTPClientMixin, ContainerRetryMixin): USER_AGENT = "Tahoe-LAFS Google Storage client" - def __init__(self, auth_client, bucket_name, override_reactor=None): + def __init__(self, auth_client, project_id, bucket_name, override_reactor=None): pass diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index f9aaef06..bb27565a 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -639,16 +639,16 @@ class OpenStackCloudBackend(ServiceParentMixin, WorkdirMixin, ShouldFailMixin, u -class GoogleStorageBackend(ShouldFailMixin, unittest.TestCase): +class GoogleStorageAuthenticationClient(unittest.TestCase): """ - Tests for the Google Storage API backend. + Tests for the Google Storage API authentication. All code references in docstrings/comments are to classes/functions in allmydata.storage.backends.cloud.googlestorage.googlestorage_container unless noted otherwise. """ - def test_authentication_credentials(self): + def test_credentials(self): """ AuthenticationClient.get_authorization_header() initializes a SignedJwtAssertionCredentials with the correct parameters. @@ -658,7 +658,7 @@ class GoogleStorageBackend(ShouldFailMixin, unittest.TestCase): self.assertEqual(auth._credentials.service_account_name, "u@example.com") self.assertEqual(auth._credentials.private_key, "xxx123".encode("base64").strip()) - def test_authentication_initial(self): + def test_initial(self): """ When AuthenticationClient() is created, it refreshes its access token. """ @@ -669,7 +669,7 @@ class GoogleStorageBackend(ShouldFailMixin, unittest.TestCase): _deferToThread=defer.maybeDeferred) self.assertEqual(auth._credentials.refresh.call_count, 1) - def test_authentication_expired(self): + def test_expired(self): """ AuthenticationClient.get_authorization_header() refreshes its credentials if the access token has expired. @@ -684,7 +684,7 @@ class GoogleStorageBackend(ShouldFailMixin, unittest.TestCase): auth.get_authorization_header() self.assertEqual(auth._credentials.refresh.call_count, 2) - def test_authentication_no_refresh(self): + def test_no_refresh(self): """ AuthenticationClient.get_authorization_header() does not refresh its credentials if the access token has not expired. @@ -699,7 +699,7 @@ class GoogleStorageBackend(ShouldFailMixin, unittest.TestCase): auth.get_authorization_header() self.assertEqual(auth._credentials.refresh.call_count, 1) - def test_authentication_header(self): + def test_header(self): """ AuthenticationClient.get_authorization_header() returns a value to be used for the Authorization header. @@ -716,7 +716,7 @@ class GoogleStorageBackend(ShouldFailMixin, unittest.TestCase): auth.get_authorization_header().addCallback(result.append) self.assertEqual(result, ["Bearer xxx"]) - def test_authentication_one_refresh(self): + def test_one_refresh(self): """ AuthenticationClient._refresh_if_necessary() only runs one refresh request at a time. @@ -742,7 +742,7 @@ class GoogleStorageBackend(ShouldFailMixin, unittest.TestCase): first.callback(None) self.assertEqual(len(results), 0) - def test_authentication_refresh_call(self): + def test_refresh_call(self): """ AuthenticationClient._refresh_if_necessary() runs the authentication refresh in a thread, since it blocks, with a @@ -764,6 +764,243 @@ class GoogleStorageBackend(ShouldFailMixin, unittest.TestCase): return auth.get_authorization_header().addCallback(gotResult) +class GoogleStorageBackend(unittest.TestCase): + """ + Tests for the Google Storage API container. + + All code references in docstrings/comments are to classes/functions in + allmydata.storage.backends.cloud.googlestorage.googlestorage_container + unless noted otherwise. + """ + class Response(object): + def __init__(self, code, headers): + self.code = code + self.headers = headers + + def setUp(self): + class FakeAuthenticationClient(object): + def get_authorization_header(self): + return "Bearer thetoken" + self.auth = FakeAuthenticationClient() + self.container = googlestorage_container.GoogleStorageContainer( + self.auth, "123", "thebucket") + + def mock_http_request(self): + """ + Override the container's _http_request with a mock whose result is a + Deferred which can be fired by the caller. + """ + d = defer.Deferred() + self.container._http_request = mock.create_autospec( + self._container._http_request, return_value=d) + return d + + def test_create(self): + """ + GoogleStorageContainer.create() sends the appropriate HTTP command to + create the bucket, and parses the response to match the expected + result documented in the IContainer interface. + """ + raise NotImplementedError() + test_create.todo = "may not be necessary" + + def test_delete(self): + """ + GoogleStorageContainer.delete() sends the appropriate HTTP command to + delete the bucket, and parses the response to match the expected + result documented in the IContainer interface. + """ + raise NotImplementedError() + test_delete.todo = "may not be necessary" + + def test_list_objects(self): + """ + GoogleStorageContainer.list_objects() sends the appropriate HTTP + command to list the objects in the bucket, and parses the response to + match the expected result documented in the IContainer interface. + """ + LIST_RESPONSE = """\ + + + thebucket + xxx xxx + themark + false + + xxx xxx1 + 1234 + 1 + 2013-01-27T01:23:45.678Z + "abc" + 123 + STANDARD + + something + + + + + xxx xxx2 + 1234 + 1 + 2013-01-28T01:23:45.678Z + "def" + 456 + NOTSTANDARD + + something + + + + + xxx + + + xxx xxx + + + + +""" + http_response = self.mock_successful_http_request() + done = [] + self.container.list_objects(prefix='xxx xxx').addCallback(done.append) + self.assertFalse(done) + self.container._http_request.assert_called_once_with( + "list objects", "GET", + "https://storage.googleapis.com/thebucket/?prefix=xxx%20xxx", + {"Authorization": ["Bearer thetoken"], + "x-goog-api-version": ["2"], + }, + body=None, + need_response_body=True) + http_response.callback((self.Response(200), LIST_RESPONSE)) + listing = done[0] + self.assertEqual(listing.name, "thebucket") + self.assertEqual(listing.prefix, "xxx xxx") + self.assertEqual(listing.marker, "themark") + self.assertEqual(listing.max_keys, None) + self.assertEqual(listing.is_truncated, "false") + self.assertEqual(listing.common_prefixes, ["xxx", "xxx xxx"]) + item1, item2 = listing.contents + self.assertEqual(item1.key, "xxx xxx1") + self.assertEqual(item1.modification_date, "2013-01-27T01:23:45.678Z") + self.assertEqual(item1.etag, '"abc"') + self.assertEqual(item1.size, '123') + self.assertEqual(item1.storage_class, 'STANDARD') + self.assertEqual(item1.owner, None) # meh, who cares + self.assertEqual(item2.key, "xxx xxx2") + self.assertEqual(item2.modification_date, "2013-01-28T01:23:45.678Z") + self.assertEqual(item2.etag, '"def"') + self.assertEqual(item2.size, '456') + self.assertEqual(item2.storage_class, 'NOTSTANDARD') + self.assertEqual(item2.owner, None) # meh, who cares + + def test_put_object(self): + """ + GoogleStorageContainer.put_object() sends the appropriate HTTP command + to upload an object to the bucket, and parses the response to match + the expected result documented in the IContainer interface. + """ + http_response = self.mock_successful_http_request() + done = [] + self.container.put_object("theobj", "the body").addCallback(done.append) + self.assertFalse(done) + self.container._http_request.assert_called_once_with( + "PUT object", "PUT", + "https://storage.googleapis.com/thebucket/theobj", + {"Authorization": ["Bearer thetoken"], + "x-goog-api-version": ["2"], + "Content-Type": ["application/octet-stream"], + }, + body="the body", + need_response_body=False) + http_response.callback((self.Response(200), None)) + self.assertTrue(done) + + def test_put_object_additional(self): + """ + GoogleStorageContainer.put_object() sends the appropriate HTTP command + to upload an object to the bucket with custom content type and + metadata, and parses the response to match the expected result + documented in the IContainer interface. + """ + http_response = self.mock_successful_http_request() + done = [] + self.container.put_object("theobj", "the body", + "text/plain", + {"key": "value"}).addCallback(done.append) + self.assertFalse(done) + self.container._http_request.assert_called_once_with( + "PUT object", "PUT", + "https://storage.googleapis.com/thebucket/theobj", + {"Authorization": ["Bearer thetoken"], + "x-goog-api-version": ["2"], + "Content-Type": ["text/plain"], + "x-goog-meta-key": ["value"], # the metadata + }, + body="the body", + need_response_body=False) + http_response.callback((self.Response(200), None)) + self.assertTrue(done) + + def test_get_object(self): + """ + GoogleStorageContainer.get_object() sends the appropriate HTTP command + to get an object from the bucket, and parses the response to match the + expected result documented in the IContainer interface. + """ + http_response = self.mock_successful_http_request() + done = [] + self.container.get_object("theobj").addCallback(done.append) + self.assertFalse(done) + self.container._http_request.assert_called_once_with( + "GET object", "GET", + "https://storage.googleapis.com/thebucket/theobj", + {"Authorization": ["Bearer thetoken"], + "x-goog-api-version": ["2"], + }, + body=None, + need_response_body=True) + http_response.callback((self.Response(200), "the body")) + self.assertEqual(done, ["the body"]) + + def test_delete_object(self): + """ + GoogleStorageContainer.delete_object() sends the appropriate HTTP + command to delete an object from the bucket, and parses the response + to match the expected result documented in the IContainer interface. + """ + http_response = self.mock_successful_http_request() + done = [] + self.container.delete_object("theobj").addCallback(done.append) + self.assertFalse(done) + self.container._http_request.assert_called_once_with( + "DELETE object", "DELETE", + "https://storage.googleapis.com/thebucket/theobj", + {"Authorization": ["Bearer thetoken"], + "x-goog-api-version": ["2"], + }, + body=None, + need_response_body=False) + http_response.callback((self.Response(200), None)) + self.assertTrue(done) + + def test_auth_failed(self): + """ + If an HTTP response code is UNAUTHORIZED, the authentication client + will be told to invalidate its credentials before retrying. + """ + + def test_head_object(self): + """ + GoogleStorageContainer.head_object() sends the appropriate HTTP + command to get an object's metadata from the bucket, and parses the + response to match the expected result documented in the IContainer + interface. + """ + + class ServerMixin: def allocate(self, account, storage_index, sharenums, size, canary=None): # These secrets are not used, but clients still provide them.