diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml index d227206843..cc9d69330f 100644 --- a/google-cloud-storage/clirr-ignored-differences.xml +++ b/google-cloud-storage/clirr-ignored-differences.xml @@ -71,4 +71,11 @@ com/google/cloud/storage/transfermanager/TransferManagerConfig$Builder * setAllowDivideAndConquer(boolean) + + + 7013 + com/google/cloud/storage/StorageOptions$Builder + com.google.cloud.storage.StorageOptions$Builder setBlobWriteSessionConfig(com.google.cloud.storage.BlobWriteSessionConfig) + + diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteSession.java index 02ea23a6a7..81cecdc892 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteSession.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteSession.java @@ -20,6 +20,7 @@ import com.google.api.core.BetaApi; import java.io.IOException; import java.nio.channels.WritableByteChannel; +import java.util.concurrent.TimeUnit; /** * A session to write an object to Google Cloud Storage. @@ -50,6 +51,10 @@ public interface BlobWriteSession { *

Upon calling {@link WritableByteChannel#close()} the object creation will be finalized, and * {@link #getResult()}s future should resolve. * + *

The returned {@code WritableByteChannel} can throw IOExceptions from any of its usual + * methods. Any {@link IOException} thrown can have a cause of a {@link StorageException}. + * However, not all {@code IOExceptions} will have {@code StorageException}s. + * * @throws IOException When creating the {@link WritableByteChannel} if an unrecoverable * underlying IOException occurs it can be rethrown * @throws IllegalStateException if open is called more than once @@ -66,6 +71,10 @@ public interface BlobWriteSession { * Google Cloud Storage 2. A terminal failure occurs, the terminal failure will become the * exception result * + *

If a terminal failure is encountered, calling either {@link ApiFuture#get()} or {@link + * ApiFuture#get(long, TimeUnit)} will result in an {@link + * java.util.concurrent.ExecutionException} with a cause that is the {@link StorageException}. + * * @since 2.26.0 This new api is in preview and is subject to breaking changes. */ @BetaApi diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteSessions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteSessions.java index 878552a125..c9da9ee05c 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteSessions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteSessions.java @@ -17,6 +17,7 @@ package com.google.cloud.storage; import com.google.api.core.ApiFuture; +import com.google.common.base.Preconditions; import java.io.IOException; import java.nio.channels.WritableByteChannel; @@ -30,14 +31,20 @@ static BlobWriteSession of(WritableByteChannelSession s) { static final class WritableByteChannelSessionAdapter implements BlobWriteSession { private final WritableByteChannelSession delegate; + private boolean open; private WritableByteChannelSessionAdapter(WritableByteChannelSession delegate) { this.delegate = delegate; + open = false; } @Override public WritableByteChannel open() throws IOException { - return delegate.open(); + synchronized (this) { + Preconditions.checkState(!open, "already open"); + open = true; + return delegate.open(); + } } @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/DefaultBlobWriteSessionConfig.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/DefaultBlobWriteSessionConfig.java index 1d45c9e34c..ffcbcfa8ee 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/DefaultBlobWriteSessionConfig.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/DefaultBlobWriteSessionConfig.java @@ -32,6 +32,8 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.storage.v2.WriteObjectRequest; import com.google.storage.v2.WriteObjectResponse; +import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.time.Clock; import java.util.Map; @@ -202,24 +204,25 @@ static final class DecoratedWritableByteChannelSession openAsync() { - return delegate.openAsync(); + return ApiFutures.catchingAsync( + delegate.openAsync(), + Throwable.class, + throwable -> ApiFutures.immediateFailedFuture(StorageException.coalesce(throwable)), + MoreExecutors.directExecutor()); } @Override public ApiFuture getResult() { - return ApiFutures.transform( - delegate.getResult(), decoder::decode, MoreExecutors.directExecutor()); + ApiFuture decodeResult = + ApiFutures.transform( + delegate.getResult(), decoder::decode, MoreExecutors.directExecutor()); + return ApiFutures.catchingAsync( + decodeResult, + Throwable.class, + throwable -> ApiFutures.immediateFailedFuture(StorageException.coalesce(throwable)), + MoreExecutors.directExecutor()); } } @@ -233,7 +236,55 @@ static final class LazySession @Override public ApiFuture openAsync() { - return lazy.getSession().openAsync(); + // make sure the errors coming out of the BufferedWritableByteChannel are either IOException + // or StorageException + return ApiFutures.transform( + lazy.getSession().openAsync(), + delegate -> + new BufferedWritableByteChannel() { + @Override + public int write(ByteBuffer src) throws IOException { + try { + return delegate.write(src); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw StorageException.coalesce(e); + } + } + + @Override + public void flush() throws IOException { + try { + delegate.flush(); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw StorageException.coalesce(e); + } + } + + @Override + public boolean isOpen() { + try { + return delegate.isOpen(); + } catch (Exception e) { + throw StorageException.coalesce(e); + } + } + + @Override + public void close() throws IOException { + try { + delegate.close(); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw StorageException.coalesce(e); + } + } + }, + MoreExecutors.directExecutor()); } @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedWritableByteChannel.java index b4a26ed4d3..32d5eb9dc8 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedWritableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedWritableByteChannel.java @@ -92,7 +92,12 @@ public void close() throws IOException { throw e; } } else { - flusher.close(null); + try { + flusher.close(null); + } catch (RuntimeException e) { + resultFuture.setException(e); + throw e; + } } open = false; } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java index ac52021308..2d1daa5444 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java @@ -49,7 +49,7 @@ GapicBidiWritableByteChannelSessionBuilder bidiByteChannel( } ApiFuture resumableWrite( - UnaryCallable x, + UnaryCallable callable, WriteObjectRequest writeObjectRequest) { StartResumableWriteRequest.Builder b = StartResumableWriteRequest.newBuilder(); if (writeObjectRequest.hasWriteObjectSpec()) { @@ -65,9 +65,16 @@ ApiFuture resumableWrite( Function f = uploadId -> writeObjectRequest.toBuilder().clearWriteObjectSpec().setUploadId(uploadId).build(); - return ApiFutures.transform( - x.futureCall(req), - (resp) -> new ResumableWrite(req, resp, f), + ApiFuture futureResumableWrite = + ApiFutures.transform( + callable.futureCall(req), + (resp) -> new ResumableWrite(req, resp, f), + MoreExecutors.directExecutor()); + // make sure we wrap any failure as a storage exception + return ApiFutures.catchingAsync( + futureResumableWrite, + Throwable.class, + throwable -> ApiFutures.immediateFailedFuture(StorageException.coalesce(throwable)), MoreExecutors.directExecutor()); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java index 192cfec6dd..622988d4cb 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java @@ -120,7 +120,7 @@ public void rewindTo(long offset) { } } else if (finalizing && JsonResumableSessionFailureScenario.isOk(code)) { @Nullable StorageObject storageObject; - @Nullable BigInteger actualSize; + BigInteger actualSize = BigInteger.ZERO; Long contentLength = response.getHeaders().getContentLength(); String contentType = response.getHeaders().getContentType(); @@ -130,7 +130,12 @@ public void rewindTo(long offset) { boolean isJson = contentType != null && contentType.startsWith("application/json"); if (isJson) { storageObject = response.parseAs(StorageObject.class); - actualSize = storageObject != null ? storageObject.getSize() : null; + if (storageObject != null) { + BigInteger size = storageObject.getSize(); + if (size != null) { + actualSize = size; + } + } } else if ((contentLength == null || contentLength == 0) && storedContentLength != null) { // when a signed url is used, the finalize response is empty response.ignore(); @@ -150,7 +155,6 @@ public void rewindTo(long offset) { int compare = expectedSize.compareTo(actualSize); if (compare == 0) { success = true; - //noinspection DataFlowIssue compareTo result will filter out actualSize == null return ResumableOperationResult.complete(storageObject, actualSize.longValue()); } else if (compare > 0) { StorageException se = diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java index f8b4f94a8d..eeb24a44b8 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java @@ -118,6 +118,9 @@ static BaseServiceException coalesce(Throwable t) { if (t instanceof ApiException) { return asStorageException((ApiException) t); } + if (t.getCause() instanceof ApiException) { + return asStorageException((ApiException) t.getCause()); + } return getStorageException(t); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java index 53ad6142e9..ab32532ae1 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java @@ -22,14 +22,17 @@ import com.google.cloud.ServiceDefaults; import com.google.cloud.ServiceOptions; import com.google.cloud.http.HttpTransportOptions; +import com.google.cloud.storage.GrpcStorageOptions.GrpcStorageDefaults; import com.google.cloud.storage.HttpStorageOptions.HttpStorageDefaults; import com.google.cloud.storage.HttpStorageOptions.HttpStorageFactory; import com.google.cloud.storage.HttpStorageOptions.HttpStorageRpcFactory; +import com.google.cloud.storage.Storage.BlobWriteOption; import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.spi.StorageRpcFactory; import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import org.checkerframework.checker.nullness.qual.NonNull; public abstract class StorageOptions extends ServiceOptions { @@ -95,6 +98,18 @@ public abstract static class Builder public abstract Builder setStorageRetryStrategy(StorageRetryStrategy storageRetryStrategy); + /** + * @see BlobWriteSessionConfig + * @see BlobWriteSessionConfigs + * @see Storage#blobWriteSession(BlobInfo, BlobWriteOption...) + * @see HttpStorageDefaults#getDefaultStorageWriterConfig() + * @see GrpcStorageDefaults#getDefaultStorageWriterConfig() + * @since 2.37.0 This new api is in preview and is subject to breaking changes. + */ + @BetaApi + public abstract StorageOptions.Builder setBlobWriteSessionConfig( + @NonNull BlobWriteSessionConfig blobWriteSessionConfig); + @Override public abstract StorageOptions build(); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteSessionCommonSemanticsTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteSessionCommonSemanticsTest.java new file mode 100644 index 0000000000..dc02c3fb9b --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteSessionCommonSemanticsTest.java @@ -0,0 +1,223 @@ +/* + * Copyright 2024 Google LLC + * + * 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.cloud.storage.it; + +import static com.google.cloud.storage.TestUtils.assertAll; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.BlobWriteSession; +import com.google.cloud.storage.BlobWriteSessionConfig; +import com.google.cloud.storage.BlobWriteSessionConfigs; +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.BufferAllocationStrategy; +import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.ExecutorSupplier; +import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.PartCleanupStrategy; +import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.PartMetadataFieldDecorator; +import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.PartNamingStrategy; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.Storage.BlobTargetOption; +import com.google.cloud.storage.Storage.BlobWriteOption; +import com.google.cloud.storage.StorageException; +import com.google.cloud.storage.StorageOptions; +import com.google.cloud.storage.TransportCompatibility.Transport; +import com.google.cloud.storage.it.ITBlobWriteSessionCommonSemanticsTest.ParamsProvider; +import com.google.cloud.storage.it.runner.StorageITRunner; +import com.google.cloud.storage.it.runner.annotations.Backend; +import com.google.cloud.storage.it.runner.annotations.CrossRun; +import com.google.cloud.storage.it.runner.annotations.Inject; +import com.google.cloud.storage.it.runner.annotations.Parameterized; +import com.google.cloud.storage.it.runner.annotations.Parameterized.Parameter; +import com.google.cloud.storage.it.runner.annotations.Parameterized.ParametersProvider; +import com.google.cloud.storage.it.runner.registry.Generator; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@CrossRun( + backends = {Backend.PROD}, + transports = {Transport.HTTP, Transport.GRPC}) +@Parameterized(ParamsProvider.class) +public final class ITBlobWriteSessionCommonSemanticsTest { + + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Inject public Storage injectedStorage; + @Inject public BucketInfo bucket; + @Inject public Generator generator; + + @Parameter public Params params; + + private Storage storage; + + @Before + public void setUp() throws Exception { + Path tmpDir = temporaryFolder.newFolder().toPath(); + BlobWriteSessionConfig config = params.ctor.apply(tmpDir); + + StorageOptions originalOptions = injectedStorage.getOptions(); + StorageOptions newOptions = null; + try { + newOptions = originalOptions.toBuilder().setBlobWriteSessionConfig(config).build(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageThat().contains("not compatible with this"); + assumeTrue(false); + } + storage = newOptions.getService(); + } + + @After + public void tearDown() throws Exception { + if (storage != null) { + storage.close(); + } + } + + @Test + public void closingAnOpenedSessionWithoutCallingWriteShouldMakeAnEmptyObject() + throws IOException, ExecutionException, InterruptedException, TimeoutException { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + BlobWriteSession session = storage.blobWriteSession(info, BlobWriteOption.doesNotExist()); + + WritableByteChannel open = session.open(); + open.close(); + BlobInfo gen1 = session.getResult().get(1, TimeUnit.SECONDS); + + // sometimes testbench will not define `.size = 0`, default it here if we get null + Long size = gen1.getSize(); + if (size == null) { + size = 0L; + } + assertThat(size).isEqualTo(0); + } + + @Test + public void attemptingToUseASessionWhichResultsInFailureShouldThrowAStorageException() { + // attempt to write to a bucket which we have not created + String badBucketName = bucket.getName() + "x"; + BlobInfo info = BlobInfo.newBuilder(badBucketName, generator.randomObjectName()).build(); + + BlobWriteSession session = storage.blobWriteSession(info, BlobWriteOption.doesNotExist()); + StorageException se = + assertThrows( + StorageException.class, + () -> { + WritableByteChannel open = session.open(); + open.close(); + }); + + assertThat(se.getCode()).isEqualTo(404); + } + + @Test + public void callingOpenIsOnlyAllowedOnce() throws Exception { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + BlobWriteSession session = storage.blobWriteSession(info, BlobWriteOption.doesNotExist()); + + WritableByteChannel open = session.open(); + IllegalStateException se = assertThrows(IllegalStateException.class, session::open); + + assertAll(() -> assertThat(se.getMessage()).contains("already open")); + } + + @Test + public void getResultErrorsWhenTheSessionErrors() throws Exception { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + byte[] helloWorld = "Hello World".getBytes(StandardCharsets.UTF_8); + storage.create(info, helloWorld, BlobTargetOption.doesNotExist()); + + BlobWriteSession session = + storage.blobWriteSession( + info, + // this precondition will result in failure + BlobWriteOption.doesNotExist()); + + try (WritableByteChannel open = session.open()) { + open.write(ByteBuffer.wrap(helloWorld)); + } catch (StorageException se) { + assertThat(se.getCode()).isEqualTo(412); + } catch (IOException ioe) { + assertThat(ioe).hasCauseThat().isInstanceOf(StorageException.class); + StorageException se = (StorageException) ioe.getCause(); + assertThat(se.getCode()).isEqualTo(412); + } + + ExecutionException resultSe = + assertThrows(ExecutionException.class, () -> session.getResult().get(10, TimeUnit.SECONDS)); + + assertAll( + () -> assertThat(resultSe).hasCauseThat().isInstanceOf(StorageException.class), + () -> assertThat(((StorageException) resultSe.getCause()).getCode()).isEqualTo(412)); + } + + public static final class ParamsProvider implements ParametersProvider { + @Override + public ImmutableList parameters() { + final int _2MiB = 2 * 1024 * 1024; + final int _4MiB = 4 * 1024 * 1024; + return ImmutableList.of( + new Params("default", p -> BlobWriteSessionConfigs.getDefault()), + new Params("c!c.2MiB", p -> BlobWriteSessionConfigs.getDefault().withChunkSize(_2MiB)), + new Params("b!p.1", BlobWriteSessionConfigs::bufferToDiskThenUpload), + new Params("j!p.1", p -> BlobWriteSessionConfigs.journaling(ImmutableList.of(p))), + new Params( + "p!t.c&b.s*&p.4MiB&c.n&m.n", + p -> + BlobWriteSessionConfigs.parallelCompositeUpload() + .withExecutorSupplier(ExecutorSupplier.cachedPool()) + .withPartNamingStrategy(PartNamingStrategy.noPrefix()) + .withBufferAllocationStrategy(BufferAllocationStrategy.simple(_4MiB)) + .withPartCleanupStrategy(PartCleanupStrategy.never()) + .withPartMetadataFieldDecorator(PartMetadataFieldDecorator.noOp())), + new Params("d!c.2MiB", p -> BlobWriteSessionConfigs.bidiWrite().withBufferSize(_2MiB))); + } + } + + public interface ParamsCtor { + BlobWriteSessionConfig apply(Path p) throws IOException; + } + + public static final class Params { + private final String desc; + private final ParamsCtor ctor; + + public Params(String desc, ParamsCtor ctor) { + this.desc = desc; + this.ctor = ctor; + } + + @Override + public String toString() { + return desc; + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteSessionTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteSessionTest.java index 4abe949498..10ae174a35 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteSessionTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteSessionTest.java @@ -17,7 +17,6 @@ package com.google.cloud.storage.it; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import com.google.cloud.storage.BlobInfo; @@ -25,12 +24,10 @@ import com.google.cloud.storage.BlobWriteSessionConfigs; import com.google.cloud.storage.BucketInfo; import com.google.cloud.storage.DataGenerator; -import com.google.cloud.storage.GrpcStorageOptions; import com.google.cloud.storage.HttpStorageOptions; import com.google.cloud.storage.JournalingBlobWriteSessionConfig; import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage.BlobWriteOption; -import com.google.cloud.storage.StorageException; import com.google.cloud.storage.StorageOptions; import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.it.runner.StorageITRunner; @@ -39,13 +36,10 @@ import com.google.cloud.storage.it.runner.annotations.Inject; import com.google.cloud.storage.it.runner.registry.Generator; import com.google.common.collect.ImmutableList; -import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.nio.file.Path; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -60,7 +54,6 @@ public final class ITBlobWriteSessionTest { @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @Inject public Storage storage; - @Inject public Transport transport; @Inject public BucketInfo bucket; @@ -73,22 +66,13 @@ public void allDefaults() throws Exception { @Test public void bufferToTempDirThenUpload() throws Exception { - StorageOptions options = null; - if (transport == Transport.GRPC) { - options = - ((GrpcStorageOptions) storage.getOptions()) - .toBuilder() - .setBlobWriteSessionConfig(BlobWriteSessionConfigs.bufferToTempDirThenUpload()) - .build(); - } else if (transport == Transport.HTTP) { - options = - ((HttpStorageOptions) storage.getOptions()) - .toBuilder() - .setBlobWriteSessionConfig(BlobWriteSessionConfigs.bufferToTempDirThenUpload()) - .build(); - } - assertWithMessage("unable to resolve options").that(options).isNotNull(); - //noinspection DataFlowIssue + Path path = temporaryFolder.newFolder().toPath(); + StorageOptions options = + storage + .getOptions() + .toBuilder() + .setBlobWriteSessionConfig(BlobWriteSessionConfigs.bufferToDiskThenUpload(path)) + .build(); try (Storage s = options.getService()) { doTest(s); } @@ -112,24 +96,13 @@ public void journalingNotSupportedByHttp() { @Test public void overrideDefaultBufferSize() throws Exception { - StorageOptions options = null; - if (transport == Transport.GRPC) { - options = - ((GrpcStorageOptions) storage.getOptions()) - .toBuilder() - .setBlobWriteSessionConfig( - BlobWriteSessionConfigs.getDefault().withChunkSize(256 * 1024)) - .build(); - } else if (transport == Transport.HTTP) { - options = - ((HttpStorageOptions) storage.getOptions()) - .toBuilder() - .setBlobWriteSessionConfig( - BlobWriteSessionConfigs.getDefault().withChunkSize(256 * 1024)) - .build(); - } - assertWithMessage("unable to resolve options").that(options).isNotNull(); - //noinspection DataFlowIssue + StorageOptions options = + (storage.getOptions()) + .toBuilder() + .setBlobWriteSessionConfig( + BlobWriteSessionConfigs.getDefault().withChunkSize(256 * 1024)) + .build(); + try (Storage s = options.getService()) { doTest(s); } @@ -138,47 +111,16 @@ public void overrideDefaultBufferSize() throws Exception { @Test @CrossRun.Exclude(transports = Transport.HTTP) public void bidiTest() throws Exception { - StorageOptions options = null; - if (transport == Transport.GRPC) { - options = - ((GrpcStorageOptions) storage.getOptions()) - .toBuilder() - .setBlobWriteSessionConfig(BlobWriteSessionConfigs.bidiWrite()) - .build(); - } - assertWithMessage("unable to resolve options").that(options).isNotNull(); - + StorageOptions options = + (storage.getOptions()) + .toBuilder() + .setBlobWriteSessionConfig(BlobWriteSessionConfigs.bidiWrite()) + .build(); try (Storage s = options.getService()) { doTest(s); } } - @Test - public void closingAnOpenedSessionWithoutCallingWriteShouldMakeAnEmptyObject() - throws IOException, ExecutionException, InterruptedException, TimeoutException { - BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); - BlobWriteSession session = storage.blobWriteSession(info, BlobWriteOption.doesNotExist()); - - WritableByteChannel open = session.open(); - open.close(); - BlobInfo gen1 = session.getResult().get(1, TimeUnit.SECONDS); - - assertThat(gen1.getSize()).isEqualTo(0); - } - - @Test - public void attemptingToOpenASessionWhichResultsInFailureShouldThrowAStorageException() { - // attempt to write to a bucket which we have not created - String badBucketName = bucket.getName() + "x"; - BlobInfo info = BlobInfo.newBuilder(badBucketName, generator.randomObjectName()).build(); - - BlobWriteSession session = storage.blobWriteSession(info, BlobWriteOption.doesNotExist()); - StorageException se = assertThrows(StorageException.class, () -> session.open().close()); - - assertThat(se.getCode()).isEqualTo(404); - assertThat(se).hasMessageThat().contains(badBucketName); - } - private void doTest(Storage underTest) throws Exception { BlobWriteSession sess = underTest.blobWriteSession( diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITJournalingBlobWriteSessionConfigTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITJournalingBlobWriteSessionConfigTest.java index 4526a09f69..70e369f307 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITJournalingBlobWriteSessionConfigTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITJournalingBlobWriteSessionConfigTest.java @@ -24,7 +24,6 @@ import com.google.cloud.storage.BlobWriteSessionConfigs; import com.google.cloud.storage.BucketInfo; import com.google.cloud.storage.DataGenerator; -import com.google.cloud.storage.GrpcStorageOptions; import com.google.cloud.storage.JournalingBlobWriteSessionConfig; import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage.BlobWriteOption; @@ -75,7 +74,9 @@ public void setUp() throws Exception { JournalingBlobWriteSessionConfig journaling = BlobWriteSessionConfigs.journaling(ImmutableList.of(tempDir)); journalingStorage = - ((GrpcStorageOptions.Builder) this.storage.getOptions().toBuilder()) + this.storage + .getOptions() + .toBuilder() .setBlobWriteSessionConfig(journaling) .build() .getService(); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITParallelCompositeUploadBlobWriteSessionConfigTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITParallelCompositeUploadBlobWriteSessionConfigTest.java index 3352a92adc..9b7d630f6b 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITParallelCompositeUploadBlobWriteSessionConfigTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITParallelCompositeUploadBlobWriteSessionConfigTest.java @@ -18,7 +18,6 @@ import static com.google.cloud.storage.TestUtils.xxd; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import com.google.api.gax.paging.Page; @@ -30,8 +29,6 @@ import com.google.cloud.storage.BlobWriteSessionConfigs; import com.google.cloud.storage.BucketInfo; import com.google.cloud.storage.DataGenerator; -import com.google.cloud.storage.GrpcStorageOptions; -import com.google.cloud.storage.HttpStorageOptions; import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig; import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.BufferAllocationStrategy; import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.ExecutorSupplier; @@ -119,22 +116,8 @@ public void setUp() throws Exception { // let our fixtures take care of cleaning things .withPartCleanupStrategy(PartCleanupStrategy.never()); - StorageOptions storageOptions = null; - if (transport == Transport.GRPC) { - storageOptions = - ((GrpcStorageOptions) injectedStorage.getOptions()) - .toBuilder() - .setBlobWriteSessionConfig(pcu) - .build(); - } else if (transport == Transport.HTTP) { - storageOptions = - ((HttpStorageOptions) injectedStorage.getOptions()) - .toBuilder() - .setBlobWriteSessionConfig(pcu) - .build(); - } - assertWithMessage("unable to resolve options").that(storageOptions).isNotNull(); - //noinspection DataFlowIssue + StorageOptions storageOptions = + injectedStorage.getOptions().toBuilder().setBlobWriteSessionConfig(pcu).build(); storage = storageOptions.getService(); rand = new Random(); }