diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java index 7a134b1d524a..1e154e3f8eea 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/testing/RemoteGcsHelper.java @@ -16,20 +16,17 @@ package com.google.gcloud.storage.testing; -import com.google.common.collect.ImmutableMap; import com.google.gcloud.AuthCredentials; import com.google.gcloud.storage.BlobInfo; import com.google.gcloud.RetryParams; import com.google.gcloud.storage.Storage; import com.google.gcloud.storage.StorageException; import com.google.gcloud.storage.StorageOptions; -import com.google.gcloud.storage.testing.RemoteGcsHelper.Option.KeyFromClasspath; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.IOException; -import java.util.Map; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -96,35 +93,19 @@ public static String generateBucketName() { } /** - * Creates a {@code RemoteGcsHelper} object for the given project id and JSON key path. + * Creates a {@code RemoteGcsHelper} object for the given project id and JSON key input stream. * * @param projectId id of the project to be used for running the tests - * @param keyPath path to the JSON key to be used for running the tests - * @param options creation options + * @param keyStream input stream for a JSON key * @return A {@code RemoteGcsHelper} object for the provided options. - * @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if the file pointed by - * {@code keyPath} does not exist + * @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if + * {@code keyStream} is not a valid JSON key stream */ - public static RemoteGcsHelper create(String projectId, String keyPath, Option... options) + public static RemoteGcsHelper create(String projectId, InputStream keyStream) throws GcsHelperException { - boolean keyFromClassPath = false; - Map, Option> optionsMap = Option.asImmutableMap(options); - if (optionsMap.containsKey(KeyFromClasspath.class)) { - keyFromClassPath = - ((KeyFromClasspath) optionsMap.get(KeyFromClasspath.class)).keyFromClasspath(); - } try { - InputStream keyFileStream; - if (keyFromClassPath) { - keyFileStream = RemoteGcsHelper.class.getResourceAsStream(keyPath); - if (keyFileStream == null) { - throw new FileNotFoundException(keyPath + " not found in classpath"); - } - } else { - keyFileStream = new FileInputStream(keyPath); - } StorageOptions storageOptions = StorageOptions.builder() - .authCredentials(AuthCredentials.createForJson(keyFileStream)) + .authCredentials(AuthCredentials.createForJson(keyStream)) .projectId(projectId) .retryParams(RetryParams.builder() .retryMaxAttempts(10) @@ -137,6 +118,28 @@ public static RemoteGcsHelper create(String projectId, String keyPath, Option... .readTimeout(60000) .build(); return new RemoteGcsHelper(storageOptions); + } catch (IOException ex) { + if (log.isLoggable(Level.WARNING)) { + log.log(Level.WARNING, ex.getMessage()); + } + throw GcsHelperException.translate(ex); + } + } + + /** + * Creates a {@code RemoteGcsHelper} object for the given project id and JSON key path. + * + * @param projectId id of the project to be used for running the tests + * @param keyPath path to the JSON key to be used for running the tests + * @return A {@code RemoteGcsHelper} object for the provided options. + * @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if the file + * pointed by {@code keyPath} does not exist + */ + public static RemoteGcsHelper create(String projectId, String keyPath) + throws GcsHelperException { + try { + InputStream keyFileStream = new FileInputStream(keyPath); + return create(projectId, keyFileStream); } catch (FileNotFoundException ex) { if (log.isLoggable(Level.WARNING)) { log.log(Level.WARNING, ex.getMessage()); @@ -154,13 +157,12 @@ public static RemoteGcsHelper create(String projectId, String keyPath, Option... * Creates a {@code RemoteGcsHelper} object. Project id and path to JSON key are read from two * environment variables: {@code GCLOUD_TESTS_PROJECT_ID} and {@code GCLOUD_TESTS_KEY}. * - * @param options creation options * @return A {@code RemoteGcsHelper} object for the provided options. - * @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if environment variables - * {@code GCLOUD_TESTS_PROJECT_ID} and {@code GCLOUD_TESTS_KEY} are not set or if the file - * pointed by {@code GCLOUD_TESTS_KEY} does not exist + * @throws com.google.gcloud.storage.testing.RemoteGcsHelper.GcsHelperException if environment + * variables {@code GCLOUD_TESTS_PROJECT_ID} and {@code GCLOUD_TESTS_KEY} are not set or if + * the file pointed by {@code GCLOUD_TESTS_KEY} does not exist */ - public static RemoteGcsHelper create(Option... options) throws GcsHelperException { + public static RemoteGcsHelper create() throws GcsHelperException { String projectId = System.getenv(PROJECT_ID_ENV_VAR); String keyPath = System.getenv(PRIVATE_KEY_ENV_VAR); if (projectId == null) { @@ -177,7 +179,7 @@ public static RemoteGcsHelper create(Option... options) throws GcsHelperExceptio } throw new GcsHelperException(message); } - return create(projectId, keyPath, options); + return create(projectId, keyPath); } private static class DeleteBucketTask implements Callable { @@ -210,42 +212,6 @@ public Boolean call() throws Exception { } } - public static abstract class Option implements java.io.Serializable { - - private static final long serialVersionUID = 8849118657896662369L; - - public static final class KeyFromClasspath extends Option { - - private static final long serialVersionUID = -5506049413185246821L; - - private final boolean keyFromClasspath; - - public KeyFromClasspath(boolean keyFromClasspath) { - this.keyFromClasspath = keyFromClasspath; - } - - public boolean keyFromClasspath() { - return keyFromClasspath; - } - } - - Option() { - // package protected - } - - public static KeyFromClasspath keyFromClassPath() { - return new KeyFromClasspath(true); - } - - static Map, Option> asImmutableMap(Option... options) { - ImmutableMap.Builder, Option> builder = ImmutableMap.builder(); - for (Option option : options) { - builder.put(option.getClass(), option); - } - return builder.build(); - } - } - public static class GcsHelperException extends RuntimeException { private static final long serialVersionUID = -7756074894502258736L; diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java new file mode 100644 index 000000000000..3cd67701e92b --- /dev/null +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.storage; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.gcloud.storage.testing.RemoteGcsHelper; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.easymock.EasyMock; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class RemoteGcsHelperTest { + + private static final String BUCKET_NAME = "bucket-name"; + private static final String PROJECT_ID = "project-id"; + private static final String JSON_KEY = "{\n" + + " \"private_key_id\": \"somekeyid\",\n" + + " \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggS" + + "kAgEAAoIBAQC+K2hSuFpAdrJI\\nnCgcDz2M7t7bjdlsadsasad+fvRSW6TjNQZ3p5LLQY1kSZRqBqylRkzteMOyHg" + + "aR\\n0Pmxh3ILCND5men43j3h4eDbrhQBuxfEMalkG92sL+PNQSETY2tnvXryOvmBRwa/\\nQP/9dJfIkIDJ9Fw9N4" + + "Bhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\\nknddadwkwewcVxHFhcZJO+XWf6ofLUXpRwiTZakGMn8EE1uVa2" + + "LgczOjwWHGi99MFjxSer5m9\\n1tCa3/KEGKiS/YL71JvjwX3mb+cewlkcmweBKZHM2JPTk0ZednFSpVZMtycjkbLa" + + "\\ndYOS8V85AgMBewECggEBAKksaldajfDZDV6nGqbFjMiizAKJolr/M3OQw16K6o3/\\n0S31xIe3sSlgW0+UbYlF" + + "4U8KifhManD1apVSC3csafaspP4RZUHFhtBywLO9pR5c\\nr6S5aLp+gPWFyIp1pfXbWGvc5VY/v9x7ya1VEa6rXvL" + + "sKupSeWAW4tMj3eo/64ge\\nsdaceaLYw52KeBYiT6+vpsnYrEkAHO1fF/LavbLLOFJmFTMxmsNaG0tuiJHgjshB\\" + + "n82DpMCbXG9YcCgI/DbzuIjsdj2JC1cascSP//3PmefWysucBQe7Jryb6NQtASmnv\\nCdDw/0jmZTEjpe4S1lxfHp" + + "lAhHFtdgYTvyYtaLZiVVkCgYEA8eVpof2rceecw/I6\\n5ng1q3Hl2usdWV/4mZMvR0fOemacLLfocX6IYxT1zA1FF" + + "JlbXSRsJMf/Qq39mOR2\\nSpW+hr4jCoHeRVYLgsbggtrevGmILAlNoqCMpGZ6vDmJpq6ECV9olliDvpPgWOP+\\nm" + + "YPDreFBGxWvQrADNbRt2dmGsrsCgYEAyUHqB2wvJHFqdmeBsaacewzV8x9WgmeX\\ngUIi9REwXlGDW0Mz50dxpxcK" + + "CAYn65+7TCnY5O/jmL0VRxU1J2mSWyWTo1C+17L0\\n3fUqjxL1pkefwecxwecvC+gFFYdJ4CQ/MHHXU81Lwl1iWdF" + + "Cd2UoGddYaOF+KNeM\\nHC7cmqra+JsCgYEAlUNywzq8nUg7282E+uICfCB0LfwejuymR93CtsFgb7cRd6ak\\nECR" + + "8FGfCpH8ruWJINllbQfcHVCX47ndLZwqv3oVFKh6pAS/vVI4dpOepP8++7y1u\\ncoOvtreXCX6XqfrWDtKIvv0vjl" + + "HBhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\\nkndj5uNl5SiuVxHFhcZJO+XWf6ofLUregtevZakGMn8EE1uVa" + + "2AY7eafmoU/nZPT\\n00YB0TBATdCbn/nBSuKDESkhSg9s2GEKQZG5hBmL5uCMfo09z3SfxZIhJdlerreP\\nJ7gSi" + + "dI12N+EZxYd4xIJh/HFDgp7RRO87f+WJkofMQKBgGTnClK1VMaCRbJZPriw\\nEfeFCoOX75MxKwXs6xgrw4W//AYG" + + "GUjDt83lD6AZP6tws7gJ2IwY/qP7+lyhjEqN\\nHtfPZRGFkGZsdaksdlaksd323423d+15/UvrlRSFPNj1tWQmNKk" + + "XyRDW4IG1Oa2p\\nrALStNBx5Y9t0/LQnFI4w3aG\\n-----END PRIVATE KEY-----\\n\",\n" + + " \"client_email\": \"someclientid@developer.gserviceaccount.com\",\n" + + " \"client_id\": \"someclientid.apps.googleusercontent.com\",\n" + + " \"type\": \"service_account\"\n" + + "}"; + private static final InputStream JSON_KEY_STREAM = new ByteArrayInputStream(JSON_KEY.getBytes()); + private static final List BLOB_LIST = ImmutableList.of( + BlobInfo.builder(BUCKET_NAME, "n1").build(), + BlobInfo.builder(BUCKET_NAME, "n2").build()); + private static final StorageException RETRYABLE_EXCEPTION = new StorageException(409, "", true); + private static final StorageException FATAL_EXCEPTION = new StorageException(500, "", false); + private static final ListResult BLOB_LIST_RESULT = new ListResult() { + + @Override + public String nextPageCursor() { + return "listResult"; + } + + @Override + public ListResult nextPage() { + return null; + } + + @Override + public Iterator iterator() { + return BLOB_LIST.iterator(); + } + }; + private static String KEY_PATH = "/does/not/exist/key." + UUID.randomUUID().toString() + ".json"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @BeforeClass + public static void beforeClass() { + while (Files.exists(Paths.get(JSON_KEY))) { + KEY_PATH = "/does/not/exist/key." + UUID.randomUUID().toString() + ".json"; + } + } + + @Test + public void testForceDelete() throws InterruptedException, ExecutionException { + Storage storageMock = EasyMock.createMock(Storage.class); + EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_LIST_RESULT); + for (BlobInfo info : BLOB_LIST) { + EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true); + } + EasyMock.expect(storageMock.delete(BUCKET_NAME)).andReturn(true); + EasyMock.replay(storageMock); + assertTrue(RemoteGcsHelper.forceDelete(storageMock, BUCKET_NAME, 5, TimeUnit.SECONDS)); + EasyMock.verify(storageMock); + } + + @Test + public void testForceDeleteTimeout() throws InterruptedException, ExecutionException { + Storage storageMock = EasyMock.createMock(Storage.class); + EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_LIST_RESULT).anyTimes(); + for (BlobInfo info : BLOB_LIST) { + EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true).anyTimes(); + } + EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(RETRYABLE_EXCEPTION).anyTimes(); + EasyMock.replay(storageMock); + assertTrue(!RemoteGcsHelper.forceDelete(storageMock, BUCKET_NAME, 50, TimeUnit.MICROSECONDS)); + EasyMock.verify(storageMock); + } + + @Test + public void testForceDeleteFail() throws InterruptedException, ExecutionException { + Storage storageMock = EasyMock.createMock(Storage.class); + EasyMock.expect(storageMock.list(BUCKET_NAME)).andReturn(BLOB_LIST_RESULT); + for (BlobInfo info : BLOB_LIST) { + EasyMock.expect(storageMock.delete(BUCKET_NAME, info.name())).andReturn(true); + } + EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(FATAL_EXCEPTION); + EasyMock.replay(storageMock); + thrown.expect(ExecutionException.class); + try { + RemoteGcsHelper.forceDelete(storageMock, BUCKET_NAME, 5, TimeUnit.SECONDS); + } finally { + EasyMock.verify(storageMock); + } + } + + @Test + public void testCreateFromStream() { + RemoteGcsHelper helper = RemoteGcsHelper.create(PROJECT_ID, JSON_KEY_STREAM); + StorageOptions options = helper.options(); + assertEquals(PROJECT_ID, options.projectId()); + assertEquals(60000, options.connectTimeout()); + assertEquals(60000, options.readTimeout()); + assertEquals(10, options.retryParams().getRetryMaxAttempts()); + assertEquals(6, options.retryParams().getRetryMinAttempts()); + assertEquals(30000, options.retryParams().getMaxRetryDelayMillis()); + assertEquals(120000, options.retryParams().getTotalRetryPeriodMillis()); + assertEquals(250, options.retryParams().getInitialRetryDelayMillis()); + } + + @Test + public void testCreateNoKey() { + thrown.expect(RemoteGcsHelper.GcsHelperException.class); + thrown.expectMessage(KEY_PATH + " (No such file or directory)"); + RemoteGcsHelper.create(PROJECT_ID, KEY_PATH); + } +}