From d54b9cd665f1f1bd362837c5881f3e16a7adc0bd Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:04:13 +0000 Subject: [PATCH 01/20] chore(main): release 2.24.1-SNAPSHOT (#2113) :robot: I have created a release *beep* *boop* --- ### Updating meta-information for bleeding-edge SNAPSHOT release. --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- gapic-google-cloud-storage-v2/pom.xml | 4 ++-- google-cloud-storage-bom/pom.xml | 10 +++++----- google-cloud-storage/pom.xml | 4 ++-- grpc-google-cloud-storage-v2/pom.xml | 4 ++-- pom.xml | 10 +++++----- proto-google-cloud-storage-v2/pom.xml | 4 ++-- samples/snapshot/pom.xml | 2 +- versions.txt | 8 ++++---- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/gapic-google-cloud-storage-v2/pom.xml b/gapic-google-cloud-storage-v2/pom.xml index b8ed6ea0dd..cb6b23a34b 100644 --- a/gapic-google-cloud-storage-v2/pom.xml +++ b/gapic-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT gapic-google-cloud-storage-v2 GRPC library for gapic-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.24.0 + 2.24.1-SNAPSHOT diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index 0d1f17d169..1d0fd5c6a7 100644 --- a/google-cloud-storage-bom/pom.xml +++ b/google-cloud-storage-bom/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.google.cloud google-cloud-storage-bom - 2.24.0 + 2.24.1-SNAPSHOT pom com.google.cloud @@ -69,22 +69,22 @@ com.google.cloud google-cloud-storage - 2.24.0 + 2.24.1-SNAPSHOT com.google.api.grpc gapic-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 32f27bf88f..ccfc41547b 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-storage - 2.24.0 + 2.24.1-SNAPSHOT jar Google Cloud Storage https://github.com/googleapis/java-storage @@ -12,7 +12,7 @@ com.google.cloud google-cloud-storage-parent - 2.24.0 + 2.24.1-SNAPSHOT google-cloud-storage diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml index c96df28ccf..e51dfb903e 100644 --- a/grpc-google-cloud-storage-v2/pom.xml +++ b/grpc-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT grpc-google-cloud-storage-v2 GRPC library for grpc-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.24.0 + 2.24.1-SNAPSHOT diff --git a/pom.xml b/pom.xml index 8945ba764c..af3f0ffde2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-storage-parent pom - 2.24.0 + 2.24.1-SNAPSHOT Storage Parent https://github.com/googleapis/java-storage @@ -83,7 +83,7 @@ com.google.cloud google-cloud-storage - 2.24.0 + 2.24.1-SNAPSHOT com.google.apis @@ -124,17 +124,17 @@ com.google.api.grpc proto-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT com.google.api.grpc gapic-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT com.google.cloud diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml index 8d988a125a..9068b9edec 100644 --- a/proto-google-cloud-storage-v2/pom.xml +++ b/proto-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-storage-v2 - 2.24.0-alpha + 2.24.1-alpha-SNAPSHOT proto-google-cloud-storage-v2 PROTO library for proto-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.24.0 + 2.24.1-SNAPSHOT diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 6a8e288f76..3723a7f2eb 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-storage - 2.24.0 + 2.24.1-SNAPSHOT diff --git a/versions.txt b/versions.txt index 15bc7260a4..1cd08c2324 100644 --- a/versions.txt +++ b/versions.txt @@ -1,7 +1,7 @@ # Format: # module:released-version:current-version -google-cloud-storage:2.24.0:2.24.0 -gapic-google-cloud-storage-v2:2.24.0-alpha:2.24.0-alpha -grpc-google-cloud-storage-v2:2.24.0-alpha:2.24.0-alpha -proto-google-cloud-storage-v2:2.24.0-alpha:2.24.0-alpha +google-cloud-storage:2.24.0:2.24.1-SNAPSHOT +gapic-google-cloud-storage-v2:2.24.0-alpha:2.24.1-alpha-SNAPSHOT +grpc-google-cloud-storage-v2:2.24.0-alpha:2.24.1-alpha-SNAPSHOT +proto-google-cloud-storage-v2:2.24.0-alpha:2.24.1-alpha-SNAPSHOT From 4c2f44e28a1ff19ffb2a02e3cefc062a1dd98fdc Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Thu, 13 Jul 2023 16:06:32 -0400 Subject: [PATCH 02/20] feat: update Storage.createFrom(BlobInfo, Path) to have 150% higher throughput (#2059) When uploading a file where we are able to rewind to an arbitrary offset, we can be more optimistic in the way we send requests to GCS. Add new code middleware to allow PUTing an entire file to GCS in a single request, and using query resumable session to recover from the specific offset in the case of retryable error. ### Benchmark Results #### Methodology Generate a random file on disk of size `128KiB..2GiB` from `/dev/urandom`, then upload the generated file using `Storage.createFrom(BlobInfo, Path)`. Perform each 4096 times. Run on a c2-standard-60 instance is us-central1 against a regional bucket located in us-central1. #### Results The following summary of throughput in MiB/s as observed between the existing implementation, and the new implementation proposed in this PR. ``` count mean std min 50% 75% 90% 99% max runId ApiName createFrom - existing JSON 4096.0 66.754 10.988 3.249 67.317 73.476 78.961 91.197 107.247 createFrom - new JSON 4096.0 158.769 67.105 4.600 170.680 218.618 240.992 266.297 305.205 ``` #### Comparison When comparing the new implementation to the existing implementation we get the following improvement to throughput (higher is better): ``` stat pct mean 137.841 50% 153.547 90% 205.204 99% 192.003 ``` --- .../google/cloud/storage/ByteRangeSpec.java | 17 + .../cloud/storage/ByteSizeConstants.java | 6 + .../cloud/storage/HttpClientContext.java | 85 ++ .../cloud/storage/HttpContentRange.java | 249 ++++++ .../cloud/storage/HttpStorageOptions.java | 27 + .../cloud/storage/JsonResumableSession.java | 83 ++ .../JsonResumableSessionFailureScenario.java | 233 +++++ .../storage/JsonResumableSessionPutTask.java | 218 +++++ .../JsonResumableSessionQueryTask.java | 136 +++ .../cloud/storage/JsonResumableWrite.java | 88 ++ .../storage/ResumableOperationResult.java | 90 ++ .../cloud/storage/ResumableSession.java | 39 + .../cloud/storage/RewindableHttpContent.java | 106 +++ .../com/google/cloud/storage/StorageImpl.java | 37 +- .../google/cloud/storage/FakeHttpServer.java | 144 ++++ .../ITJsonResumableSessionPutTaskTest.java | 814 ++++++++++++++++++ .../ITJsonResumableSessionQueryTaskTest.java | 235 +++++ .../storage/ITJsonResumableSessionTest.java | 134 +++ ...onResumableSessionFailureScenarioTest.java | 155 ++++ .../RewindableHttpContentPropertyTest.java | 201 +++++ .../cloud/storage/StorageImplMockitoTest.java | 11 - .../storage/conformance/retry/Functions.java | 4 + .../retry/ITRetryConformanceTest.java | 13 +- .../conformance/retry/RpcMethodMappings.java | 26 +- .../it/ITObjectChecksumSupportTest.java | 53 +- 25 files changed, 3184 insertions(+), 20 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/HttpClientContext.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/HttpContentRange.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionFailureScenario.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableOperationResult.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSession.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionQueryTaskTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java index ca4cd64d71..524bfe3c2e 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java @@ -16,6 +16,9 @@ package com.google.cloud.storage; +import static com.google.api.client.util.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.api.core.InternalApi; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; @@ -124,6 +127,20 @@ static ByteRangeSpec explicitClosed( return create(beginOffset, endOffsetInclusive, LeftClosedRightClosedByteRangeSpec::new); } + static ByteRangeSpec parse(String string) { + checkNotNull(string, "Range header is null"); + checkArgument(string.startsWith("bytes="), "malformed Range header value: %s", string); + + int i = string.indexOf('-'); + String minS = string.substring(6, i); + String maxS = string.substring(i + 1); + + long min = Long.parseLong(minS); + long max = Long.parseLong(maxS); + + return explicitClosed(min, max); + } + private static ByteRangeSpec create( @Nullable Long beginOffset, @Nullable Long length, diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteSizeConstants.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteSizeConstants.java index cac56927fa..98b31a3ded 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteSizeConstants.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteSizeConstants.java @@ -23,10 +23,16 @@ final class ByteSizeConstants { static final int _256KiB = 256 * _1KiB; static final int _384KiB = 384 * _1KiB; static final int _512KiB = 512 * _1KiB; + static final int _768KiB = 768 * _1KiB; static final int _1MiB = 1024 * _1KiB; static final int _2MiB = 2 * _1MiB; static final int _16MiB = 16 * _1MiB; static final int _32MiB = 32 * _1MiB; + static final long _128KiBL = 131072L; + static final long _256KiBL = 262144L; + static final long _512KiBL = 524288L; + static final long _768KiBL = 786432L; + private ByteSizeConstants() {} } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpClientContext.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpClientContext.java new file mode 100644 index 0000000000..35c93c02f5 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpClientContext.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 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; + +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.util.ObjectParser; +import com.google.cloud.storage.spi.v1.StorageRpc; +import io.opencensus.trace.Span; +import io.opencensus.trace.Tracer; +import io.opencensus.trace.Tracing; +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +final class HttpClientContext { + + private final HttpRequestFactory requestFactory; + private final ObjectParser objectParser; + private final Tracer tracer; + + private HttpClientContext( + HttpRequestFactory requestFactory, ObjectParser objectParser, Tracer tracer) { + this.requestFactory = requestFactory; + this.objectParser = objectParser; + this.tracer = tracer; + } + + @SuppressWarnings({"unchecked", "SameParameterValue"}) + static @Nullable String firstHeaderValue( + @NonNull HttpHeaders headers, @NonNull String headerName) { + Object v = headers.get(headerName); + // HttpHeaders doesn't type its get method, so we have to jump through hoops here + if (v instanceof List) { + List list = (List) v; + return list.get(0); + } else { + return null; + } + } + + public HttpRequestFactory getRequestFactory() { + return requestFactory; + } + + public ObjectParser getObjectParser() { + return objectParser; + } + + public Tracer getTracer() { + return tracer; + } + + public Span startSpan(String name) { + // record events is hardcoded to true in HttpStorageRpc, preserve it here + return tracer.spanBuilder(name).setRecordEvents(true).startSpan(); + } + + static HttpClientContext from(StorageRpc storageRpc) { + return new HttpClientContext( + storageRpc.getStorage().getRequestFactory(), + storageRpc.getStorage().getObjectParser(), + Tracing.getTracer()); + } + + public static HttpClientContext of( + HttpRequestFactory requestFactory, JsonObjectParser jsonObjectParser) { + return new HttpClientContext(requestFactory, jsonObjectParser, Tracing.getTracer()); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpContentRange.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpContentRange.java new file mode 100644 index 0000000000..8961f52393 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpContentRange.java @@ -0,0 +1,249 @@ +/* + * Copyright 2023 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; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.MoreObjects; +import java.util.Objects; +import java.util.function.UnaryOperator; + +abstract class HttpContentRange { + + private final boolean finalizing; + + private HttpContentRange(boolean finalizing) { + this.finalizing = finalizing; + } + + public abstract String getHeaderValue(); + + public boolean isFinalizing() { + return finalizing; + } + + static Total of(ByteRangeSpec spec, long size) { + checkArgument(size >= 0, "size must be >= 0"); + checkArgument(size >= spec.endOffsetInclusive(), "size must be >= end"); + return new Total(spec, size); + } + + static Incomplete of(ByteRangeSpec spec) { + return new Incomplete(spec); + } + + static Size of(long size) { + checkArgument(size >= 0, "size must be >= 0"); + return new Size(size); + } + + static Query query() { + return Query.INSTANCE; + } + + static HttpContentRange parse(String string) { + if ("bytes */*".equals(string)) { + return HttpContentRange.query(); + } else if (string.startsWith("bytes */")) { + return HttpContentRange.of(Long.parseLong(string.substring(8))); + } else { + int idxDash = string.indexOf('-'); + int idxSlash = string.indexOf('/'); + + String beginS = string.substring(6, idxDash); + String endS = string.substring(idxDash + 1, idxSlash); + long begin = Long.parseLong(beginS); + long end = Long.parseLong(endS); + if (string.endsWith("/*")) { + return HttpContentRange.of(ByteRangeSpec.explicitClosed(begin, end)); + } else { + String sizeS = string.substring(idxSlash + 1); + long size = Long.parseLong(sizeS); + return HttpContentRange.of(ByteRangeSpec.explicitClosed(begin, end), size); + } + } + } + + static final class Incomplete extends HttpContentRange implements HasRange { + + private final ByteRangeSpec spec; + + private Incomplete(ByteRangeSpec spec) { + super(false); + this.spec = spec; + } + + @Override + public String getHeaderValue() { + return String.format("bytes %d-%d/*", spec.beginOffset(), spec.endOffsetInclusive()); + } + + @Override + public ByteRangeSpec range() { + return spec; + } + + @Override + public Incomplete map(UnaryOperator f) { + return new Incomplete(f.apply(spec)); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Incomplete)) { + return false; + } + Incomplete that = (Incomplete) o; + return Objects.equals(spec, that.spec); + } + + @Override + public int hashCode() { + return Objects.hash(spec); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("spec", spec).toString(); + } + } + + static final class Total extends HttpContentRange implements HasRange, HasSize { + + private final ByteRangeSpec spec; + private final long size; + + private Total(ByteRangeSpec spec, long size) { + super(true); + this.spec = spec; + this.size = size; + } + + @Override + public String getHeaderValue() { + return String.format("bytes %d-%d/%d", spec.beginOffset(), spec.endOffsetInclusive(), size); + } + + @Override + public long getSize() { + return size; + } + + @Override + public ByteRangeSpec range() { + return spec; + } + + @Override + public Total map(UnaryOperator f) { + return new Total(f.apply(spec), size); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Total)) { + return false; + } + Total total = (Total) o; + return size == total.size && Objects.equals(spec, total.spec); + } + + @Override + public int hashCode() { + return Objects.hash(spec, size); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("spec", spec).add("size", size).toString(); + } + } + + static final class Size extends HttpContentRange implements HasSize { + + private final long size; + + private Size(long size) { + super(true); + this.size = size; + } + + @Override + public String getHeaderValue() { + return String.format("bytes */%d", size); + } + + @Override + public long getSize() { + return size; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Size)) { + return false; + } + Size size1 = (Size) o; + return size == size1.size; + } + + @Override + public int hashCode() { + return Objects.hash(size); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("size", size).toString(); + } + } + + static final class Query extends HttpContentRange { + + private static final Query INSTANCE = new Query(); + + private Query() { + super(false); + } + + @Override + public String getHeaderValue() { + return "bytes */*"; + } + } + + interface HasRange { + + ByteRangeSpec range(); + + T map(UnaryOperator f); + } + + interface HasSize { + + long getSize(); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpStorageOptions.java index 3483dd9074..b525cf75b8 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpStorageOptions.java @@ -29,6 +29,7 @@ import com.google.cloud.TransportOptions; import com.google.cloud.http.HttpTransportOptions; import com.google.cloud.spi.ServiceRpcFactory; +import com.google.cloud.storage.Retrying.RetryingDependencies; import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.spi.StorageRpcFactory; import com.google.cloud.storage.spi.v1.HttpStorageRpc; @@ -51,6 +52,7 @@ public class HttpStorageOptions extends StorageOptions { private static final String DEFAULT_HOST = "https://storage.googleapis.com"; private final HttpRetryAlgorithmManager retryAlgorithmManager; + private final transient RetryDependenciesAdapter retryDepsAdapter; private HttpStorageOptions(Builder builder, StorageDefaults serviceDefaults) { super(builder, serviceDefaults); @@ -58,6 +60,7 @@ private HttpStorageOptions(Builder builder, StorageDefaults serviceDefaults) { new HttpRetryAlgorithmManager( MoreObjects.firstNonNull( builder.storageRetryStrategy, defaults().getStorageRetryStrategy())); + retryDepsAdapter = new RetryDependenciesAdapter(); } @Override @@ -102,6 +105,11 @@ public static HttpStorageDefaults defaults() { return HttpStorageDefaults.INSTANCE; } + @InternalApi + RetryingDependencies asRetryDependencies() { + return retryDepsAdapter; + } + public static class Builder extends StorageOptions.Builder { private StorageRetryStrategy storageRetryStrategy; @@ -321,4 +329,23 @@ public ServiceRpc create(StorageOptions options) { } } } + + /** + * We don't yet want to make HttpStorageOptions itself implement {@link RetryingDependencies} but + * we do need use it in a couple places, for those we create this adapter. + */ + private final class RetryDependenciesAdapter implements RetryingDependencies { + + private RetryDependenciesAdapter() {} + + @Override + public RetrySettings getRetrySettings() { + return HttpStorageOptions.this.getRetrySettings(); + } + + @Override + public ApiClock getClock() { + return HttpStorageOptions.this.getClock(); + } + } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java new file mode 100644 index 0000000000..f59355502b --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023 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; + +import com.google.api.gax.retrying.ResultRetryAlgorithm; +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.Conversions.Decoder; +import com.google.cloud.storage.Retrying.RetryingDependencies; +import com.google.cloud.storage.spi.v1.HttpStorageRpc; +import io.opencensus.trace.EndSpanOptions; +import java.util.concurrent.atomic.AtomicBoolean; +import org.checkerframework.checker.nullness.qual.Nullable; + +final class JsonResumableSession extends ResumableSession { + + static final String SPAN_NAME_WRITE = + String.format("Sent.%s.write", HttpStorageRpc.class.getName()); + static final EndSpanOptions END_SPAN_OPTIONS = + EndSpanOptions.builder().setSampleToLocalSpanStore(true).build(); + + private final HttpClientContext context; + private final RetryingDependencies deps; + private final ResultRetryAlgorithm alg; + private final JsonResumableWrite resumableWrite; + + JsonResumableSession( + HttpClientContext context, + RetryingDependencies deps, + ResultRetryAlgorithm alg, + JsonResumableWrite resumableWrite) { + this.context = context; + this.deps = deps; + this.alg = alg; + this.resumableWrite = resumableWrite; + } + + /** + * Not automatically retried. Usually called from within another retrying context. We don't yet + * have the concept of nested retry handling. + */ + @Override + ResumableOperationResult<@Nullable StorageObject> query() { + return new JsonResumableSessionQueryTask(context, resumableWrite.getUploadId()).call(); + } + + @Override + ResumableOperationResult<@Nullable StorageObject> put( + RewindableHttpContent content, HttpContentRange contentRange) { + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + context, resumableWrite.getUploadId(), content, contentRange); + AtomicBoolean dirty = new AtomicBoolean(false); + return Retrying.run( + deps, + alg, + () -> { + if (dirty.getAndSet(true)) { + ResumableOperationResult<@Nullable StorageObject> query = query(); + if (query.getObject() != null) { + return query; + } else { + task.rewindTo(query.getPersistedSize()); + } + } + return task.call(); + }, + Decoder.identity()); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionFailureScenario.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionFailureScenario.java new file mode 100644 index 0000000000..2b6e8d569c --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionFailureScenario.java @@ -0,0 +1,233 @@ +/* + * Copyright 2023 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; + +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.cloud.BaseServiceException; +import com.google.cloud.storage.StorageException.IOExceptionCallable; +import com.google.common.io.CharStreams; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Predicate; +import javax.annotation.ParametersAreNonnullByDefault; +import org.checkerframework.checker.nullness.qual.Nullable; + +@ParametersAreNonnullByDefault +enum JsonResumableSessionFailureScenario { + // TODO: send more bytes than are in the Content-Range header + SCENARIO_0(BaseServiceException.UNKNOWN_CODE, null, "Unknown Error"), + SCENARIO_0_1(BaseServiceException.UNKNOWN_CODE, null, "Response not application/json."), + SCENARIO_1( + BaseServiceException.UNKNOWN_CODE, + "invalid", + "Attempt to append to already finalized resumable session."), + SCENARIO_2( + BaseServiceException.UNKNOWN_CODE, + "invalid", + "Attempt to finalize resumable session with fewer bytes than the backend has received."), + SCENARIO_3( + BaseServiceException.UNKNOWN_CODE, + "dataLoss", + "Attempt to finalize resumable session with more bytes than the backend has received."), + SCENARIO_4(200, "ok", "Attempt to finalize an already finalized session with same object size"), + SCENARIO_4_1( + BaseServiceException.UNKNOWN_CODE, + "dataLoss", + "Finalized resumable session, but object size less than expected."), + SCENARIO_4_2( + BaseServiceException.UNKNOWN_CODE, + "dataLoss", + "Finalized resumable session, but object size greater than expected."), + SCENARIO_5( + BaseServiceException.UNKNOWN_CODE, + "dataLoss", + "Client side data loss detected. Attempt to append to a resumable session with an offset higher than the backend has"), + SCENARIO_7( + BaseServiceException.UNKNOWN_CODE, + "dataLoss", + "Client side data loss detected. Bytes acked is more than client sent."), + SCENARIO_9(503, "backendNotConnected", "Ack less than bytes sent"), + QUERY_SCENARIO_1(503, "", "Missing Range header in response"); + + private static final String PREFIX_I = "\t|< "; + private static final String PREFIX_O = "\t|> "; + private static final String PREFIX_X = "\t| "; + + private static final Predicate includedHeaders = + matches("Content-Length") + .or(matches("Content-Encoding")) + .or(matches("Content-Range")) + .or(matches("Content-Type")) + .or(matches("Range")) + .or(startsWith("X-Goog-Stored-")) + .or(matches("X-GUploader-UploadID")); + + private static final Predicate> includeHeader = + e -> includedHeaders.test(e.getKey()); + + private final int code; + @Nullable private final String reason; + private final String message; + + JsonResumableSessionFailureScenario(int code, @Nullable String reason, String message) { + this.code = code; + this.reason = reason; + this.message = message; + } + + StorageException toStorageException(String uploadId, HttpResponse resp) { + return toStorageException( + uploadId, resp, null, () -> CharStreams.toString(new InputStreamReader(resp.getContent()))); + } + + StorageException toStorageException( + String uploadId, @Nullable HttpResponse resp, @Nullable Throwable cause) { + if (resp != null) { + // an exception caused this, do not try to read the content from the response. + return toStorageException(uploadId, resp, cause, () -> null); + } else { + return new StorageException(code, message, reason, cause); + } + } + + StorageException toStorageException( + String uploadId, + HttpResponse resp, + @Nullable Throwable cause, + IOExceptionCallable<@Nullable String> contentCallable) { + return toStorageException(code, message, reason, uploadId, resp, cause, contentCallable); + } + + static StorageException toStorageException( + HttpResponse response, HttpResponseException cause, String uploadId) { + String statusMessage = cause.getStatusMessage(); + StorageException se = + JsonResumableSessionFailureScenario.toStorageException( + cause.getStatusCode(), + String.format( + "%d %s", cause.getStatusCode(), statusMessage == null ? "" : statusMessage), + "", + uploadId, + response, + cause, + () -> null); + return se; + } + + static StorageException toStorageException( + int overrideCode, + String message, + @Nullable String reason, + String uploadId, + HttpResponse resp, + @Nullable Throwable cause, + IOExceptionCallable<@Nullable String> contentCallable) { + Throwable suppress = null; + StringBuilder sb = new StringBuilder(); + sb.append(message); + // add request context + sb.append("\n").append(PREFIX_O).append("PUT ").append(uploadId); + recordHeaderTo(resp.getRequest().getHeaders(), PREFIX_O, sb); + + sb.append("\n").append(PREFIX_X); + // add response context + { + int code = resp.getStatusCode(); + sb.append("\n").append(PREFIX_I).append("HTTP/1.1 ").append(code); + if (resp.getStatusMessage() != null) { + sb.append(" ").append(resp.getStatusMessage()); + } + + recordHeaderTo(resp.getHeaders(), PREFIX_I, sb); + // try to include any body that we can handle + if (isOk(code) || code == 503 || code == 400) { + try { + String content = contentCallable.call(); + if (content != null) { + sb.append("\n").append(PREFIX_I); + if (content.contains("\n") || content.contains("\r\n")) { + sb.append("\n").append(PREFIX_I).append(content.replaceAll("\r?\n", "\n" + PREFIX_I)); + } else { + sb.append("\n").append(PREFIX_I).append(content); + } + } + } catch (IOException e) { + // com.google.api.client.http.HttpResponseException.Builder.Builder + // prints an exception which might occur while attempting to resolve the content + // this can lose the context about the request it was for, instead we register it + // as a suppressed exception + suppress = new StorageException(0, "Error reading response content for diagnostics.", e); + } + } + + sb.append("\n").append(PREFIX_X); + } + StorageException storageException = + new StorageException(overrideCode, sb.toString(), reason, cause); + if (suppress != null) { + storageException.addSuppressed(suppress); + } + return storageException; + } + + static boolean isOk(int code) { + return code == 200 || code == 201; + } + + static boolean isContinue(int code) { + return code == 308; + } + + // The header names from HttpHeaders are lower cased, define some utility methods to create + // predicates where we can specify values ignoring case + private static Predicate matches(String expected) { + String lower = expected.toLowerCase(Locale.US); + return lower::equals; + } + + private static Predicate startsWith(String prefix) { + String lower = prefix.toLowerCase(Locale.US); + return s -> s.startsWith(lower); + } + + private static void recordHeaderTo(HttpHeaders h, String prefix, StringBuilder sb) { + h.entrySet().stream() + .filter(includeHeader) + .forEach( + e -> { + String key = e.getKey(); + String value = headerValueToString(e.getValue()); + sb.append("\n").append(prefix).append(key).append(": ").append(value); + }); + } + + private static String headerValueToString(Object o) { + if (o instanceof List) { + List l = (List) o; + if (l.size() == 1) { + return l.get(0).toString(); + } + } + + return o.toString(); + } +} 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 new file mode 100644 index 0000000000..73b7d14a46 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionPutTask.java @@ -0,0 +1,218 @@ +/* + * Copyright 2023 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; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.StorageException.IOExceptionCallable; +import com.google.common.annotations.VisibleForTesting; +import io.opencensus.common.Scope; +import io.opencensus.trace.Span; +import io.opencensus.trace.Status; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Locale; +import java.util.concurrent.Callable; +import org.checkerframework.checker.nullness.qual.Nullable; + +final class JsonResumableSessionPutTask + implements Callable> { + + private final HttpClientContext context; + private final String uploadId; + private final RewindableHttpContent content; + private final HttpContentRange originalContentRange; + + private HttpContentRange contentRange; + + @VisibleForTesting + JsonResumableSessionPutTask( + HttpClientContext httpClientContext, + String uploadId, + RewindableHttpContent content, + HttpContentRange originalContentRange) { + this.context = httpClientContext; + this.uploadId = uploadId; + this.content = content; + this.originalContentRange = originalContentRange; + this.contentRange = originalContentRange; + } + + public void rewindTo(long offset) { + content.rewindTo(offset); + if (contentRange instanceof HttpContentRange.HasRange) { + HttpContentRange.HasRange range = (HttpContentRange.HasRange) contentRange; + contentRange = range.map(s -> s.withNewBeginOffset(offset)); + } + } + + public ResumableOperationResult<@Nullable StorageObject> call() throws IOException { + Span span = context.startSpan(JsonResumableSession.SPAN_NAME_WRITE); + Scope scope = context.getTracer().withSpan(span); + + boolean success = false; + boolean finalizing = originalContentRange.isFinalizing(); + + HttpRequest req = + context + .getRequestFactory() + .buildPutRequest(new GenericUrl(uploadId), content) + .setParser(context.getObjectParser()); + req.setThrowExceptionOnExecuteError(false); + req.getHeaders().setContentRange(contentRange.getHeaderValue()); + + HttpResponse response = null; + try { + response = req.execute(); + + int code = response.getStatusCode(); + + if (!finalizing && JsonResumableSessionFailureScenario.isContinue(code)) { + long effectiveEnd = ((HttpContentRange.HasRange) contentRange).range().endOffset(); + @Nullable String range = response.getHeaders().getRange(); + ByteRangeSpec ackRange = ByteRangeSpec.parse(range); + if (ackRange.endOffset() == effectiveEnd) { + success = true; + return ResumableOperationResult.incremental(ackRange.endOffset()); + } else if (ackRange.endOffset() < effectiveEnd) { + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_9.toStorageException(uploadId, response); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } else { + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_7.toStorageException(uploadId, response); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } + } else if (finalizing && JsonResumableSessionFailureScenario.isOk(code)) { + @Nullable StorageObject storageObject; + @Nullable BigInteger actualSize; + + Long contentLength = response.getHeaders().getContentLength(); + String contentType = response.getHeaders().getContentType(); + String storedContentLength = + HttpClientContext.firstHeaderValue( + response.getHeaders(), "x-goog-stored-content-length"); + boolean isJson = contentType != null && contentType.startsWith("application/json"); + if (isJson) { + storageObject = response.parseAs(StorageObject.class); + actualSize = storageObject != null ? storageObject.getSize() : null; + } else if ((contentLength == null || contentLength == 0) && storedContentLength != null) { + // when a signed url is used, the finalize response is empty + response.ignore(); + actualSize = new BigInteger(storedContentLength, 10); + success = true; + storageObject = null; + } else { + response.ignore(); + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_0_1.toStorageException( + uploadId, response, null, () -> null); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } + BigInteger expectedSize = + BigInteger.valueOf(((HttpContentRange.HasSize) contentRange).getSize()); + 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 = + JsonResumableSessionFailureScenario.SCENARIO_4_1.toStorageException( + uploadId, response, null, toString(storageObject)); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } else { + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_4_2.toStorageException( + uploadId, response, null, toString(storageObject)); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } + } else if (!finalizing && JsonResumableSessionFailureScenario.isOk(code)) { + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_1.toStorageException(uploadId, response); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } else if (finalizing && JsonResumableSessionFailureScenario.isContinue(code)) { + // in order to finalize the content range must have a size, cast down to read it + HttpContentRange.HasSize size = (HttpContentRange.HasSize) contentRange; + + ByteRangeSpec range = ByteRangeSpec.parse(response.getHeaders().getRange()); + if (range.endOffsetInclusive() < size.getSize()) { + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_3.toStorageException(uploadId, response); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } else { + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_2.toStorageException(uploadId, response); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } + } else { + HttpResponseException cause = new HttpResponseException(response); + String contentType = response.getHeaders().getContentType(); + // If the content-range header value has run ahead of the backend, it will respond with + // a 503 with plain text content + // Attempt to detect this very loosely as to minimize impact of modified error message + // This is accurate circa 2023-06 + if ((!JsonResumableSessionFailureScenario.isOk(code) + && !JsonResumableSessionFailureScenario.isContinue(code)) + && contentType != null + && contentType.startsWith("text/plain")) { + String errorMessage = cause.getContent().toLowerCase(Locale.US); + if (errorMessage.contains("content-range")) { + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_5.toStorageException( + uploadId, response, cause, cause::getContent); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } + } + StorageException se = + JsonResumableSessionFailureScenario.toStorageException(response, cause, uploadId); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } + } catch (StorageException e) { + span.setStatus(Status.UNKNOWN.withDescription(e.getMessage())); + throw e; + } catch (Exception e) { + StorageException se = + JsonResumableSessionFailureScenario.SCENARIO_0.toStorageException(uploadId, response, e); + span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); + throw se; + } finally { + if (success && !finalizing && response != null) { + response.ignore(); + } + scope.close(); + span.end(JsonResumableSession.END_SPAN_OPTIONS); + } + } + + static IOExceptionCallable<@Nullable String> toString(@Nullable Object o) { + return () -> o != null ? o.toString() : null; + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java new file mode 100644 index 0000000000..b37d2396d3 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSessionQueryTask.java @@ -0,0 +1,136 @@ +/* + * Copyright 2023 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; + +import static com.google.cloud.storage.HttpClientContext.firstHeaderValue; + +import com.google.api.client.http.EmptyContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpResponseException; +import com.google.api.services.storage.model.StorageObject; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Locale; +import java.util.concurrent.Callable; +import org.checkerframework.checker.nullness.qual.Nullable; + +final class JsonResumableSessionQueryTask + implements Callable> { + + private final HttpClientContext context; + private final String uploadId; + + JsonResumableSessionQueryTask(HttpClientContext context, String uploadId) { + this.context = context; + this.uploadId = uploadId; + } + + public ResumableOperationResult<@Nullable StorageObject> call() { + HttpResponse response = null; + try { + HttpRequest req = + context + .getRequestFactory() + .buildPutRequest(new GenericUrl(uploadId), new EmptyContent()) + .setParser(context.getObjectParser()); + req.setThrowExceptionOnExecuteError(false); + req.getHeaders().setContentRange(HttpContentRange.query().getHeaderValue()); + + response = req.execute(); + + int code = response.getStatusCode(); + if (JsonResumableSessionFailureScenario.isOk(code)) { + @Nullable StorageObject storageObject; + @Nullable BigInteger actualSize; + + Long contentLength = response.getHeaders().getContentLength(); + String contentType = response.getHeaders().getContentType(); + String storedContentLength = + firstHeaderValue(response.getHeaders(), "x-goog-stored-content-length"); + boolean isJson = contentType != null && contentType.startsWith("application/json"); + if (isJson) { + storageObject = response.parseAs(StorageObject.class); + actualSize = storageObject != null ? storageObject.getSize() : null; + } else if ((contentLength == null || contentLength == 0) && storedContentLength != null) { + // when a signed url is used, the finalize response is empty + response.ignore(); + actualSize = new BigInteger(storedContentLength, 10); + storageObject = null; + } else { + response.ignore(); + throw JsonResumableSessionFailureScenario.SCENARIO_0_1.toStorageException( + uploadId, response, null, () -> null); + } + if (actualSize != null) { + if (storageObject != null) { + return ResumableOperationResult.complete(storageObject, actualSize.longValue()); + } else { + return ResumableOperationResult.incremental(actualSize.longValue()); + } + } else { + throw JsonResumableSessionFailureScenario.SCENARIO_0.toStorageException( + uploadId, + response, + null, + () -> storageObject != null ? storageObject.toString() : null); + } + } else if (JsonResumableSessionFailureScenario.isContinue(code)) { + String range1 = response.getHeaders().getRange(); + if (range1 != null) { + ByteRangeSpec range = ByteRangeSpec.parse(range1); + long endOffset = range.endOffset(); + return ResumableOperationResult.incremental(endOffset); + } else { + throw JsonResumableSessionFailureScenario.QUERY_SCENARIO_1.toStorageException( + uploadId, response); + } + } else { + HttpResponseException cause = new HttpResponseException(response); + String contentType = response.getHeaders().getContentType(); + // If the content-range header value has run ahead of the backend, it will respond with + // a 503 with plain text content + // Attempt to detect this very loosely as to minimize impact of modified error message + // This is accurate circa 2023-06 + if ((!JsonResumableSessionFailureScenario.isOk(code) + && !JsonResumableSessionFailureScenario.isContinue(code)) + && contentType != null + && contentType.startsWith("text/plain")) { + String errorMessage = cause.getContent().toLowerCase(Locale.US); + if (errorMessage.contains("content-range")) { + throw JsonResumableSessionFailureScenario.SCENARIO_5.toStorageException( + uploadId, response, cause, cause::getContent); + } + } + throw JsonResumableSessionFailureScenario.toStorageException(response, cause, uploadId); + } + } catch (StorageException se) { + throw se; + } catch (Exception e) { + throw JsonResumableSessionFailureScenario.SCENARIO_0.toStorageException( + uploadId, response, e); + } finally { + if (response != null) { + try { + response.ignore(); + } catch (IOException ignore) { + } + } + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java new file mode 100644 index 0000000000..c5e17aad52 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java @@ -0,0 +1,88 @@ +/* + * Copyright 2023 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; + +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.spi.v1.StorageRpc; +import com.google.common.base.MoreObjects; +import java.util.Map; +import java.util.Objects; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; + +final class JsonResumableWrite { + @MonotonicNonNull private final StorageObject object; + @MonotonicNonNull private final Map options; + + @MonotonicNonNull private final String signedUrl; + + @NonNull private final String uploadId; + + private JsonResumableWrite( + StorageObject object, + Map options, + String signedUrl, + @NonNull String uploadId) { + this.object = object; + this.options = options; + this.signedUrl = signedUrl; + this.uploadId = uploadId; + } + + public @NonNull String getUploadId() { + return uploadId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof JsonResumableWrite)) { + return false; + } + JsonResumableWrite that = (JsonResumableWrite) o; + return Objects.equals(object, that.object) + && Objects.equals(options, that.options) + && Objects.equals(signedUrl, that.signedUrl) + && uploadId.equals(that.uploadId); + } + + @Override + public int hashCode() { + return Objects.hash(object, options, signedUrl, uploadId); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("object", object) + .add("options", options) + .add("signedUrl", signedUrl) + .add("uploadId", uploadId) + .toString(); + } + + static JsonResumableWrite of( + StorageObject req, Map options, String uploadId) { + return new JsonResumableWrite(req, options, null, uploadId); + } + + static JsonResumableWrite of(String signedUrl, String uploadId) { + return new JsonResumableWrite(null, null, signedUrl, uploadId); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableOperationResult.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableOperationResult.java new file mode 100644 index 0000000000..88b5b7565e --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableOperationResult.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 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; + +import com.google.common.base.MoreObjects; +import org.checkerframework.checker.nullness.qual.Nullable; + +abstract class ResumableOperationResult<@Nullable T> { + + private ResumableOperationResult() {} + + abstract @Nullable T getObject(); + + abstract long getPersistedSize(); + + static ResumableOperationResult complete(T t, long persistedSize) { + return new CompletedResult<>(t, persistedSize); + } + + static <@Nullable T> ResumableOperationResult incremental(long persistedSize) { + return new IncrementalResult<>(persistedSize); + } + + private static final class CompletedResult extends ResumableOperationResult { + + private final long persistedSize; + private final T entity; + + private CompletedResult(T entity, long persistedSize) { + this.entity = entity; + this.persistedSize = persistedSize; + } + + @Override + public @Nullable T getObject() { + return entity; + } + + @Override + public long getPersistedSize() { + return persistedSize; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("persistedSize", persistedSize) + .add("entity", entity) + .toString(); + } + } + + private static final class IncrementalResult<@Nullable T> extends ResumableOperationResult { + + private final long persistedSize; + + private IncrementalResult(long persistedSize) { + this.persistedSize = persistedSize; + } + + @Override + public @Nullable T getObject() { + return null; + } + + @Override + public long getPersistedSize() { + return persistedSize; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("persistedSize", persistedSize).toString(); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSession.java new file mode 100644 index 0000000000..7b608c68a4 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSession.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 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; + +import com.google.api.gax.retrying.ResultRetryAlgorithm; +import com.google.cloud.storage.Retrying.RetryingDependencies; +import org.checkerframework.checker.nullness.qual.Nullable; + +abstract class ResumableSession { + + ResumableSession() {} + + abstract ResumableOperationResult<@Nullable T> put( + RewindableHttpContent content, HttpContentRange contentRange); + + abstract ResumableOperationResult<@Nullable T> query(); + + static JsonResumableSession json( + HttpClientContext context, + RetryingDependencies deps, + ResultRetryAlgorithm alg, + JsonResumableWrite resumableWrite) { + return new JsonResumableSession(context, deps, alg, resumableWrite); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java new file mode 100644 index 0000000000..fd171e0544 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java @@ -0,0 +1,106 @@ +/* + * Copyright 2023 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; + +import com.google.api.client.http.AbstractHttpContent; +import com.google.api.client.http.HttpMediaType; +import com.google.common.base.Preconditions; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +abstract class RewindableHttpContent extends AbstractHttpContent { + + private RewindableHttpContent() { + super((HttpMediaType) null); + } + + @Override + public abstract long getLength(); + + abstract void rewindTo(long offset); + + @Override + public final boolean retrySupported() { + return false; + } + + static RewindableHttpContent empty() { + return EmptyRewindableContent.INSTANCE; + } + + static RewindableHttpContent of(Path path) throws IOException { + return new PathRewindableHttpContent(path); + } + + private static final class EmptyRewindableContent extends RewindableHttpContent { + private static final EmptyRewindableContent INSTANCE = new EmptyRewindableContent(); + + @Override + public long getLength() { + return 0L; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + out.flush(); + } + + @Override + protected void rewindTo(long offset) {} + } + + private static final class PathRewindableHttpContent extends RewindableHttpContent { + + private final Path path; + private final long size; + + private long readOffset; + + private PathRewindableHttpContent(Path path) throws IOException { + this.path = path; + this.size = Files.size(path); + this.readOffset = 0; + } + + @Override + public long getLength() { + return size - readOffset; + } + + @Override + protected void rewindTo(long offset) { + Preconditions.checkArgument( + offset < size, "provided offset must be less than size (%d < %d)", offset, size); + this.readOffset = offset; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + try (SeekableByteChannel in = Files.newByteChannel(path, StandardOpenOption.READ)) { + in.position(readOffset); + ByteStreams.copy(in, Channels.newChannel(out)); + out.flush(); + } + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index 8c0768b9de..a97a325c5c 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -86,6 +86,8 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.Nullable; final class StorageImpl extends BaseService implements Storage { @@ -225,9 +227,40 @@ public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOp if (Files.isDirectory(path)) { throw new StorageException(0, path + " is a directory"); } - try (InputStream input = Files.newInputStream(path)) { - return createFrom(blobInfo, input, bufferSize, options); + Opts opts = Opts.unwrap(options).resolveFrom(blobInfo); + final Map optionsMap = opts.getRpcOptions(); + BlobInfo.Builder builder = blobInfo.toBuilder().setMd5(null).setCrc32c(null); + BlobInfo updated = opts.blobInfoMapper().apply(builder).build(); + StorageObject encode = codecs.blobInfo().encode(updated); + + Supplier uploadIdSupplier = + ResumableMedia.startUploadForBlobInfo( + getOptions(), + updated, + optionsMap, + retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get()); + + JsonResumableSession session = + ResumableSession.json( + HttpClientContext.from(storageRpc), + getOptions().asRetryDependencies(), + retryAlgorithmManager.idempotent(), + jsonResumableWrite); + long size = Files.size(path); + HttpContentRange contentRange = + HttpContentRange.of(ByteRangeSpec.relativeLength(0L, size), size); + ResumableOperationResult put = + session.put(RewindableHttpContent.of(path), contentRange); + // all exception translation is taken care of down in the JsonResumableSession + StorageObject object = put.getObject(); + if (object == null) { + // if by some odd chance the put didn't get the StorageObject, query for it + ResumableOperationResult<@Nullable StorageObject> query = session.query(); + object = query.getObject(); } + return codecs.blobInfo().decode(object).asBlob(this); } @Override diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java new file mode 100644 index 0000000000..be739ab188 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 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; + +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderValues.CLOSE; + +import io.grpc.netty.shaded.io.netty.bootstrap.ServerBootstrap; +import io.grpc.netty.shaded.io.netty.buffer.ByteBuf; +import io.grpc.netty.shaded.io.netty.channel.Channel; +import io.grpc.netty.shaded.io.netty.channel.ChannelFuture; +import io.grpc.netty.shaded.io.netty.channel.ChannelFutureListener; +import io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext; +import io.grpc.netty.shaded.io.netty.channel.ChannelInitializer; +import io.grpc.netty.shaded.io.netty.channel.ChannelOption; +import io.grpc.netty.shaded.io.netty.channel.ChannelPipeline; +import io.grpc.netty.shaded.io.netty.channel.EventLoopGroup; +import io.grpc.netty.shaded.io.netty.channel.SimpleChannelInboundHandler; +import io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoopGroup; +import io.grpc.netty.shaded.io.netty.channel.socket.SocketChannel; +import io.grpc.netty.shaded.io.netty.channel.socket.nio.NioServerSocketChannel; +import io.grpc.netty.shaded.io.netty.handler.codec.http.FullHttpResponse; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaders; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpRequest; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpServerCodec; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpServerExpectContinueHandler; +import io.grpc.netty.shaded.io.netty.handler.logging.LogLevel; +import io.grpc.netty.shaded.io.netty.handler.logging.LoggingHandler; +import java.net.InetSocketAddress; +import java.net.URI; + +final class FakeHttpServer implements AutoCloseable { + + private final URI endpoint; + private final Channel channel; + private final Runnable shutdown; + + private FakeHttpServer(URI endpoint, Channel channel, Runnable shutdown) { + this.endpoint = endpoint; + this.channel = channel; + this.shutdown = shutdown; + } + + public URI getEndpoint() { + return endpoint; + } + + @Override + public void close() throws Exception { + shutdown.run(); + channel.closeFuture().syncUninterruptibly(); + } + + static FakeHttpServer of(HttpRequestHandler server) { + // based on + // https://github.com/netty/netty/blob/59aa6e635b9996cf21cd946e64353270679adc73/example/src/main/java/io/netty/example/http/helloworld/HttpHelloWorldServer.java + InetSocketAddress address = new InetSocketAddress("localhost", 0); + // Configure the server. + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + ServerBootstrap b = new ServerBootstrap(); + b.option(ChannelOption.SO_BACKLOG, 1024); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.DEBUG)) + .childHandler( + new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline p = ch.pipeline(); + p.addLast(new HttpServerCodec()); + p.addLast(new HttpServerExpectContinueHandler()); + p.addLast(new Handler(server)); + } + }); + + Channel channel = b.bind(address).syncUninterruptibly().channel(); + + InetSocketAddress socketAddress = (InetSocketAddress) channel.localAddress(); + return new FakeHttpServer( + URI.create("http://localhost:" + socketAddress.getPort()), + channel, + () -> { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + }); + } + + interface HttpRequestHandler { + FullHttpResponse apply(HttpRequest req) throws Exception; + } + + /** + * Based on + * https://github.com/netty/netty/blob/59aa6e635b9996cf21cd946e64353270679adc73/example/src/main/java/io/netty/example/http/helloworld/HttpHelloWorldServerHandler.java + */ + private static final class Handler extends SimpleChannelInboundHandler { + + private final HttpRequestHandler server; + + private Handler(HttpRequestHandler server) { + this.server = server; + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, HttpRequest req) throws Exception { + FullHttpResponse resp = server.apply(req); + HttpHeaders headers = resp.headers(); + if (!headers.contains(CONTENT_LENGTH)) { + ByteBuf content = resp.content(); + headers.setInt(CONTENT_LENGTH, content.readableBytes()); + } + headers.set(CONNECTION, CLOSE); + ChannelFuture future = ctx.writeAndFlush(resp); + future.addListener(ChannelFutureListener.CLOSE); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + ctx.close(); + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java new file mode 100644 index 0000000000..18704dc288 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java @@ -0,0 +1,814 @@ +/* + * Copyright 2023 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; + +import static com.google.cloud.storage.ByteSizeConstants._128KiBL; +import static com.google.cloud.storage.ByteSizeConstants._256KiBL; +import static com.google.cloud.storage.ByteSizeConstants._512KiBL; +import static com.google.cloud.storage.ByteSizeConstants._768KiBL; +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONTENT_RANGE; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus.OK; +import static org.junit.Assert.assertThrows; + +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.gax.retrying.ResultRetryAlgorithm; +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.FakeHttpServer.HttpRequestHandler; +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.ParallelFriendly; +import com.google.cloud.storage.it.runner.annotations.SingleBackend; +import com.google.common.collect.ImmutableMap; +import io.grpc.netty.shaded.io.netty.buffer.ByteBuf; +import io.grpc.netty.shaded.io.netty.buffer.Unpooled; +import io.grpc.netty.shaded.io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.grpc.netty.shaded.io.netty.handler.codec.http.FullHttpResponse; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus; +import java.math.BigInteger; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import org.checkerframework.checker.nullness.qual.Nullable; +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) +@SingleBackend(Backend.PROD) +@ParallelFriendly +public final class ITJsonResumableSessionPutTaskTest { + private static final GsonFactory gson = GsonFactory.getDefaultInstance(); + private static final NetHttpTransport transport = new NetHttpTransport.Builder().build(); + private static final HttpResponseStatus RESUME_INCOMPLETE = + HttpResponseStatus.valueOf(308, "Resume Incomplete"); + private static final HttpResponseStatus APPEND_GREATER_THAN_CURRENT_SIZE = + HttpResponseStatus.valueOf(503, ""); + private HttpClientContext httpClientContext; + + @Rule public final TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + httpClientContext = + HttpClientContext.of(transport.createRequestFactory(), new JsonObjectParser(gson)); + } + + @Test + public void emptyObjectHappyPath() throws Exception { + + HttpRequestHandler handler = + req -> { + StorageObject so = new StorageObject(); + so.setName("object-name").setSize(BigInteger.ZERO); + ByteBuf buf = Unpooled.wrappedBuffer(gson.toByteArray(so)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/json; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.empty(), + HttpContentRange.of(ByteRangeSpec.explicitClosed(0L, 0L), 0)); + + ResumableOperationResult<@Nullable StorageObject> operationResult = task.call(); + StorageObject object = operationResult.getObject(); + assertThat(object).isNotNull(); + assertThat(operationResult.getPersistedSize()).isEqualTo(0L); + } + } + + /** + * + * + *

S.9

+ * + * Partial successful append to session + * + *

The client has sent N bytes, the server confirmed N bytes as committed. The client sends K + * bytes starting at offset N. The server responds with only N + L with 0 <= L < K bytes as + * committed. + */ + @Test + public void scenario9() throws Exception { + + HttpRequestHandler handler = + req -> { + String contentRangeString = req.headers().get(CONTENT_RANGE); + HttpContentRange parse = HttpContentRange.parse(contentRangeString); + long endInclusive = ((HttpContentRange.HasRange) parse).range().endOffsetInclusive(); + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + ByteRangeSpec range = ByteRangeSpec.explicitClosed(0L, endInclusive - 1); + resp.headers().set(HttpHeaderNames.RANGE, range.getHttpRangeHeader()); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.empty(), + HttpContentRange.of(ByteRangeSpec.explicitClosed(0L, 10L))); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(503); + assertThat(confirmedBytes.get()).isEqualTo(-1L); + } + } + + /** + * + * + *

S.7

+ * + * GCS Acknowledges more bytes than were sent in the PUT + * + *

The client believes the server offset is N, it sends K bytes and the server responds that N + * + 2K bytes are now committed. + * + *

The client has detected data loss and should raise an error and prevent sending of more + * bytes. + */ + @Test + public void scenario7() throws Exception { + + HttpRequestHandler handler = + req -> { + String contentRangeString = req.headers().get(CONTENT_RANGE); + HttpContentRange parse = HttpContentRange.parse(contentRangeString); + long endInclusive = ((HttpContentRange.HasRange) parse).range().endOffsetInclusive(); + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + ByteRangeSpec range = ByteRangeSpec.explicitClosed(0L, endInclusive + 1); + resp.headers().set(HttpHeaderNames.RANGE, range.getHttpRangeHeader()); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.empty(), + HttpContentRange.of(ByteRangeSpec.explicitClosed(0L, 10L))); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo("dataLoss"); + assertThat(confirmedBytes.get()).isEqualTo(-1L); + } + } + + /** + * + * + *

S.1

+ * + * Attempting to append to a session which has already been finalized should raise an error + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
server state
+   * resource = { name= obj, persisted_size = 524288 }
+   *     
client state
+   * write_offset = 0, data = [0:262144]
+   *     
request
+   * PUT $UPLOAD_ID
+   * Content-Range: bytes 0-262143/*
+   *     
response
+   * 200 OK
+   * Content-Type: application/json; charset=utf-8
+   *
+   * {"name": "obj", "size": 524288}
+   *     
+ */ + @Test + public void scenario1() throws Exception { + HttpRequestHandler handler = + req -> { + StorageObject so = new StorageObject(); + URI uri = URI.create(req.uri()); + so.setName("object") + .setBucket("bucket") + .setGeneration(1L) + .setMetageneration(1L) + .setSize(BigInteger.valueOf(_512KiBL)) + .setMetadata(ImmutableMap.of("upload_id", uri.toString())); + + ByteBuf buf = Unpooled.wrappedBuffer(gson.toByteArray(so)); + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/json; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler); + TmpFile tmpFile = + DataGenerator.base64Characters().tempFile(temp.newFolder().toPath(), _256KiBL)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.of(tmpFile.getPath()), + HttpContentRange.of(ByteRangeSpec.explicit(0L, _256KiBL))); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo("invalid"); + assertThat(confirmedBytes.get()).isEqualTo(-1L); + } + } + + /** + * + * + *

S.2

+ * + * Attempting to finalize a session with fewer bytes than GCS acknowledges. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
server state
+   * persisted_size = 524288
+   *     
client state
+   * write_offset = 262144, finish = true
+   *     
request
+   * PUT $UPLOAD_ID
+   * Content-Range: bytes */262144
+   *     
response
+   * 308 Resume Incomplete
+   * Range: bytes=0-524287
+   *     
+ */ + @Test + public void scenario2() throws Exception { + + HttpRequestHandler handler = + req -> { + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + ByteRangeSpec range = ByteRangeSpec.explicit(0L, _512KiBL); + resp.headers().set(HttpHeaderNames.RANGE, range.getHttpRangeHeader()); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.empty(), + HttpContentRange.of(_256KiBL)); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo("invalid"); + assertThat(confirmedBytes.get()).isEqualTo(-1L); + } + } + + /** + * + * + *

S.3

+ * + * Attempting to finalize a session with more bytes than GCS acknowledges. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
server state
+   * persisted_size = 262144
+   *     
client state
+   * write_offset = 524288, finish = true
+   *     
request
+   * PUT $UPLOAD_ID
+   * Content-Range: bytes */524288
+   *     
response
+   * 308 Resume Incomplete
+   * Range: bytes=0-262143
+   *     
+ */ + @Test + public void scenario3() throws Exception { + + HttpRequestHandler handler = + req -> { + FullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + ByteRangeSpec range = ByteRangeSpec.explicit(0L, _256KiBL); + resp.headers().set(HttpHeaderNames.RANGE, range.getHttpRangeHeader()); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.empty(), + HttpContentRange.of(_512KiBL)); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo("dataLoss"); + assertThat(confirmedBytes.get()).isEqualTo(-1L); + } + } + + /** + * + * + *

S.4

+ * + * Attempting to finalize an already finalized session + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
server state
+   * resource = {name = obj1, size = 262114}
+   *     
client state
+   * write_offset = 262114, finish = true
+   *     
request
+   * PUT $UPLOAD_ID
+   * Content-Range: bytes */262114
+   *     
response
+   * 200 Ok
+   * Content-Type: application/json; charset=utf-8
+   *
+   * {"name": "obj", "size": 262114}
+   *     
+ */ + @Test + public void scenario4() throws Exception { + + HttpRequestHandler handler = + req -> { + StorageObject so = new StorageObject(); + URI uri = URI.create(req.uri()); + so.setName("object") + .setBucket("bucket") + .setGeneration(1L) + .setMetageneration(1L) + .setSize(BigInteger.valueOf(_256KiBL)) + .setMetadata(ImmutableMap.of("upload_id", uri.toString())); + + ByteBuf buf = Unpooled.wrappedBuffer(gson.toByteArray(so)); + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/json; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.empty(), + HttpContentRange.of(_256KiBL)); + + ResumableOperationResult<@Nullable StorageObject> operationResult = task.call(); + StorageObject call = operationResult.getObject(); + assertThat(call).isNotNull(); + assertThat(call.getMetadata()) + .containsEntry("upload_id", uploadUrl.substring(endpoint.toString().length())); + assertThat(operationResult.getPersistedSize()).isEqualTo(_256KiBL); + } + } + + /** + * + * + *

S.4.1

+ * + * Attempting to finalize an already finalized session (ack < expected) + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
server state
+   * resource = {name = obj1, size = 262114}
+   *     
client state
+   * write_offset = 524288, finish = true
+   *     
request
+   * PUT $UPLOAD_ID
+   * Content-Range: bytes */524288
+   *     
response
+   * 200 Ok
+   * Content-Type: application/json; charset=utf-8
+   *
+   * {"name": "obj", "size": 262114}
+   *     
+ */ + @Test + public void scenario4_1() throws Exception { + + HttpRequestHandler handler = + req -> { + StorageObject so = new StorageObject(); + URI uri = URI.create(req.uri()); + so.setName("object") + .setBucket("bucket") + .setGeneration(1L) + .setMetageneration(1L) + .setSize(BigInteger.valueOf(_256KiBL)) + .setMetadata(ImmutableMap.of("upload_id", uri.toString())); + + ByteBuf buf = Unpooled.wrappedBuffer(gson.toByteArray(so)); + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/json; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.empty(), + HttpContentRange.of(_512KiBL)); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo("dataLoss"); + assertThat(confirmedBytes.get()).isEqualTo(-1); + } + } + + /** + * + * + *

S.4.2

+ * + * Attempting to finalize an already finalized session (ack > expected) + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
server state
+   * resource = {name = obj1, size = 262114}
+   *     
client state
+   * write_offset = 524288, finish = true
+   *     
request
+   * PUT $UPLOAD_ID
+   * Content-Range: bytes */131072
+   *     
response
+   * 200 Ok
+   * Content-Type: application/json; charset=utf-8
+   *
+   * {"name": "obj", "size": 262114}
+   *     
+ */ + @Test + public void scenario4_2() throws Exception { + + HttpRequestHandler handler = + req -> { + StorageObject so = new StorageObject(); + URI uri = URI.create(req.uri()); + so.setName("object") + .setBucket("bucket") + .setGeneration(1L) + .setMetageneration(1L) + .setSize(BigInteger.valueOf(_256KiBL)) + .setMetadata(ImmutableMap.of("upload_id", uri.toString())); + + ByteBuf buf = Unpooled.wrappedBuffer(gson.toByteArray(so)); + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/json; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.empty(), + HttpContentRange.of(_128KiBL)); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo("dataLoss"); + assertThat(confirmedBytes.get()).isEqualTo(-1); + } + } + + /** + * + * + *

S.5

+ * + * Attempt to append to a resumable session with an offset higher than GCS expects + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
server state
+   * persisted_size = 262144
+   *     
client state
+   * write_offset = 524288, data = [524288:786432]
+   *     
request
+   * PUT $UPLOAD_ID
+   * Content-Range: bytes 524288-786431/*
+   *     
response
+   * 503
+   * Content-Type: text/plain; charset=utf-8
+   *
+   * Invalid request. According to the Content-Range header, the upload offset is 524288 byte(s), which exceeds already uploaded size of 262144 byte(s).
+   *     
+ */ + @Test + public void scenario5() throws Exception { + + HttpRequestHandler handler = + req -> { + // error message from GCS circa 2023-02 + ByteBuf buf = + Unpooled.wrappedBuffer( + "Invalid request. According to the Content-Range header, the upload offset is 524288 byte(s), which exceeds already uploaded size of 262144 byte(s)." + .getBytes(StandardCharsets.UTF_8)); + FullHttpResponse resp = + new DefaultFullHttpResponse( + req.protocolVersion(), APPEND_GREATER_THAN_CURRENT_SIZE, buf); + resp.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler); + TmpFile tmpFile = + DataGenerator.base64Characters().tempFile(temp.newFolder().toPath(), _256KiBL)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, + uploadUrl, + RewindableHttpContent.of(tmpFile.getPath()), + HttpContentRange.of(ByteRangeSpec.explicit(_512KiBL, _768KiBL))); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo("dataLoss"); + assertThat(confirmedBytes.get()).isEqualTo(-1); + } + } + + @Test + public void jsonParseFailure() throws Exception { + + HttpRequestHandler handler = + req -> { + StorageObject so = new StorageObject(); + URI uri = URI.create(req.uri()); + so.setName("object") + .setBucket("bucket") + .setGeneration(1L) + .setMetageneration(1L) + .setSize(BigInteger.ZERO) + .setMetadata(ImmutableMap.of("upload_id", uri.toString())); + + byte[] bytes = gson.toByteArray(so); + ByteBuf buf = Unpooled.wrappedBuffer(bytes, 0, bytes.length / 2); + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/json; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + AtomicLong confirmedBytes = new AtomicLong(-1L); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, uploadUrl, RewindableHttpContent.empty(), HttpContentRange.of(0)); + + StorageException se = assertThrows(StorageException.class, task::call); + // the parse error happens while trying to read the success object, make sure we raise it as + // a client side retryable exception + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo(null); + // Finalization was successful, but we can't confirm the number of bytes due to the parse + // error + assertThat(confirmedBytes.get()).isEqualTo(-1); + + ResultRetryAlgorithm idempotentHandler = + StorageRetryStrategy.getDefaultStorageRetryStrategy().getIdempotentHandler(); + boolean shouldRetry = idempotentHandler.shouldRetry(se, null); + assertThat(shouldRetry).isTrue(); + } + } + + @Test + public void jsonDeserializationOnlyAttemptedWhenContentPresent() throws Exception { + + HttpRequestHandler handler = + req -> { + DefaultFullHttpResponse resp = new DefaultFullHttpResponse(req.protocolVersion(), OK); + resp.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); + resp.headers().set("x-goog-stored-content-length", "0"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + httpClientContext, uploadUrl, RewindableHttpContent.empty(), HttpContentRange.of(0)); + + ResumableOperationResult<@Nullable StorageObject> operationResult = task.call(); + StorageObject call = operationResult.getObject(); + assertThat(call).isNull(); + assertThat(operationResult.getPersistedSize()).isEqualTo(0L); + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionQueryTaskTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionQueryTaskTest.java new file mode 100644 index 0000000000..07a04ed61e --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionQueryTaskTest.java @@ -0,0 +1,235 @@ +/* + * Copyright 2023 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; + +import static com.google.cloud.storage.ByteSizeConstants._256KiBL; +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus.OK; +import static org.junit.Assert.assertThrows; + +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.FakeHttpServer.HttpRequestHandler; +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.ParallelFriendly; +import com.google.cloud.storage.it.runner.annotations.SingleBackend; +import io.grpc.netty.shaded.io.netty.buffer.ByteBuf; +import io.grpc.netty.shaded.io.netty.buffer.Unpooled; +import io.grpc.netty.shaded.io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.grpc.netty.shaded.io.netty.handler.codec.http.FullHttpResponse; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus; +import java.math.BigInteger; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@SingleBackend(Backend.PROD) +@ParallelFriendly +public final class ITJsonResumableSessionQueryTaskTest { + private static final GsonFactory gson = GsonFactory.getDefaultInstance(); + private static final NetHttpTransport transport = new NetHttpTransport.Builder().build(); + private static final HttpResponseStatus RESUME_INCOMPLETE = + HttpResponseStatus.valueOf(308, "Resume Incomplete"); + private static final HttpResponseStatus APPEND_GREATER_THAN_CURRENT_SIZE = + HttpResponseStatus.valueOf(503, ""); + + private HttpClientContext httpClientContext; + + @Before + public void setUp() throws Exception { + httpClientContext = + HttpClientContext.of(transport.createRequestFactory(), new JsonObjectParser(gson)); + } + + @Test + public void successfulSession() throws Exception { + HttpRequestHandler handler = + req -> { + StorageObject so = new StorageObject(); + so.setName("object-name").setSize(BigInteger.ZERO); + ByteBuf buf = Unpooled.wrappedBuffer(gson.toByteArray(so)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/json; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionQueryTask task = + new JsonResumableSessionQueryTask(httpClientContext, uploadUrl); + + ResumableOperationResult<@Nullable StorageObject> result = task.call(); + StorageObject object = result.getObject(); + assertThat(object).isNotNull(); + assertThat(result.getPersistedSize()).isEqualTo(0L); + } + } + + @Test + public void successfulSession_noObject() throws Exception { + HttpRequestHandler handler = + req -> { + DefaultFullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK); + response.headers().set("X-Goog-Stored-Content-Length", 0); + return response; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionQueryTask task = + new JsonResumableSessionQueryTask(httpClientContext, uploadUrl); + + ResumableOperationResult<@Nullable StorageObject> result = task.call(); + StorageObject object = result.getObject(); + assertThat(object).isNull(); + assertThat(result.getPersistedSize()).isEqualTo(0L); + } + } + + @Test + public void incompleteSession() throws Exception { + HttpRequestHandler handler = + req -> { + DefaultFullHttpResponse response = + new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + response + .headers() + .set( + HttpHeaderNames.RANGE, + ByteRangeSpec.relativeLength(0L, _256KiBL).getHttpRangeHeader()); + return response; + }; + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionQueryTask task = + new JsonResumableSessionQueryTask(httpClientContext, uploadUrl); + + ResumableOperationResult<@Nullable StorageObject> result = task.call(); + assertThat(result.getPersistedSize()).isEqualTo(_256KiBL); + } + } + + /** + * This is a hard failure from the perspective of GCS as a range header is a required header to be + * included in the response to a query upload request. + */ + @Test + public void incompleteSession_missingRangeHeader() throws Exception { + HttpRequestHandler handler = + req -> new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionQueryTask task = + new JsonResumableSessionQueryTask(httpClientContext, uploadUrl); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(503); + assertThat(se).hasMessageThat().contains("Range"); + } + } + + @Test + public void successfulSession_noJson_noStoredContentLength() throws Exception { + HttpRequestHandler handler = req -> new DefaultFullHttpResponse(req.protocolVersion(), OK); + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionQueryTask task = + new JsonResumableSessionQueryTask(httpClientContext, uploadUrl); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + } + } + + @Test + public void successfulSession_noSize() throws Exception { + HttpRequestHandler handler = + req -> { + StorageObject so = new StorageObject(); + so.setName("object-name"); + ByteBuf buf = Unpooled.wrappedBuffer(gson.toByteArray(so)); + + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), OK, buf); + resp.headers().set(CONTENT_TYPE, "application/json; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionQueryTask task = + new JsonResumableSessionQueryTask(httpClientContext, uploadUrl); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + } + } + + @Test + public void query_badOffset() throws Exception { + HttpRequestHandler handler = + req -> { + // error message from GCS circa 2023-02 + ByteBuf buf = + Unpooled.wrappedBuffer( + "Invalid request. According to the Content-Range header, the upload offset is 524288 byte(s), which exceeds already uploaded size of 262144 byte(s)." + .getBytes(StandardCharsets.UTF_8)); + FullHttpResponse resp = + new DefaultFullHttpResponse( + req.protocolVersion(), APPEND_GREATER_THAN_CURRENT_SIZE, buf); + resp.headers().set(CONTENT_TYPE, "text/plain; charset=utf-8"); + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableSessionQueryTask task = + new JsonResumableSessionQueryTask(httpClientContext, uploadUrl); + + StorageException se = assertThrows(StorageException.class, task::call); + assertThat(se.getCode()).isEqualTo(0); + assertThat(se.getReason()).isEqualTo("dataLoss"); + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java new file mode 100644 index 0000000000..a71f5cf493 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2023 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; + +import static com.google.cloud.storage.ByteSizeConstants._256KiBL; +import static com.google.cloud.storage.ByteSizeConstants._512KiBL; +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONTENT_RANGE; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.RANGE; + +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonObjectParser; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.core.ApiClock; +import com.google.api.core.NanoClock; +import com.google.api.gax.retrying.ResultRetryAlgorithm; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.FakeHttpServer.HttpRequestHandler; +import com.google.cloud.storage.Retrying.RetryingDependencies; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.grpc.netty.shaded.io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpRequest; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public final class ITJsonResumableSessionTest { + private static final GsonFactory gson = GsonFactory.getDefaultInstance(); + private static final NetHttpTransport transport = new NetHttpTransport.Builder().build(); + private static final HttpResponseStatus RESUME_INCOMPLETE = + HttpResponseStatus.valueOf(308, "Resume Incomplete"); + private static final HttpResponseStatus APPEND_GREATER_THAN_CURRENT_SIZE = + HttpResponseStatus.valueOf(503, ""); + private static final RetryingDependencies RETRYING_DEPENDENCIES = + new RetryingDependencies() { + @Override + public RetrySettings getRetrySettings() { + return RetrySettings.newBuilder().setMaxAttempts(3).build(); + } + + @Override + public ApiClock getClock() { + return NanoClock.getDefaultClock(); + } + }; + private static final ResultRetryAlgorithm RETRY_ALGORITHM = + StorageRetryStrategy.getUniformStorageRetryStrategy().getIdempotentHandler(); + private HttpClientContext httpClientContext; + + @Rule public final TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + httpClientContext = + HttpClientContext.of(transport.createRequestFactory(), new JsonObjectParser(gson)); + } + + @Test + public void rewindWillQueryStatusOnlyWhenDirty() throws Exception { + HttpContentRange range1 = HttpContentRange.of(ByteRangeSpec.explicit(0L, _512KiBL)); + HttpContentRange range2 = HttpContentRange.query(); + HttpContentRange range3 = HttpContentRange.of(ByteRangeSpec.explicit(_256KiBL, _512KiBL)); + + final List requests = Collections.synchronizedList(new ArrayList<>()); + HttpRequestHandler handler = + req -> { + requests.add(req); + String contentRange = req.headers().get(CONTENT_RANGE); + System.out.println("contentRange = " + contentRange); + DefaultFullHttpResponse resp = + new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + if (range1.getHeaderValue().equals(contentRange)) { + resp.headers().set(RANGE, ByteRangeSpec.explicit(0L, _256KiBL).getHttpRangeHeader()); + } else if (range2.getHeaderValue().equals(contentRange)) { + resp.headers().set(RANGE, ByteRangeSpec.explicit(0L, _256KiBL).getHttpRangeHeader()); + } else { + resp.headers().set(RANGE, ByteRangeSpec.explicit(0L, _512KiBL).getHttpRangeHeader()); + } + return resp; + }; + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler); + TmpFile tmpFile = + DataGenerator.base64Characters().tempFile(temp.newFolder().toPath(), _512KiBL)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableWrite resumableWrite = JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl); + JsonResumableSession session = + new JsonResumableSession( + httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); + + ResumableOperationResult<@Nullable StorageObject> operationResult = + session.put(RewindableHttpContent.of(tmpFile.getPath()), range1); + StorageObject call = operationResult.getObject(); + assertThat(call).isNull(); + assertThat(operationResult.getPersistedSize()).isEqualTo(_512KiBL); + } + + assertThat(requests).hasSize(3); + List actual = + requests.stream().map(r -> r.headers().get(CONTENT_RANGE)).collect(Collectors.toList()); + + List expected = + ImmutableList.of(range1.getHeaderValue(), range2.getHeaderValue(), range3.getHeaderValue()); + + assertThat(actual).isEqualTo(expected); + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java new file mode 100644 index 0000000000..5f23a5aa19 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java @@ -0,0 +1,155 @@ +/* + * Copyright 2023 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; + +import static com.google.cloud.storage.JsonResumableSessionFailureScenario.isContinue; +import static com.google.cloud.storage.JsonResumableSessionFailureScenario.isOk; +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.client.http.EmptyContent; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.services.storage.model.StorageObject; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import org.junit.Test; + +public final class JsonResumableSessionFailureScenarioTest { + private static final GsonFactory gson = GsonFactory.getDefaultInstance(); + + @Test + public void isOk_200() { + assertThat(isOk(200)).isTrue(); + } + + @Test + public void isOk_201() { + assertThat(isOk(201)).isTrue(); + } + + @Test + public void isContinue_308() { + assertThat(isContinue(308)).isTrue(); + } + + @Test + public void toStorageException_ioExceptionDuringContentResolutionAddedAsSuppressed() + throws IOException { + HttpRequest req = + new MockHttpTransport() + .createRequestFactory() + .buildPutRequest(new GenericUrl("http://localhost:80980"), new EmptyContent()); + req.getHeaders().setContentLength(0L).setContentRange(HttpContentRange.of(0).getHeaderValue()); + + HttpResponse resp = req.execute(); + resp.getHeaders().setContentType("text/plain; charset=utf-8").setContentLength(5L); + + StorageException storageException = + JsonResumableSessionFailureScenario.SCENARIO_1.toStorageException( + "uploadId", + resp, + new Cause(), + () -> { + throw new Kaboom(); + }); + + assertThat(storageException.getCode()).isEqualTo(400); + assertThat(storageException).hasCauseThat().isInstanceOf(Cause.class); + assertThat(storageException.getSuppressed()).isNotEmpty(); + assertThat(storageException.getSuppressed()[0]).isInstanceOf(StorageException.class); + assertThat(storageException.getSuppressed()[0]).hasCauseThat().isInstanceOf(Kaboom.class); + } + + @Test + public void multilineResponseBodyIsProperlyPrefixed() throws Exception { + StorageObject so = new StorageObject(); + so.setName("object-name") + .setSize(BigInteger.ZERO) + .setGeneration(1L) + .setMetageneration(2L) + .setMetadata( + ImmutableMap.of( + "k1", "v1", + "k2", "v2")); + final String json = gson.toPrettyString(so); + + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); + HttpRequest req = + new MockHttpTransport() + .createRequestFactory() + .buildPutRequest(new GenericUrl("http://localhost:80980"), new EmptyContent()); + req.getHeaders().setContentLength(0L); + + HttpResponse resp = req.execute(); + resp.getHeaders() + .setContentType("application/json; charset=utf-8") + .setContentLength((long) bytes.length); + + StorageException storageException = + JsonResumableSessionFailureScenario.SCENARIO_0.toStorageException( + "uploadId", resp, null, () -> json); + + assertThat(storageException.getCode()).isEqualTo(0); + assertThat(storageException).hasMessageThat().contains("\t|< \"generation\": \"1\",\n"); + } + + @Test + public void xGoogStoredHeadersIncludedIfPresent() throws IOException { + HttpRequest req = + new MockHttpTransport() + .createRequestFactory() + .buildPutRequest(new GenericUrl("http://localhost:80980"), new EmptyContent()); + req.getHeaders().setContentLength(0L); + + HttpResponse resp = req.execute(); + resp.getHeaders() + .set("X-Goog-Stored-Content-Length", "5") + .set("x-goog-stored-content-encoding", "identity") + .set("X-GOOG-STORED-SOMETHING", "blah") + .setContentLength(0L); + + StorageException storageException = + JsonResumableSessionFailureScenario.SCENARIO_0.toStorageException( + "uploadId", resp, null, () -> null); + + assertThat(storageException.getCode()).isEqualTo(0); + assertThat(storageException).hasMessageThat().contains("|< x-goog-stored-content-length: 5"); + assertThat(storageException) + .hasMessageThat() + .contains("|< x-goog-stored-content-encoding: identity"); + assertThat(storageException).hasMessageThat().contains("|< x-goog-stored-something: blah"); + } + + private static final class Cause extends RuntimeException { + + private Cause() { + super("Cause"); + } + } + + private static final class Kaboom extends IOException { + + private Kaboom() { + super("Kaboom!!!"); + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java new file mode 100644 index 0000000000..5d23537771 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 2023 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; + +import static com.google.cloud.storage.TestUtils.xxd; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.base.MoreObjects; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import net.jqwik.api.Arbitraries; +import net.jqwik.api.Arbitrary; +import net.jqwik.api.Combinators; +import net.jqwik.api.ForAll; +import net.jqwik.api.Property; +import net.jqwik.api.Provide; +import net.jqwik.api.RandomDistribution; +import org.checkerframework.checker.nullness.qual.NonNull; + +final class RewindableHttpContentPropertyTest { + + @Property + void path(@ForAll("PathScenario") PathScenario pathScenario) throws Exception { + try (PathScenario s = pathScenario) { + RewindableHttpContent content = RewindableHttpContent.of(s.getPath()); + assertThrows( + IOException.class, + () -> { + try (ErroringOutputStream erroringOutputStream = + new ErroringOutputStream(s.getErrorAtOffset())) { + content.writeTo(erroringOutputStream); + } + }); + content.rewindTo(s.getRewindOffset()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + content.writeTo(baos); + + String actual = xxd(baos.toByteArray()); + + assertThat(actual).isEqualTo(s.getExpectedXxd()); + } + } + + @Provide("PathScenario") + static Arbitrary pathScenario() { + return Arbitraries.lazyOf( + () -> + Arbitraries.oneOf( + bytes(1, 10), + bytes(10, 100), + bytes(100, 1_000), + bytes(1_000, 10_000), + bytes(10_000, 100_000), + bytes(100_000, 1_000_000), + bytes(1_000_000, 10_000_000)) + .flatMap( + bytes -> + Combinators.combine( + Arbitraries.integers().between(0, bytes.length - 1), + Arbitraries.integers().between(0, bytes.length - 1), + Arbitraries.just(bytes)) + .as(PathScenario::of))); + } + + @NonNull + private static Arbitrary bytes(int minFileSize, int maxFileSize) { + return Arbitraries.integers() + .between(minFileSize, maxFileSize) + .withDistribution(RandomDistribution.uniform()) + .map(DataGenerator.base64Characters()::genBytes); + } + + private static final class PathScenario implements AutoCloseable { + + private static final Path TMP_DIR = Paths.get(System.getProperty("java.io.tmpdir")); + + private final int rewindOffset; + private final int errorAtOffset; + private final TmpFile tmpFile; + private final byte[] expectedBytes; + private final String expectedXxd; + + private PathScenario( + int rewindOffset, int errorAtOffset, TmpFile tmpFile, byte[] expectedBytes) { + this.rewindOffset = rewindOffset; + this.errorAtOffset = errorAtOffset; + this.tmpFile = tmpFile; + this.expectedBytes = expectedBytes; + this.expectedXxd = xxd(expectedBytes); + } + + public int getRewindOffset() { + return rewindOffset; + } + + public int getErrorAtOffset() { + return errorAtOffset; + } + + public Path getPath() { + return tmpFile.getPath(); + } + + public String getExpectedXxd() { + return expectedXxd; + } + + public long getFullLength() throws IOException { + return Files.size(tmpFile.getPath()); + } + + @Override + public void close() throws IOException { + tmpFile.close(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("expectedXxd", "\n" + expectedXxd) + .add("expectedBytes.length", expectedBytes.length) + .add("rewindOffset", rewindOffset) + .add("errorAtOffset", errorAtOffset) + .add("tmpFile", tmpFile) + .toString(); + } + + private static PathScenario of(int rewindOffset, int errorAtOffset, byte[] bytes) { + try { + TmpFile tmpFile1 = TmpFile.of(TMP_DIR, "PathScenario", ".bin"); + try (SeekableByteChannel writer = tmpFile1.writer()) { + writer.write(ByteBuffer.wrap(bytes)); + } + byte[] expectedBytes = + Arrays.copyOfRange(bytes, Math.min(rewindOffset, bytes.length), bytes.length); + return new PathScenario(rewindOffset, errorAtOffset, tmpFile1, expectedBytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + static final class ErroringOutputStream extends OutputStream { + private final long errorAt; + private long totalWritten; + + ErroringOutputStream(long errorAt) { + this.errorAt = errorAt; + this.totalWritten = 0; + } + + @Override + public void write(int b) throws IOException { + if (totalWritten++ >= errorAt) { + throw new IOException("Reached errorAt limit"); + } + } + + @Override + public void write(byte[] b) throws IOException { + if (totalWritten + b.length >= errorAt) { + throw new IOException("Reached errorAt limit"); + } else { + totalWritten += b.length; + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int diff = len - off; + if (totalWritten + diff >= errorAt) { + throw new IOException("Reached errorAt limit"); + } else { + totalWritten += diff; + } + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java index ce750154e3..8395c6d0a5 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java @@ -983,17 +983,6 @@ private BlobInfo initializeUpload( return blobInfo; } - @Test - public void testCreateFromFile() throws Exception { - byte[] dataToSend = {1, 2, 3, 4}; - Path tempFile = Files.createTempFile("testCreateFrom", ".tmp"); - Files.write(tempFile, dataToSend); - - BlobInfo blobInfo = initializeUpload(dataToSend); - Blob blob = storage.createFrom(blobInfo, tempFile); - assertEquals(expectedUpdated, blob); - } - @Test public void testCreateFromStream() throws Exception { byte[] dataToSend = {1, 2, 3, 4, 5}; diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/Functions.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/Functions.java index 46d5643e0b..47ab7a8251 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/Functions.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/Functions.java @@ -37,6 +37,10 @@ default CtxFunction andThen(CtxFunction f) { return (Ctx ctx, TestRetryConformance trc) -> f.apply(apply(ctx, trc), trc); } + default CtxFunction compose(CtxFunction f) { + return (Ctx ctx, TestRetryConformance trc) -> apply(f.apply(ctx, trc), trc); + } + static CtxFunction identity() { return (ctx, c) -> ctx; } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java index 8aa55955a2..d42755ef07 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java @@ -60,6 +60,7 @@ import java.util.Random; import java.util.Set; import java.util.function.BiPredicate; +import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; import org.junit.After; @@ -209,7 +210,7 @@ private static CtxFunction getReplaceStorageInObjectsFromCtx() { * each defined scenario from google-cloud-conformance-tests and our defined {@link * RpcMethodMappings}. */ - private static final class RetryTestCaseResolver { + static final class RetryTestCaseResolver { private static final String HEX_SHUFFLE_SEED_OVERRIDE = System.getProperty("HEX_SHUFFLE_SEED_OVERRIDE"); @@ -220,7 +221,7 @@ private static final class RetryTestCaseResolver { private final String host; private final String projectId; - RetryTestCaseResolver( + private RetryTestCaseResolver( String retryTestsJsonResourcePath, RpcMethodMappings mappings, BiPredicate testAllowFilter, @@ -383,8 +384,12 @@ static BiPredicate specificMappings(int... mapp return (m, c) -> set.contains(c.getMappingId()); } - static BiPredicate instructionsAre(String... instructions) { - return (m, trc) -> + static BiPredicate lift(Predicate p) { + return (m, trc) -> p.test(trc); + } + + static Predicate instructionsAre(String... instructions) { + return trc -> trc.getInstruction().getInstructionsList().equals(ImmutableList.copyOf(instructions)); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java index 67b8159b44..7a87f33048 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java @@ -20,10 +20,12 @@ import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.notificationSetup; import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.pubsubTopicSetup; import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.serviceAccount; +import static com.google.cloud.storage.conformance.retry.ITRetryConformanceTest.RetryTestCaseResolver.instructionsAre; import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.not; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import com.google.cloud.BaseServiceException; import com.google.cloud.Binding; @@ -54,6 +56,7 @@ import com.google.cloud.storage.conformance.retry.CtxFunctions.Local; import com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup; import com.google.cloud.storage.conformance.retry.CtxFunctions.Rpc; +import com.google.cloud.storage.conformance.retry.Functions.CtxFunction; import com.google.cloud.storage.conformance.retry.Functions.EConsumer; import com.google.cloud.storage.conformance.retry.RpcMethod.storage.bucket_acl; import com.google.cloud.storage.conformance.retry.RpcMethod.storage.buckets; @@ -124,6 +127,11 @@ final class RpcMethodMappings { static final int _2MiB = 2 * 1024 * 1024; private static final ImmutableMap MODIFY = ImmutableMap.of("a", "b"); + private static final CtxFunction skipUntil2114Fixed = + temporarilySkipMapping( + "Skipped until https://github.com/googleapis/java-storage/issues/2114 is fixed", + instructionsAre("return-503-after-8192K", "return-408") + .or(instructionsAre("return-503-after-256K"))); final Multimap funcMap; RpcMethodMappings() { @@ -1526,7 +1534,10 @@ private static void insert(ArrayList a) { a.add( RpcMethodMapping.newBuilder(50, objects.insert) .withApplicable(TestRetryConformance::isPreconditionsProvided) - .withSetup(defaultSetup.andThen(Local.blobInfoWithGenerationZero)) + .withSetup( + defaultSetup + .andThen(Local.blobInfoWithGenerationZero) + .compose(skipUntil2114Fixed)) .withTest( (ctx, c) -> ctx.map( @@ -1541,7 +1552,10 @@ private static void insert(ArrayList a) { a.add( RpcMethodMapping.newBuilder(51, objects.insert) .withApplicable(TestRetryConformance::isPreconditionsProvided) - .withSetup(defaultSetup.andThen(Local.blobInfoWithGenerationZero)) + .withSetup( + defaultSetup + .andThen(Local.blobInfoWithGenerationZero) + .compose(skipUntil2114Fixed)) .withTest( (ctx, c) -> ctx.map( @@ -2099,4 +2113,12 @@ private static void put(ArrayList a) {} private static Predicate methodGroupIs(String s) { return (c) -> s.equals(c.getMethod().getGroup()); } + + private static CtxFunction temporarilySkipMapping( + String message, java.util.function.Predicate p) { + return (ctx, trc) -> { + assumeFalse(message, p.test(trc)); + return ctx; + }; + } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectChecksumSupportTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectChecksumSupportTest.java index 2bcb78e640..d8e026cbc0 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectChecksumSupportTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectChecksumSupportTest.java @@ -28,6 +28,7 @@ import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage.BlobWriteOption; import com.google.cloud.storage.StorageException; +import com.google.cloud.storage.TmpFile; import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.it.ITObjectChecksumSupportTest.ChecksummedTestContentProvider; import com.google.cloud.storage.it.runner.StorageITRunner; @@ -42,18 +43,24 @@ import com.google.common.io.ByteStreams; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Path; +import java.nio.file.Paths; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(StorageITRunner.class) @CrossRun( - transports = {Transport.HTTP, Transport.GRPC}, + transports = {Transport.HTTP /*, Transport.GRPC*/}, backends = Backend.PROD) @Parameterized(ChecksummedTestContentProvider.class) public final class ITObjectChecksumSupportTest { + private static final Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir")); + @Inject public Generator generator; @Inject public Storage storage; @@ -116,6 +123,50 @@ public void testCrc32cValidated_createFrom_expectSuccess() throws IOException { assertThat(blob.getCrc32c()).isEqualTo(content.getCrc32cBase64()); } + @Test + public void testCrc32cValidated_createFrom_path_expectFailure() throws IOException { + String blobName = generator.randomObjectName(); + BlobId blobId = BlobId.of(bucket.getName(), blobName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setCrc32c(content.getCrc32cBase64()).build(); + + try (TmpFile tmpFile = TmpFile.of(tmpDir, "prefix", "bin")) { + try (SeekableByteChannel writer = tmpFile.writer()) { + writer.write(ByteBuffer.wrap(content.concat('x'))); + } + StorageException expected = + assertThrows( + StorageException.class, + () -> + storage.createFrom( + blobInfo, + tmpFile.getPath(), + BlobWriteOption.doesNotExist(), + BlobWriteOption.crc32cMatch())); + assertThat(expected.getCode()).isEqualTo(400); + } + } + + @Test + public void testCrc32cValidated_createFrom_path_expectSuccess() throws IOException { + String blobName = generator.randomObjectName(); + BlobId blobId = BlobId.of(bucket.getName(), blobName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setCrc32c(content.getCrc32cBase64()).build(); + + try (TmpFile tmpFile = TmpFile.of(tmpDir, "prefix", "bin")) { + try (SeekableByteChannel writer = tmpFile.writer()) { + writer.write(ByteBuffer.wrap(content.getBytes())); + } + + Blob blob = + storage.createFrom( + blobInfo, + tmpFile.getPath(), + BlobWriteOption.doesNotExist(), + BlobWriteOption.crc32cMatch()); + assertThat(blob.getCrc32c()).isEqualTo(content.getCrc32cBase64()); + } + } + @Test public void testCrc32cValidated_writer_expectFailure() { String blobName = generator.randomObjectName(); From 29feeaf3b17739cda518862fa9e4164095664366 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 13 Jul 2023 23:17:55 +0200 Subject: [PATCH 03/20] test(deps): update dependency com.google.api.grpc:proto-google-cloud-kms-v1 to v0.115.0 (#2116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(deps): update dependency com.google.api.grpc:proto-google-cloud-kms-v1 to v0.115.0 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- README.md | 6 +++--- google-cloud-storage/pom.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e40d26ae56..1edd542d18 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,13 @@ implementation 'com.google.cloud:google-cloud-storage' If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-storage:2.23.0' +implementation 'com.google.cloud:google-cloud-storage:2.24.0' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.23.0" +libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.24.0" ``` @@ -428,7 +428,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-storage/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-storage.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.23.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.24.0 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index ccfc41547b..d501ed0196 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -173,7 +173,7 @@ com.google.api.grpc proto-google-cloud-kms-v1 - 0.114.0 + 0.115.0 test From 1b52a1053130620011515060787bada10c324c0b Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Thu, 13 Jul 2023 19:20:35 -0400 Subject: [PATCH 04/20] feat: BlobWriteChannelV2 - same throughput less GC (#2110) Use stable buffer allocation with laziness. Leverage new JsonResumableSession to provide more robustness and easier separation of concerns compared to BlobWriteChannel * rename blobWriteChannel.ser.properties to the correct blobReadChannel.ser.properties ### Runtime improvments Throughput is on par with the existing v1 implementation, however GC impact has been lightened with the new implementation. Below is the summary of the GC improvement between v1 and v2. These GC numbers were collected while uploading 4096 randomly sized objects, from 128KiB..2GiB across 16 concurrent threads, using a default chunkSize of 16MiB. | metric | unit | v1 | v2 | % decrease | |---------------------------------|--------|-------------:|-------------:|-----------:| | gc.alloc.rate | MB/sec | 2240.056 | 1457.731 | 34.924 | | gc.alloc.rate.norm | B/op | 955796726217 | 638403730507 | 33.207 | | gc.churn.G1_Eden_Space | MB/sec | 1597.009 | 1454.304 | 8.936 | | gc.churn.G1_Eden_Space.norm | B/op | 681418424320 | 636902965248 | 6.533 | | gc.churn.G1_Old_Gen | MB/sec | 691.877 | 11.316 | 98.364 | | gc.churn.G1_Old_Gen.norm | B/op | 295213237398 | 4955944331 | 98.321 | | gc.churn.G1_Survivor_Space | MB/sec | 0.004 | 0.002 | 50.000 | | gc.churn.G1_Survivor_Space.norm | B/op | 1572864 | 786432 | 50.000 | | gc.count | counts | 1670 | 1319 | 21.018 | | gc.time | ms | 15936 | 9527 | 40.217 | Overall allocation rate is decreased, while Old_Gen use is almost entirely eliminated. ``` openjdk version "11.0.18" 2023-01-17 OpenJDK Runtime Environment (build 11.0.18+10-post-Debian-1deb11u1) OpenJDK 64-Bit Server VM (build 11.0.18+10-post-Debian-1deb11u1, mixed mode, sharing) -Xms12g -Xmx12g ``` All other java parameters are defaults. --- .../clirr-ignored-differences.xml | 4 +- .../ApiaryUnbufferedWritableByteChannel.java | 109 ++ .../storage/BaseStorageWriteChannel.java | 178 ++++ .../cloud/storage/BlobReadChannelV2.java | 18 +- .../cloud/storage/BlobWriteChannel.java | 367 +------ .../cloud/storage/BlobWriteChannelV2.java | 153 +++ .../com/google/cloud/storage/Buffers.java | 4 + .../com/google/cloud/storage/Conversions.java | 4 + .../GapicUnbufferedWritableByteChannel.java | 10 - .../cloud/storage/GrpcBlobWriteChannel.java | 119 +-- .../cloud/storage/HttpContentRange.java | 22 + .../storage/HttpDownloadSessionBuilder.java | 2 +- .../storage/HttpUploadSessionBuilder.java | 34 + ...HttpWritableByteChannelSessionBuilder.java | 190 ++++ .../cloud/storage/JsonResumableSession.java | 8 +- .../storage/JsonResumableSessionPutTask.java | 25 +- .../cloud/storage/JsonResumableWrite.java | 38 +- .../cloud/storage/LazyWriteChannel.java | 69 ++ .../google/cloud/storage/ResumableMedia.java | 4 + .../com/google/cloud/storage/Retrying.java | 11 +- .../cloud/storage/RewindableHttpContent.java | 84 +- .../com/google/cloud/storage/StorageImpl.java | 72 +- .../cloud/storage/StorageWriteChannel.java | 24 + .../UnbufferedWritableByteChannelSession.java | 14 +- .../java/com/google/cloud/storage/Utils.java | 9 + .../cloud/storage/spi/v1/HttpStorageRpc.java | 9 +- .../com/google/cloud/storage/BlobTest.java | 38 - .../cloud/storage/BlobWriteChannelTest.java | 961 ------------------ .../ITJsonResumableSessionPutTaskTest.java | 24 + .../storage/ITJsonResumableSessionTest.java | 141 ++- ...onResumableSessionFailureScenarioTest.java | 2 +- .../PackagePrivateMethodWorkarounds.java | 14 +- .../RewindableByteBufferContentTest.java | 160 +++ .../RewindableHttpContentPropertyTest.java | 164 ++- .../cloud/storage/SerializationTest.java | 52 +- .../cloud/storage/StorageImplMockitoTest.java | 206 ---- .../com/google/cloud/storage/TestUtils.java | 18 +- .../com/google/cloud/storage/TmpFile.java | 6 + .../retry/ITRetryConformanceTest.java | 5 + .../storage/it/ITBlobWriteChannelTest.java | 187 +--- .../cloud/storage/it/TemporaryBucket.java | 18 +- .../storage/blobReadChannel.ser.properties | 70 ++ .../storage/blobWriteChannel.ser.properties | 78 +- 43 files changed, 1802 insertions(+), 1923 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageWriteChannel.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/HttpUploadSessionBuilder.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/HttpWritableByteChannelSessionBuilder.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/LazyWriteChannel.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/StorageWriteChannel.java delete mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableByteBufferContentTest.java create mode 100644 google-cloud-storage/src/test/resources/com/google/cloud/storage/blobReadChannel.ser.properties diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml index 703b8ad654..ad681c4be3 100644 --- a/google-cloud-storage/clirr-ignored-differences.xml +++ b/google-cloud-storage/clirr-ignored-differences.xml @@ -4,8 +4,8 @@ 7012 - com/google/cloud/storage/UnbufferedReadableByteChannelSession$UnbufferedReadableByteChannel - * read(*) + com/google/cloud/storage/UnbufferedWritableByteChannelSession$UnbufferedWritableByteChannel + * write(*) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java new file mode 100644 index 0000000000..5fce18ae52 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java @@ -0,0 +1,109 @@ +/* + * Copyright 2023 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; + +import com.google.api.core.SettableApiFuture; +import com.google.api.gax.retrying.ResultRetryAlgorithm; +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.Retrying.RetryingDependencies; +import com.google.cloud.storage.UnbufferedWritableByteChannelSession.UnbufferedWritableByteChannel; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.util.function.LongConsumer; +import javax.annotation.ParametersAreNonnullByDefault; +import org.checkerframework.checker.nullness.qual.Nullable; + +@ParametersAreNonnullByDefault +final class ApiaryUnbufferedWritableByteChannel implements UnbufferedWritableByteChannel { + + private final ResumableSession session; + + private final SettableApiFuture result; + private final LongConsumer committedBytesCallback; + + private boolean open = true; + private long cumulativeByteCount; + private boolean finished = false; + + ApiaryUnbufferedWritableByteChannel( + HttpClientContext httpClientContext, + RetryingDependencies deps, + ResultRetryAlgorithm alg, + JsonResumableWrite resumableWrite, + SettableApiFuture result, + LongConsumer committedBytesCallback) { + this.session = ResumableSession.json(httpClientContext, deps, alg, resumableWrite); + this.result = result; + this.committedBytesCallback = committedBytesCallback; + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { + if (!open) { + throw new ClosedChannelException(); + } + RewindableHttpContent content = RewindableHttpContent.of(Utils.subArray(srcs, offset, length)); + long available = content.getLength(); + long newFinalByteOffset = cumulativeByteCount + available; + final HttpContentRange header; + ByteRangeSpec rangeSpec = ByteRangeSpec.explicit(cumulativeByteCount, newFinalByteOffset); + if (available % ByteSizeConstants._256KiB == 0) { + header = HttpContentRange.of(rangeSpec); + } else { + header = HttpContentRange.of(rangeSpec, newFinalByteOffset); + finished = true; + } + try { + ResumableOperationResult<@Nullable StorageObject> operationResult = + session.put(content, header); + long persistedSize = operationResult.getPersistedSize(); + committedBytesCallback.accept(persistedSize); + this.cumulativeByteCount = persistedSize; + if (finished) { + StorageObject storageObject = operationResult.getObject(); + result.set(storageObject); + } + return available; + } catch (Exception e) { + result.setException(e); + throw StorageException.coalesce(e); + } + } + + @Override + public boolean isOpen() { + return open; + } + + @Override + public void close() throws IOException { + open = false; + if (!finished) { + try { + ResumableOperationResult<@Nullable StorageObject> operationResult = + session.put(RewindableHttpContent.empty(), HttpContentRange.of(cumulativeByteCount)); + long persistedSize = operationResult.getPersistedSize(); + committedBytesCallback.accept(persistedSize); + result.set(operationResult.getObject()); + } catch (Exception e) { + result.setException(e); + throw StorageException.coalesce(e); + } + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageWriteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageWriteChannel.java new file mode 100644 index 0000000000..f5f076327c --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseStorageWriteChannel.java @@ -0,0 +1,178 @@ +/* + * Copyright 2023 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; + +import static com.google.cloud.storage.ByteSizeConstants._16MiB; +import static com.google.cloud.storage.ByteSizeConstants._256KiB; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutureCallback; +import com.google.api.core.ApiFutures; +import com.google.api.core.SettableApiFuture; +import com.google.cloud.storage.BufferedWritableByteChannelSession.BufferedWritableByteChannel; +import com.google.cloud.storage.Conversions.Decoder; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.MoreExecutors; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import org.checkerframework.checker.nullness.qual.Nullable; + +abstract class BaseStorageWriteChannel implements StorageWriteChannel { + + private final Decoder objectDecoder; + private final SettableApiFuture result; + + private long position; + private boolean open; + private int chunkSize; + private LazyWriteChannel lazyWriteChannel; + private BufferHandle bufferHandle; + + /** + * This is tracked for compatibility with BlobWriteChannel, such that simply creating a writer + * will create an object. + * + *

In the future we should move away from this behavior, and only create an object if write is + * called. + */ + protected boolean writeCalledAtLeastOnce; + + protected BaseStorageWriteChannel(Decoder objectDecoder) { + this.objectDecoder = objectDecoder; + this.result = SettableApiFuture.create(); + this.open = true; + this.chunkSize = _16MiB; + this.writeCalledAtLeastOnce = false; + } + + @Override + public final synchronized void setChunkSize(int chunkSize) { + Preconditions.checkArgument(chunkSize > 0, "chunkSize must be > 0, received %d", chunkSize); + Preconditions.checkState( + bufferHandle == null || bufferHandle.position() == 0, + "unable to change chunk size with data buffered"); + this.chunkSize = chunkSize; + } + + @Override + public final synchronized boolean isOpen() { + return open; + } + + @Override + public final synchronized void close() throws IOException { + try { + if (open && !writeCalledAtLeastOnce) { + this.write(ByteBuffer.allocate(0)); + } + if (internalGetLazyChannel().isOpen()) { + StorageException.wrapIOException(internalGetLazyChannel().getChannel()::close); + } + } finally { + open = false; + } + } + + @Override + public final synchronized int write(ByteBuffer src) throws IOException { + if (!open) { + throw new ClosedChannelException(); + } + writeCalledAtLeastOnce = true; + try { + BufferedWritableByteChannel tmp = internalGetLazyChannel().getChannel(); + if (!tmp.isOpen()) { + return 0; + } + int write = tmp.write(src); + return write; + } catch (StorageException e) { + throw new IOException(e); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(StorageException.coalesce(e)); + } + } + + @Override + public final ApiFuture getObject() { + return ApiFutures.transform(result, objectDecoder::decode, MoreExecutors.directExecutor()); + } + + protected final BufferHandle getBufferHandle() { + if (bufferHandle == null) { + bufferHandle = BufferHandle.allocate(Buffers.alignSize(getChunkSize(), _256KiB)); + } + return bufferHandle; + } + + protected final int getChunkSize() { + return chunkSize; + } + + @Nullable + protected final T getResolvedObject() { + if (result.isDone()) { + return StorageException.wrapFutureGet(result); + } else { + return null; + } + } + + protected final long getCommittedPosition() { + return position; + } + + protected final void setCommittedPosition(long l) { + position = l; + } + + protected final void setOpen(boolean isOpen) { + this.open = isOpen; + } + + protected abstract LazyWriteChannel newLazyWriteChannel(); + + private LazyWriteChannel internalGetLazyChannel() { + if (lazyWriteChannel == null) { + LazyWriteChannel tmp = newLazyWriteChannel(); + ApiFuture future = tmp.getSession().getResult(); + ApiFutures.addCallback( + future, + new ApiFutureCallback() { + @Override + public void onFailure(Throwable t) { + if (!result.isDone()) { + result.setException(t); + } + } + + @Override + public void onSuccess(T t) { + if (!result.isDone()) { + result.set(t); + } + } + }, + MoreExecutors.directExecutor()); + lazyWriteChannel = tmp; + } + return lazyWriteChannel; + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannelV2.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannelV2.java index 1f3c5b52cc..82aba8b136 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannelV2.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannelV2.java @@ -143,15 +143,18 @@ public String toString() { static final class BlobReadChannelContext { private final HttpStorageOptions storageOptions; private final HttpRetryAlgorithmManager retryAlgorithmManager; + private final HttpClientContext httpClientContext; private final Storage apiaryClient; private BlobReadChannelContext( HttpStorageOptions storageOptions, - Storage apiaryClient, - HttpRetryAlgorithmManager retryAlgorithmManager) { + HttpRetryAlgorithmManager retryAlgorithmManager, + HttpClientContext httpClientContext, + Storage apiaryClient) { this.storageOptions = storageOptions; - this.apiaryClient = apiaryClient; this.retryAlgorithmManager = retryAlgorithmManager; + this.httpClientContext = httpClientContext; + this.apiaryClient = apiaryClient; } public HttpStorageOptions getStorageOptions() { @@ -162,13 +165,20 @@ public HttpRetryAlgorithmManager getRetryAlgorithmManager() { return retryAlgorithmManager; } + public HttpClientContext getHttpClientContext() { + return httpClientContext; + } + public Storage getApiaryClient() { return apiaryClient; } static BlobReadChannelContext from(HttpStorageOptions options) { return new BlobReadChannelContext( - options, options.getStorageRpcV1().getStorage(), options.getRetryAlgorithmManager()); + options, + options.getRetryAlgorithmManager(), + HttpClientContext.from(options.getStorageRpcV1()), + options.getStorageRpcV1().getStorage()); } static BlobReadChannelContext from(com.google.cloud.storage.Storage s) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java index 7b19258790..28c4b8d1e6 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java @@ -16,371 +16,78 @@ package com.google.cloud.storage; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.Executors.callable; - -import com.google.api.core.InternalApi; +import com.google.api.core.ApiFuture; import com.google.api.gax.retrying.ResultRetryAlgorithm; import com.google.api.services.storage.model.StorageObject; import com.google.cloud.BaseWriteChannel; import com.google.cloud.RestorableState; -import com.google.cloud.RetryHelper; import com.google.cloud.WriteChannel; -import java.math.BigInteger; -import java.util.function.Function; -import java.util.function.Supplier; -import org.checkerframework.checker.nullness.qual.NonNull; - -/** Write channel implementation to upload Google Cloud Storage blobs. */ -class BlobWriteChannel extends BaseWriteChannel { - - private final ResultRetryAlgorithm algorithmForWrite; - // Detect if flushBuffer() is being retried or not. - // TODO: I don't think this is thread safe, and there's probably a better way to detect a retry - // occuring. - private boolean retrying = false; - private boolean checkingForLastChunk = false; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; - // Contains metadata of the updated object or null if upload is not completed. - private StorageObject storageObject; - - BlobWriteChannel( - HttpStorageOptions storageOptions, - BlobInfo blobInfo, - String uploadId, - ResultRetryAlgorithm algorithmForWrite) { - super(storageOptions, blobInfo, uploadId); - this.algorithmForWrite = algorithmForWrite; - } +/** + * Hierarchy retained for {@link RestorableState#restore()}. Will be removed in next major version! + */ +@Deprecated +class BlobWriteChannel extends BaseWriteChannel + implements StorageWriteChannel { - boolean isRetrying() { - return retrying; + private BlobWriteChannel() { + super(null, null, null); + throw new IllegalStateException("Illegal method access"); } - StorageObject getStorageObject() { - return storageObject; + @Override + public ApiFuture getObject() { + throw new IllegalStateException("Illegal method access"); } @Override protected HttpStorageOptions getOptions() { - return (HttpStorageOptions) super.getOptions(); - } - - private StorageObject transmitChunk( - int chunkOffset, int chunkLength, long position, boolean last) { - return getOptions() - .getStorageRpcV1() - .writeWithResponse(getUploadId(), getBuffer(), chunkOffset, position, chunkLength, last); + throw new IllegalStateException("Illegal method access"); } - private long getRemotePosition() { - return getOptions().getStorageRpcV1().getCurrentUploadOffset(getUploadId()); - } - - private static StorageException unrecoverableState( - String uploadId, - int chunkOffset, - int chunkLength, - long localPosition, - long remotePosition, - boolean last) { - return unrecoverableState( - uploadId, - chunkOffset, - chunkLength, - localPosition, - remotePosition, - last, - "Unable to recover in upload.\nThis may be a symptom of multiple clients uploading to the same upload session."); - } - - private static StorageException errorResolvingMetadataLastChunk( - String uploadId, - int chunkOffset, - int chunkLength, - long localPosition, - long remotePosition, - boolean last) { - return unrecoverableState( - uploadId, - chunkOffset, - chunkLength, - localPosition, - remotePosition, - last, - "Unable to load object metadata to determine if last chunk was successfully written"); - } - - private static StorageException unrecoverableState( - String uploadId, - int chunkOffset, - int chunkLength, - long localPosition, - long remotePosition, - boolean last, - String message) { - StringBuilder sb = new StringBuilder(); - sb.append(message).append("\n\n"); - sb.append("For debugging purposes:\n"); - sb.append("uploadId: ").append(uploadId).append('\n'); - sb.append("chunkOffset: ").append(chunkOffset).append('\n'); - sb.append("chunkLength: ").append(chunkLength).append('\n'); - sb.append("localOffset: ").append(localPosition).append('\n'); - sb.append("remoteOffset: ").append(remotePosition).append('\n'); - sb.append("lastChunk: ").append(last).append("\n\n"); - return new StorageException(0, sb.toString()); - } - - // Retriable interruption occurred. - // Variables: - // chunk = getBuffer() - // localNextByteOffset == getPosition() - // chunkSize = getChunkSize() - // - // Case 1: localNextByteOffset == remoteNextByteOffset: - // Retrying the entire chunk - // - // Case 2: localNextByteOffset < remoteNextByteOffset - // && driftOffset < chunkSize: - // Upload progressed and localNextByteOffset is not in-sync with - // remoteNextByteOffset and driftOffset is less than chunkSize. - // driftOffset must be less than chunkSize for it to retry using - // chunk maintained in memory. - // Find the driftOffset by subtracting localNextByteOffset from - // remoteNextByteOffset. - // Use driftOffset to determine where to restart from using the chunk in - // memory. - // - // Case 3: localNextByteOffset < remoteNextByteOffset - // && driftOffset == chunkSize: - // Special case of Case 2. - // If chunkSize is equal to driftOffset then remoteNextByteOffset has moved on - // to the next chunk. - // - // Case 4: localNextByteOffset < remoteNextByteOffset - // && driftOffset > chunkSize: - // Throw exception as remoteNextByteOffset has drifted beyond the retriable - // chunk maintained in memory. This is not possible unless there's multiple - // clients uploading to the same resumable upload session. - // - // Case 5: localNextByteOffset > remoteNextByteOffset: - // For completeness, this case is not possible because it would require retrying - // a 400 status code which is not allowed. - // - // Case 6: remoteNextByteOffset==-1 && last == true - // Upload is complete and retry occurred in the "last" chunk. Data sent was - // received by the service. - // - // Case 7: remoteNextByteOffset==-1 && last == false && !checkingForLastChunk - // Not last chunk and are not checkingForLastChunk, allow for the client to - // catch up to final chunk which meets - // Case 6. - // - // Case 8: remoteNextByteOffset==-1 && last == false && checkingForLastChunk - // Not last chunk and checkingForLastChunk means this is the second time we - // hit this case, meaning the upload was completed by a different client. - // - // Case 9: Only possible if the client local offset continues beyond the remote - // offset which is not possible. - // @Override protected void flushBuffer(final int length, final boolean lastChunk) { - try { - Retrying.run( - getOptions(), - algorithmForWrite, - callable( - new Runnable() { - @Override - public void run() { - // Get remote offset from API - final long localPosition = getPosition(); - // For each request it should be possible to retry from its location in this code - final long remotePosition = isRetrying() ? getRemotePosition() : localPosition; - final int chunkOffset = (int) (remotePosition - localPosition); - final int chunkLength = length - chunkOffset; - final boolean uploadAlreadyComplete = remotePosition == -1; - // Enable isRetrying state to reduce number of calls to getRemotePosition() - if (!isRetrying()) { - retrying = true; - } - if (uploadAlreadyComplete && lastChunk) { - // Case 6 - // Request object metadata if not available - long totalBytes = getPosition() + length; - if (storageObject == null) { - storageObject = - getOptions() - .getStorageRpcV1() - .queryCompletedResumableUpload(getUploadId(), totalBytes); - } - // the following checks are defined here explicitly to provide a more - // informative if either storageObject is unable to be resolved or it's size is - // unable to be determined. This scenario is a very rare case of failure that - // can arise when packets are lost. - if (storageObject == null) { - throw errorResolvingMetadataLastChunk( - getUploadId(), - chunkOffset, - chunkLength, - localPosition, - remotePosition, - lastChunk); - } - // Verify that with the final chunk we match the blob length - BigInteger size = storageObject.getSize(); - if (size == null) { - throw errorResolvingMetadataLastChunk( - getUploadId(), - chunkOffset, - chunkLength, - localPosition, - remotePosition, - lastChunk); - } - if (size.longValue() != totalBytes) { - throw unrecoverableState( - getUploadId(), - chunkOffset, - chunkLength, - localPosition, - remotePosition, - lastChunk); - } - retrying = false; - } else if (uploadAlreadyComplete && !lastChunk && !checkingForLastChunk) { - // Case 7 - // Make sure this is the second to last chunk. - checkingForLastChunk = true; - // Continue onto next chunk in case this is the last chunk - } else if (localPosition <= remotePosition && chunkOffset < getChunkSize()) { - // Case 1 && Case 2 - // We are in a position to send a chunk - storageObject = - transmitChunk(chunkOffset, chunkLength, remotePosition, lastChunk); - retrying = false; - } else if (localPosition < remotePosition && chunkOffset == getChunkSize()) { - // Case 3 - // Continue to next chunk to catch up with remotePosition we are one chunk - // behind - retrying = false; - } else { - // Case 4 && Case 8 && Case 9 - throw unrecoverableState( - getUploadId(), - chunkOffset, - chunkLength, - localPosition, - remotePosition, - lastChunk); - } - } - }), - Function.identity()); - } catch (RetryHelper.RetryHelperException e) { - throw StorageException.translateAndThrow(e); - } + throw new IllegalStateException("Illegal method access"); } protected StateImpl.Builder stateBuilder() { - return StateImpl.builder(getOptions(), getEntity(), getUploadId()) - .setResultRetryAlgorithm(algorithmForWrite); + throw new IllegalStateException("Illegal method access"); } - static Builder newBuilder() { - return new Builder(); - } - - static final class Builder { - private HttpStorageOptions storageOptions; - private BlobInfo blobInfo; - private Supplier<@NonNull String> uploadIdSupplier; - private ResultRetryAlgorithm algorithmForWrite; - - public Builder setStorageOptions(HttpStorageOptions storageOptions) { - this.storageOptions = storageOptions; - return this; - } - - public Builder setBlobInfo(BlobInfo blobInfo) { - this.blobInfo = blobInfo; - return this; - } - - public Builder setUploadIdSupplier(Supplier uploadIdSupplier) { - this.uploadIdSupplier = uploadIdSupplier; - return this; - } - - public Builder setAlgorithmForWrite(ResultRetryAlgorithm algorithmForWrite) { - this.algorithmForWrite = algorithmForWrite; - return this; - } - - BlobWriteChannel build() { - String uploadId = requireNonNull(uploadIdSupplier, "uploadId must be non null").get(); - return new BlobWriteChannel( - requireNonNull(storageOptions, "storageOptions must be non null"), - blobInfo, - requireNonNull(uploadId, "uploadId must be non null"), - requireNonNull(algorithmForWrite, "algorithmForWrite must be non null")); - } + @Override + public RestorableState capture() { + throw new IllegalStateException("Illegal method access"); } + /** Retained for binary compatibility. Will be removed at next major version! */ + @SuppressWarnings("unused") + @Deprecated + @VisibleForTesting static class StateImpl extends BaseWriteChannel.BaseState { private static final long serialVersionUID = -6700378962714601115L; - private final ResultRetryAlgorithm algorithmForWrite; + private ResultRetryAlgorithm algorithmForWrite; - StateImpl(Builder builder) { + private StateImpl(Builder builder) { super(builder); - this.algorithmForWrite = builder.algorithmForWrite; - } - - @InternalApi - Builder toBuilder(StorageOptions options) { - Builder builder = new Builder(options, entity, uploadId); - builder.setPosition(position).setBuffer(buffer).setIsOpen(isOpen).setChunkSize(chunkSize); - return builder; - } - - static class Builder extends BaseWriteChannel.BaseState.Builder { - private ResultRetryAlgorithm algorithmForWrite; - - private Builder(StorageOptions options, BlobInfo blobInfo, String uploadId) { - super(options, blobInfo, uploadId); - } - - public Builder setResultRetryAlgorithm(ResultRetryAlgorithm algorithmForWrite) { - this.algorithmForWrite = algorithmForWrite; - return this; - } - - @Override - public RestorableState build() { - return new StateImpl(this); - } - } - - static Builder builder(StorageOptions options, BlobInfo blobInfo, String uploadId) { - return new Builder(options, blobInfo, uploadId); } @Override public WriteChannel restore() { try { - BlobWriteChannel channel = - BlobWriteChannel.newBuilder() - // should be okay, as the value always comes from an instance we gate to have - // HttpStorageOptions - .setStorageOptions((HttpStorageOptions) serviceOptions) - .setBlobInfo(entity) - .setUploadIdSupplier(() -> uploadId) - .setAlgorithmForWrite(algorithmForWrite) - .build(); - channel.restore(this); - return channel; + StorageObject encode = + entity != null ? Conversions.apiary().blobInfo().encode(entity) : null; + return new BlobWriteChannelV2.BlobWriteChannelV2State( + (HttpStorageOptions) serviceOptions, + JsonResumableWrite.of(encode, ImmutableMap.of(), uploadId), + position, + isOpen, + chunkSize, + buffer) + .restore(); } catch (Exception e) { throw StorageException.coalesce(e); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java new file mode 100644 index 0000000000..1bb0742513 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java @@ -0,0 +1,153 @@ +/* + * Copyright 2023 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; + +import com.google.api.core.ApiFutures; +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.RestorableState; +import com.google.cloud.WriteChannel; +import com.google.cloud.storage.BlobReadChannelV2.BlobReadChannelContext; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; + +final class BlobWriteChannelV2 extends BaseStorageWriteChannel { + + private final BlobReadChannelContext blobChannelContext; + private final JsonResumableWrite start; + + BlobWriteChannelV2(BlobReadChannelContext blobChannelContext, JsonResumableWrite start) { + super(Conversions.apiary().blobInfo()); + this.start = start; + this.blobChannelContext = blobChannelContext; + } + + @Override + public synchronized RestorableState capture() { + final byte[] bufferSnapshot; + BufferHandle handle = getBufferHandle(); + if (handle.position() > 0) { + ByteBuffer byteBuffer = handle.get(); + // duplicate so we don't actually modify the existing instance + ByteBuffer dup = byteBuffer.duplicate(); + dup.flip(); + int remaining = dup.remaining(); + bufferSnapshot = new byte[remaining]; + dup.get(bufferSnapshot); + } else { + bufferSnapshot = new byte[0]; + } + return new BlobWriteChannelV2State( + blobChannelContext.getStorageOptions(), + start, + getCommittedPosition(), + isOpen(), + getChunkSize(), + bufferSnapshot); + } + + @Override + protected LazyWriteChannel newLazyWriteChannel() { + return new LazyWriteChannel<>( + () -> + ResumableMedia.http() + .write() + .byteChannel(blobChannelContext.getHttpClientContext()) + .resumable() + .setCommittedBytesCallback(this::setCommittedPosition) + .withRetryConfig( + blobChannelContext.getStorageOptions().asRetryDependencies(), + blobChannelContext.getRetryAlgorithmManager().idempotent()) + .buffered(getBufferHandle()) + .setStartAsync(ApiFutures.immediateFuture(start)) + .build()); + } + + static final class BlobWriteChannelV2State + implements RestorableState, Serializable { + private static final long serialVersionUID = -1901664719924133474L; + + private final HttpStorageOptions options; + private final JsonResumableWrite resumableWrite; + + private final Long position; + private final Boolean isOpen; + private final Integer chunkSize; + private final byte[] bufferSnapshot; + + BlobWriteChannelV2State( + HttpStorageOptions options, + JsonResumableWrite resumableWrite, + Long position, + Boolean isOpen, + Integer chunkSize, + byte[] bufferSnapshot) { + this.options = options; + this.resumableWrite = resumableWrite; + this.position = position; + this.isOpen = isOpen; + this.chunkSize = chunkSize; + this.bufferSnapshot = bufferSnapshot; + } + + @Override + public WriteChannel restore() { + BlobWriteChannelV2 channel = + new BlobWriteChannelV2(BlobReadChannelContext.from(options), resumableWrite); + if (chunkSize != null) { + channel.setChunkSize(chunkSize); + } + if (bufferSnapshot != null && bufferSnapshot.length > 0) { + BufferHandle handle = channel.getBufferHandle(); + ByteBuffer byteBuffer = handle.get(); + byteBuffer.put(bufferSnapshot); + } + if (position != null) { + channel.setCommittedPosition(position); + } + if (isOpen != null) { + channel.setOpen(isOpen); + } + return channel; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BlobWriteChannelV2State)) { + return false; + } + BlobWriteChannelV2State that = (BlobWriteChannelV2State) o; + return Objects.equals(options, that.options) + && Objects.equals(resumableWrite, that.resumableWrite) + && Objects.equals(position, that.position) + && Objects.equals(isOpen, that.isOpen) + && Objects.equals(chunkSize, that.chunkSize) + && Arrays.equals(bufferSnapshot, that.bufferSnapshot); + } + + @Override + public int hashCode() { + int result = Objects.hash(options, resumableWrite, position, isOpen, chunkSize); + result = 31 * result + Arrays.hashCode(bufferSnapshot); + return result; + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Buffers.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Buffers.java index 1e397608ac..a828523cab 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Buffers.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Buffers.java @@ -54,6 +54,10 @@ static void position(Buffer b, int position) { b.position(position); } + static int position(Buffer b) { + return b.position(); + } + /** * attempt to drain all of {@code content} into {@code dsts} starting from {@code dsts[0]} through * {@code dsts[dsts.length - 1]} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Conversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Conversions.java index 680dd1aec4..3b81639a08 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Conversions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Conversions.java @@ -46,6 +46,10 @@ default Decoder andThen(Decoder d) { static Decoder identity() { return (x) -> x; } + + default Decoder compose(Decoder before) { + return in -> this.decode(before.decode(in)); + } } interface Codec extends Encoder, Decoder { 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 007e48021d..951a52edbc 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 @@ -62,16 +62,6 @@ final class GapicUnbufferedWritableByteChannel< requestFactory.bucketName(), writeCtx.getConfirmedBytes()::set, resultFuture::set); } - @Override - public int write(ByteBuffer src) throws IOException { - return Math.toIntExact(write(new ByteBuffer[] {src})); - } - - @Override - public long write(ByteBuffer[] srcs) throws IOException { - return write(srcs, 0, srcs.length); - } - @Override public long write(ByteBuffer[] srcs, int srcsOffset, int srcLength) throws IOException { if (!open) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobWriteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobWriteChannel.java index 7eaae03397..f3520180b3 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobWriteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobWriteChannel.java @@ -16,38 +16,23 @@ package com.google.cloud.storage; -import static com.google.cloud.storage.ByteSizeConstants._16MiB; -import static com.google.cloud.storage.ByteSizeConstants._256KiB; - import com.google.api.core.ApiFuture; import com.google.api.gax.retrying.ResultRetryAlgorithm; import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.cloud.RestorableState; import com.google.cloud.WriteChannel; -import com.google.cloud.storage.BufferedWritableByteChannelSession.BufferedWritableByteChannel; import com.google.cloud.storage.Retrying.RetryingDependencies; -import com.google.common.base.Preconditions; -import com.google.common.base.Suppliers; import com.google.storage.v2.WriteObjectRequest; import com.google.storage.v2.WriteObjectResponse; -import java.io.IOException; -import java.nio.ByteBuffer; import java.util.function.Supplier; -final class GrpcBlobWriteChannel implements WriteChannel { - - private final LazyWriteChannel lazyWriteChannel; - - private int chunkSize = _16MiB; +final class GrpcBlobWriteChannel extends BaseStorageWriteChannel { - /** - * This is tracked for compatibility with BlobWriteChannel, such that simply creating a writer - * will create an object. - * - *

In the future we should move away from this behavior, and only create an object if write is - * called. - */ - private boolean writeCalledAtLeastOnce = false; + private final ClientStreamingCallable write; + private final RetryingDependencies deps; + private final ResultRetryAlgorithm alg; + private final Supplier> start; + private final Hasher hasher; GrpcBlobWriteChannel( ClientStreamingCallable write, @@ -55,27 +40,12 @@ final class GrpcBlobWriteChannel implements WriteChannel { ResultRetryAlgorithm alg, Supplier> start, Hasher hasher) { - lazyWriteChannel = - new LazyWriteChannel( - Suppliers.memoize( - () -> - ResumableMedia.gapic() - .write() - .byteChannel(write) - .setHasher(hasher) - .setByteStringStrategy(ByteStringStrategy.copy()) - .resumable() - .withRetryConfig(deps, alg) - .buffered(BufferHandle.allocate(Buffers.alignSize(chunkSize, _256KiB))) - .setStartAsync(start.get()) - .build())); - } - - @Override - public void setChunkSize(int chunkSize) { - Preconditions.checkState( - !lazyWriteChannel.isOpened(), "Unable to change chunkSize after write"); - this.chunkSize = chunkSize; + super(Conversions.grpc().blobInfo().compose(WriteObjectResponse::getResource)); + this.write = write; + this.deps = deps; + this.alg = alg; + this.start = start; + this.hasher = hasher; } @Override @@ -84,57 +54,18 @@ public RestorableState capture() { } @Override - public int write(ByteBuffer src) throws IOException { - writeCalledAtLeastOnce = true; - return lazyWriteChannel.getChannel().write(src); - } - - @Override - public boolean isOpen() { - if (!writeCalledAtLeastOnce) { - return true; - } else { - return lazyWriteChannel.isOpened() && lazyWriteChannel.getChannel().isOpen(); - } - } - - @Override - public void close() throws IOException { - if (!writeCalledAtLeastOnce) { - lazyWriteChannel.getChannel().write(ByteBuffer.allocate(0)); - } - if (isOpen()) { - lazyWriteChannel.getChannel().close(); - } - } - - ApiFuture getResults() { - return lazyWriteChannel.session.get().getResult(); - } - - private static final class LazyWriteChannel { - private final Supplier> session; - private final Supplier channel; - - private boolean opened = false; - - public LazyWriteChannel( - Supplier> session) { - this.session = session; - this.channel = - Suppliers.memoize( - () -> { - opened = true; - return session.get().open(); - }); - } - - public BufferedWritableByteChannel getChannel() { - return channel.get(); - } - - public boolean isOpened() { - return opened; - } + protected LazyWriteChannel newLazyWriteChannel() { + return new LazyWriteChannel<>( + () -> + ResumableMedia.gapic() + .write() + .byteChannel(write) + .setHasher(hasher) + .setByteStringStrategy(ByteStringStrategy.copy()) + .resumable() + .withRetryConfig(deps, alg) + .buffered(getBufferHandle()) + .setStartAsync(start.get()) + .build()); } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpContentRange.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpContentRange.java index 8961f52393..49fc5a1949 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpContentRange.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpContentRange.java @@ -32,6 +32,8 @@ private HttpContentRange(boolean finalizing) { public abstract String getHeaderValue(); + public abstract boolean endOffsetEquals(long e); + public boolean isFinalizing() { return finalizing; } @@ -92,6 +94,11 @@ public String getHeaderValue() { return String.format("bytes %d-%d/*", spec.beginOffset(), spec.endOffsetInclusive()); } + @Override + public boolean endOffsetEquals(long e) { + return e == spec.endOffset(); + } + @Override public ByteRangeSpec range() { return spec; @@ -141,6 +148,11 @@ public String getHeaderValue() { return String.format("bytes %d-%d/%d", spec.beginOffset(), spec.endOffsetInclusive(), size); } + @Override + public boolean endOffsetEquals(long e) { + return e == spec.endOffset(); + } + @Override public long getSize() { return size; @@ -193,6 +205,11 @@ public String getHeaderValue() { return String.format("bytes */%d", size); } + @Override + public boolean endOffsetEquals(long e) { + return false; + } + @Override public long getSize() { return size; @@ -229,6 +246,11 @@ private Query() { super(false); } + @Override + public boolean endOffsetEquals(long e) { + return false; + } + @Override public String getHeaderValue() { return "bytes */*"; diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpDownloadSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpDownloadSessionBuilder.java index 370c9d6cea..2cf21d767b 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpDownloadSessionBuilder.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpDownloadSessionBuilder.java @@ -38,7 +38,7 @@ final class HttpDownloadSessionBuilder { private HttpDownloadSessionBuilder() {} - public static HttpDownloadSessionBuilder create() { + static HttpDownloadSessionBuilder create() { return INSTANCE; } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpUploadSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpUploadSessionBuilder.java new file mode 100644 index 0000000000..a673c1f9b4 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpUploadSessionBuilder.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 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; + +import org.checkerframework.checker.nullness.qual.NonNull; + +final class HttpUploadSessionBuilder { + private static final HttpUploadSessionBuilder INSTANCE = new HttpUploadSessionBuilder(); + + private HttpUploadSessionBuilder() {} + + static HttpUploadSessionBuilder create() { + return INSTANCE; + } + + @NonNull + HttpWritableByteChannelSessionBuilder byteChannel(@NonNull HttpClientContext httpClientContext) { + return new HttpWritableByteChannelSessionBuilder(httpClientContext); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpWritableByteChannelSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpWritableByteChannelSessionBuilder.java new file mode 100644 index 0000000000..19abf0928b --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpWritableByteChannelSessionBuilder.java @@ -0,0 +1,190 @@ +/* + * Copyright 2023 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; + +import static java.util.Objects.requireNonNull; + +import com.google.api.core.ApiFuture; +import com.google.api.core.SettableApiFuture; +import com.google.api.gax.retrying.ResultRetryAlgorithm; +import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.ChannelSession.BufferedWriteSession; +import com.google.cloud.storage.ChannelSession.UnbufferedWriteSession; +import com.google.cloud.storage.Retrying.RetryingDependencies; +import com.google.cloud.storage.UnbufferedWritableByteChannelSession.UnbufferedWritableByteChannel; +import java.nio.ByteBuffer; +import java.util.function.BiFunction; +import java.util.function.LongConsumer; +import org.checkerframework.checker.nullness.qual.NonNull; + +final class HttpWritableByteChannelSessionBuilder { + + private static final int DEFAULT_BUFFER_CAPACITY = ByteSizeConstants._16MiB; + @NonNull private final HttpClientContext httpClientContext; + + HttpWritableByteChannelSessionBuilder(@NonNull HttpClientContext httpClientContext) { + this.httpClientContext = + requireNonNull(httpClientContext, "httpClientContext must be non null"); + } + + /** + * The build {@link WritableByteChannelSession} will perform a "Resumable" upload. + * + *

A "Resumable" upload will sync the transmitted data with GCS upon each individual flush and + * when the channel is closed. + * + *

If an error is returned the individual flush can be transparently retried. + */ + ResumableUploadBuilder resumable() { + return new ResumableUploadBuilder(httpClientContext); + } + + static final class ResumableUploadBuilder { + + @NonNull private final HttpClientContext httpClientContext; + private RetryingDependencies deps; + private ResultRetryAlgorithm alg; + private LongConsumer committedBytesCallback; + + ResumableUploadBuilder(@NonNull HttpClientContext httpClientContext) { + this.httpClientContext = httpClientContext; + this.deps = RetryingDependencies.attemptOnce(); + this.alg = Retrying.neverRetry(); + this.committedBytesCallback = l -> {}; + } + + ResumableUploadBuilder setCommittedBytesCallback(@NonNull LongConsumer committedBytesCallback) { + this.committedBytesCallback = + requireNonNull(committedBytesCallback, "committedBytesCallback must be non null"); + return this; + } + + ResumableUploadBuilder withRetryConfig( + @NonNull RetryingDependencies deps, @NonNull ResultRetryAlgorithm alg) { + this.deps = requireNonNull(deps, "deps must be non null"); + this.alg = requireNonNull(alg, "alg must be non null"); + return this; + } + + /** + * Do not apply any intermediate buffering. Any call to {@link + * java.nio.channels.WritableByteChannel#write(ByteBuffer)} will be segmented as is and sent to + * GCS. + * + *

Note: this is considered an advanced API, and should not be used in circumstances in which + * control of {@link ByteBuffer}s sent to {@code write} is not self-contained. + */ + UnbufferedResumableUploadBuilder unbuffered() { + return new UnbufferedResumableUploadBuilder(); + } + + /** Buffer up to {@link #DEFAULT_BUFFER_CAPACITY} worth of bytes before attempting to flush */ + BufferedResumableUploadBuilder buffered() { + return buffered(BufferHandle.allocate(DEFAULT_BUFFER_CAPACITY)); + } + + /** + * Buffer using {@code byteBuffer} worth of space before attempting to flush. + * + *

The provided {@link ByteBuffer} should be aligned with GCSs block size of 256 + * KiB. + */ + BufferedResumableUploadBuilder buffered(ByteBuffer byteBuffer) { + return buffered(BufferHandle.handleOf(byteBuffer)); + } + + BufferedResumableUploadBuilder buffered(BufferHandle bufferHandle) { + return new BufferedResumableUploadBuilder(bufferHandle); + } + + /** + * When constructing any of our channel sessions, there is always a {@link + * GapicUnbufferedWritableByteChannel} at the bottom of it. This method creates a BiFunction + * which will instantiate the {@link GapicUnbufferedWritableByteChannel} when provided with a + * {@code StartT} value and a {@code SettableApiFuture}. + * + *

As part of providing the function, the provided parameters {@code FlusherFactory} and + * {@code f} are "bound" into the returned function. In conjunction with the configured fields + * of this class a new instance of {@link GapicUnbufferedWritableByteChannel} can be + * constructed. + */ + private BiFunction< + JsonResumableWrite, SettableApiFuture, UnbufferedWritableByteChannel> + bindFunction() { + // it is theoretically possible that the setter methods for the following variables could + // be called again between when this method is invoked and the resulting function is invoked. + // To ensure we are using the specified values at the point in time they are bound to the + // function read them into local variables which will be closed over rather than the class + // fields. + RetryingDependencies boundDeps = deps; + ResultRetryAlgorithm boundAlg = alg; + return (start, resultFuture) -> + new ApiaryUnbufferedWritableByteChannel( + httpClientContext, boundDeps, boundAlg, start, resultFuture, committedBytesCallback); + } + + final class UnbufferedResumableUploadBuilder { + + private ApiFuture start; + + /** + * Set the Future which will contain the ResumableWrite information necessary to open the + * Write stream. + */ + UnbufferedResumableUploadBuilder setStartAsync(ApiFuture start) { + this.start = requireNonNull(start, "start must be non null"); + return this; + } + + UnbufferedWritableByteChannelSession build() { + return new UnbufferedWriteSession<>( + requireNonNull(start, "start must be non null"), + bindFunction().andThen(StorageByteChannels.writable()::createSynchronized)); + } + } + + final class BufferedResumableUploadBuilder { + + private final BufferHandle bufferHandle; + + private ApiFuture start; + + BufferedResumableUploadBuilder(BufferHandle bufferHandle) { + this.bufferHandle = bufferHandle; + } + + /** + * Set the Future which will contain the ResumableWrite information necessary to open the + * Write stream. + */ + BufferedResumableUploadBuilder setStartAsync(ApiFuture start) { + this.start = requireNonNull(start, "start must be non null"); + return this; + } + + BufferedWritableByteChannelSession build() { + return new BufferedWriteSession<>( + requireNonNull(start, "start must be non null"), + bindFunction() + .andThen(c -> new DefaultBufferedWritableByteChannel(bufferHandle, c)) + .andThen(StorageByteChannels.writable()::createSynchronized)); + } + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java index f59355502b..debd611b04 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java @@ -20,6 +20,7 @@ import com.google.api.services.storage.model.StorageObject; import com.google.cloud.storage.Conversions.Decoder; import com.google.cloud.storage.Retrying.RetryingDependencies; +import com.google.cloud.storage.spi.v1.HttpRpcContext; import com.google.cloud.storage.spi.v1.HttpStorageRpc; import io.opencensus.trace.EndSpanOptions; import java.util.concurrent.atomic.AtomicBoolean; @@ -63,6 +64,8 @@ final class JsonResumableSession extends ResumableSession { JsonResumableSessionPutTask task = new JsonResumableSessionPutTask( context, resumableWrite.getUploadId(), content, contentRange); + HttpRpcContext httpRpcContext = HttpRpcContext.getInstance(); + httpRpcContext.newInvocationId(); AtomicBoolean dirty = new AtomicBoolean(false); return Retrying.run( deps, @@ -70,10 +73,11 @@ final class JsonResumableSession extends ResumableSession { () -> { if (dirty.getAndSet(true)) { ResumableOperationResult<@Nullable StorageObject> query = query(); - if (query.getObject() != null) { + long persistedSize = query.getPersistedSize(); + if (contentRange.endOffsetEquals(persistedSize) || query.getObject() != null) { return query; } else { - task.rewindTo(query.getPersistedSize()); + task.rewindTo(persistedSize); } } return task.call(); 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 73b7d14a46..de905fb48e 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 @@ -21,8 +21,10 @@ import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; import com.google.api.services.storage.model.StorageObject; +import com.google.cloud.storage.HttpContentRange.HasRange; import com.google.cloud.storage.StorageException.IOExceptionCallable; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; import io.opencensus.common.Scope; import io.opencensus.trace.Span; import io.opencensus.trace.Status; @@ -56,7 +58,22 @@ final class JsonResumableSessionPutTask } public void rewindTo(long offset) { - content.rewindTo(offset); + if (originalContentRange instanceof HasRange) { + HasRange hasRange = (HasRange) originalContentRange; + ByteRangeSpec range = hasRange.range(); + long originalBegin = range.beginOffset(); + long contentOffset = offset - originalBegin; + Preconditions.checkArgument( + 0 <= contentOffset && contentOffset < content.getLength(), + "Rewind offset is out of bounds. (%s <= %s < %s)", + range.beginOffset(), + offset, + range.endOffset()); + content.rewindTo(contentOffset); + } else { + content.rewindTo(0); + } + if (contentRange instanceof HttpContentRange.HasRange) { HttpContentRange.HasRange range = (HttpContentRange.HasRange) contentRange; contentRange = range.map(s -> s.withNewBeginOffset(offset)); @@ -195,7 +212,11 @@ public void rewindTo(long offset) { span.setStatus(Status.UNKNOWN.withDescription(se.getMessage())); throw se; } - } catch (StorageException e) { + } catch (StorageException | IllegalArgumentException e) { + // IllegalArgumentException can happen if there is no json in the body and we try to parse it + // Our retry algorithms have special case for this, so in an effort to keep compatibility + // with those existing behaviors, explicitly rethrow an IllegalArgumentException that may have + // happened span.setStatus(Status.UNKNOWN.withDescription(e.getMessage())); throw e; } catch (Exception e) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java index c5e17aad52..4ec1fc8919 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java @@ -19,19 +19,31 @@ import com.google.api.services.storage.model.StorageObject; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.base.MoreObjects; +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.io.StringReader; import java.util.Map; import java.util.Objects; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; -final class JsonResumableWrite { - @MonotonicNonNull private final StorageObject object; +final class JsonResumableWrite implements Serializable { + private static final long serialVersionUID = 7934407897802252292L; + private static final Gson gson = new Gson(); + + @MonotonicNonNull private transient StorageObject object; @MonotonicNonNull private final Map options; @MonotonicNonNull private final String signedUrl; @NonNull private final String uploadId; + private volatile String objectJson; + private JsonResumableWrite( StorageObject object, Map options, @@ -77,6 +89,28 @@ public String toString() { .toString(); } + private String getObjectJson() { + if (objectJson == null) { + synchronized (this) { + if (objectJson == null) { + objectJson = gson.toJson(object); + } + } + } + return objectJson; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + String ignore = getObjectJson(); + out.defaultWriteObject(); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + JsonReader jsonReader = gson.newJsonReader(new StringReader(this.objectJson)); + this.object = gson.fromJson(jsonReader, StorageObject.class); + } + static JsonResumableWrite of( StorageObject req, Map options, String uploadId) { return new JsonResumableWrite(req, options, null, uploadId); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/LazyWriteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/LazyWriteChannel.java new file mode 100644 index 0000000000..1c14eda853 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/LazyWriteChannel.java @@ -0,0 +1,69 @@ +/* + * Copyright 2022 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; + +import com.google.cloud.storage.BufferedWritableByteChannelSession.BufferedWritableByteChannel; +import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; + +final class LazyWriteChannel { + + private final Supplier> sessionSupplier; + + @MonotonicNonNull private volatile BufferedWritableByteChannelSession session; + @MonotonicNonNull private volatile BufferedWritableByteChannel channel; + + private boolean open = false; + + LazyWriteChannel(Supplier> sessionSupplier) { + this.sessionSupplier = sessionSupplier; + } + + @NonNull + BufferedWritableByteChannel getChannel() { + if (channel != null) { + return channel; + } else { + synchronized (this) { + if (channel == null) { + open = true; + channel = getSession().open(); + } + return channel; + } + } + } + + @NonNull + BufferedWritableByteChannelSession getSession() { + if (session != null) { + return session; + } else { + synchronized (this) { + if (session == null) { + session = sessionSupplier.get(); + } + return session; + } + } + } + + boolean isOpen() { + return open && getChannel().isOpen(); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableMedia.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableMedia.java index 85c96bd8d5..2d3fbf939a 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableMedia.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableMedia.java @@ -103,6 +103,10 @@ static final class HttpMediaSession { private HttpMediaSession() {} + HttpUploadSessionBuilder write() { + return HttpUploadSessionBuilder.create(); + } + HttpDownloadSessionBuilder read() { return HttpDownloadSessionBuilder.create(); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Retrying.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Retrying.java index f625b58b95..0b5f66ce35 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Retrying.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Retrying.java @@ -103,11 +103,16 @@ static U run( } catch (StorageException se) { // we hope for this case throw se; + } catch (IllegalArgumentException iae) { + // IllegalArgumentException can happen if there is no json in the body and we try + // to parse it Our retry algorithms have special case for this, so in an effort to + // keep compatibility with those existing behaviors, explicitly rethrow an + // IllegalArgumentException that may have happened + throw iae; } catch (Exception e) { - // but wire in this fall through just in case. + // Wire in this fall through just in case. // all of our retry algorithms are centered around StorageException so this helps - // those - // be more effective + // those be more effective throw StorageException.coalesce(e); } }, diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java index fd171e0544..68bf4e22f0 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java @@ -22,11 +22,15 @@ import com.google.common.io.ByteStreams; import java.io.IOException; import java.io.OutputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.SeekableByteChannel; +import java.nio.channels.WritableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.Arrays; abstract class RewindableHttpContent extends AbstractHttpContent { @@ -48,6 +52,10 @@ static RewindableHttpContent empty() { return EmptyRewindableContent.INSTANCE; } + static RewindableHttpContent of(ByteBuffer... buffers) { + return new ByteBufferHttpContent(buffers); + } + static RewindableHttpContent of(Path path) throws IOException { return new PathRewindableHttpContent(path); } @@ -88,7 +96,7 @@ public long getLength() { } @Override - protected void rewindTo(long offset) { + void rewindTo(long offset) { Preconditions.checkArgument( offset < size, "provided offset must be less than size (%d < %d)", offset, size); this.readOffset = offset; @@ -103,4 +111,78 @@ public void writeTo(OutputStream out) throws IOException { } } } + + private static final class ByteBufferHttpContent extends RewindableHttpContent { + + private final ByteBuffer[] buffers; + // keep an array of the positions in case we need to rewind them for retries + // doing this is simpler than duplicating the buffers and using marks, as we don't need to + // advance the position of the original buffers upon success. + // We generally expect success, and in this case are planning in case of failure. + private final int[] positions; + private final long totalLength; + // track whether we have changed any state + private boolean dirty; + + private long offset; + + private ByteBufferHttpContent(ByteBuffer[] buffers) { + this.buffers = buffers; + this.positions = Arrays.stream(buffers).mapToInt(Buffers::position).toArray(); + this.totalLength = Arrays.stream(buffers).mapToLong(Buffer::remaining).sum(); + this.dirty = false; + } + + @Override + public long getLength() { + return totalLength - offset; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + dirty = true; + WritableByteChannel c = Channels.newChannel(out); + for (ByteBuffer buffer : buffers) { + c.write(buffer); + } + out.flush(); + } + + @Override + void rewindTo(long offset) { + Preconditions.checkArgument( + offset < totalLength, + "provided offset must be less than totalLength (%s < %s)", + offset, + totalLength); + if (dirty || offset != this.offset) { + // starting from the end of our data, walk back the buffers updating their position + // to coincide with the rewind of the overall content + int idx = buffers.length - 1; + for (long currentOffset = totalLength; currentOffset > 0; ) { + int position = positions[idx]; + ByteBuffer buf = buffers[idx]; + + int origRemaining = buf.limit() - position; + + long begin = currentOffset - origRemaining; + + if (begin <= offset && offset < currentOffset) { + long diff = offset - begin; + Buffers.position(buf, position + Math.toIntExact(diff)); + } else if (offset >= currentOffset) { + // the desired offset is after this buf + // ensure it does not have any available + Buffers.position(buf, buf.limit()); + } else { + Buffers.position(buf, position); + } + + currentOffset = begin; + idx -= 1; + } + } + this.offset = offset; + } + } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index a97a325c5c..fc8601cb6c 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -23,6 +23,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.Executors.callable; +import com.google.api.core.ApiFuture; import com.google.api.gax.paging.Page; import com.google.api.gax.retrying.ResultRetryAlgorithm; import com.google.api.services.storage.model.BucketAccessControl; @@ -84,7 +85,9 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.Nullable; @@ -274,14 +277,20 @@ public Blob createFrom( BlobInfo blobInfo, InputStream content, int bufferSize, BlobWriteOption... options) throws IOException { - BlobWriteChannel blobWriteChannel; - try (WriteChannel writer = writer(blobInfo, options)) { - blobWriteChannel = (BlobWriteChannel) writer; + ApiFuture objectFuture; + try (StorageWriteChannel writer = writer(blobInfo, options)) { + objectFuture = writer.getObject(); uploadHelper(Channels.newChannel(content), writer, bufferSize); } - StorageObject objectProto = blobWriteChannel.getStorageObject(); - BlobInfo info = Conversions.apiary().blobInfo().decode(objectProto); - return info.asBlob(this); + // keep these two try blocks separate for the time being + // leaving the above will cause the writer to close writing and finalizing the session and + // (hopefully, on successful finalization) resolve our future + try { + BlobInfo info = objectFuture.get(10, TimeUnit.SECONDS); + return info.asBlob(this); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw StorageException.coalesce(e); + } } /* @@ -646,38 +655,41 @@ public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption. } @Override - public BlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { + public StorageWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { Opts opts = Opts.unwrap(options).resolveFrom(blobInfo); final Map optionsMap = opts.getRpcOptions(); BlobInfo.Builder builder = blobInfo.toBuilder().setMd5(null).setCrc32c(null); BlobInfo updated = opts.blobInfoMapper().apply(builder).build(); - return BlobWriteChannel.newBuilder() - .setStorageOptions(getOptions()) - .setUploadIdSupplier( - ResumableMedia.startUploadForBlobInfo( - getOptions(), - updated, - optionsMap, - retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap))) - .setAlgorithmForWrite(retryAlgorithmManager.getForResumableUploadSessionWrite(optionsMap)) - .build(); + + StorageObject encode = codecs.blobInfo().encode(updated); + // open the resumable session outside the write channel + // the exception behavior of open is different from #write(ByteBuffer) + Supplier uploadIdSupplier = + ResumableMedia.startUploadForBlobInfo( + getOptions(), + updated, + optionsMap, + retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get()); + return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); } @Override - public BlobWriteChannel writer(URL signedURL) { + public StorageWriteChannel writer(URL signedURL) { + // TODO: is it possible to know if a signed url is configured to have a constraint which makes + // it idempotent? ResultRetryAlgorithm forResumableUploadSessionCreate = - retryAlgorithmManager.getForResumableUploadSessionCreate( - Collections - .emptyMap()); // TODO: is it possible to know if a signed url is configured to have - // a constraint which makes it idempotent? - return BlobWriteChannel.newBuilder() - .setStorageOptions(getOptions()) - .setUploadIdSupplier( - ResumableMedia.startUploadForSignedUrl( - getOptions(), signedURL, forResumableUploadSessionCreate)) - .setAlgorithmForWrite( - retryAlgorithmManager.getForResumableUploadSessionWrite(Collections.emptyMap())) - .build(); + retryAlgorithmManager.getForResumableUploadSessionCreate(Collections.emptyMap()); + // open the resumable session outside the write channel + // the exception behavior of open is different from #write(ByteBuffer) + String signedUrlString = signedURL.toString(); + Supplier uploadIdSupplier = + ResumableMedia.startUploadForSignedUrl( + getOptions(), signedURL, forResumableUploadSessionCreate); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(signedUrlString, uploadIdSupplier.get()); + return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); } @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageWriteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageWriteChannel.java new file mode 100644 index 0000000000..d1badc0b17 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageWriteChannel.java @@ -0,0 +1,24 @@ +/* + * Copyright 2023 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; + +import com.google.api.core.ApiFuture; +import com.google.cloud.WriteChannel; + +interface StorageWriteChannel extends WriteChannel { + ApiFuture getObject(); +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnbufferedWritableByteChannelSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnbufferedWritableByteChannelSession.java index 1df74aba76..8affde6b59 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnbufferedWritableByteChannelSession.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnbufferedWritableByteChannelSession.java @@ -17,11 +17,23 @@ package com.google.cloud.storage; import com.google.cloud.storage.UnbufferedWritableByteChannelSession.UnbufferedWritableByteChannel; +import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.GatheringByteChannel; import java.nio.channels.WritableByteChannel; interface UnbufferedWritableByteChannelSession extends WritableByteChannelSession { - interface UnbufferedWritableByteChannel extends WritableByteChannel, GatheringByteChannel {} + interface UnbufferedWritableByteChannel extends WritableByteChannel, GatheringByteChannel { + @Override + default int write(ByteBuffer src) throws IOException { + return Math.toIntExact(write(new ByteBuffer[] {src})); + } + + @Override + default long write(ByteBuffer[] srcs) throws IOException { + return write(srcs, 0, srcs.length); + } + } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java index 67bafab86f..c24a68d4d6 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Utils.java @@ -37,6 +37,7 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -283,6 +284,14 @@ static void diffMaps( keys.map(NamedField::literal).map(k -> NamedField.nested(parent, k)).forEach(sink); } + static T[] subArray(T[] ts, int offset, int length) { + if (offset == 0 && length == ts.length) { + return ts; + } else { + return Arrays.copyOfRange(ts, offset, length); + } + } + private static int crc32cDecode(String from) { byte[] decodeCrc32c = BaseEncoding.base64().decode(from); return Ints.fromByteArray(decodeCrc32c); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index 100f5b6ce5..98d9476f89 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -102,11 +102,11 @@ public class HttpStorageRpc implements StorageRpc { // declare this HttpStatus code here as it's not included in java.net.HttpURLConnection private static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + private static final boolean IS_RECORD_EVENTS = true; private final StorageOptions options; private final Storage storage; private final Tracer tracer = Tracing.getTracer(); - private final CensusHttpModule censusHttpModule; private final HttpRequestInitializer batchRequestInitializer; private static final long MEGABYTE = 1024L * 1024L; @@ -123,7 +123,7 @@ public HttpStorageRpc(StorageOptions options, JsonFactory jsonFactory) { this.options = options; // Open Census initialization - censusHttpModule = new CensusHttpModule(tracer, true); + CensusHttpModule censusHttpModule = new CensusHttpModule(tracer, IS_RECORD_EVENTS); initializer = censusHttpModule.getHttpRequestInitializer(initializer); initializer = new InvocationIdInitializer(initializer); batchRequestInitializer = censusHttpModule.getHttpRequestInitializer(null); @@ -318,10 +318,7 @@ private static void setEncryptionHeaders( /** Helper method to start a span. */ private Span startSpan(String spanName) { - return tracer - .spanBuilder(spanName) - .setRecordEvents(censusHttpModule.isRecordEvents()) - .startSpan(); + return tracer.spanBuilder(spanName).setRecordEvents(IS_RECORD_EVENTS).startSpan(); } @Override diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java index 68ac4683be..4cb1ee86ae 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java @@ -40,7 +40,6 @@ import com.google.cloud.storage.Acl.User; import com.google.cloud.storage.Blob.BlobSourceOption; import com.google.cloud.storage.BlobInfo.BuilderImpl; -import com.google.cloud.storage.Storage.BlobWriteOption; import com.google.cloud.storage.Storage.CopyRequest; import com.google.cloud.storage.spi.v1.HttpStorageRpc; import com.google.cloud.storage.spi.v1.StorageRpc; @@ -387,43 +386,6 @@ public void testReaderWithDecryptionKey() throws Exception { assertSame(channel, blob.reader(BlobSourceOption.decryptionKey(KEY))); } - @Test - public void testWriter() throws Exception { - initializeExpectedBlob(); - BlobWriteChannel channel = createMock(BlobWriteChannel.class); - expect(storage.getOptions()).andReturn(mockOptions).anyTimes(); - expect(storage.writer(eq(expectedBlob))).andReturn(channel); - replay(storage); - initializeBlob(); - assertSame(channel, blob.writer()); - } - - @Test - public void testWriterWithEncryptionKey() throws Exception { - initializeExpectedBlob(); - BlobWriteChannel channel = createMock(BlobWriteChannel.class); - expect(storage.getOptions()).andReturn(mockOptions).anyTimes(); - expect(storage.writer(eq(expectedBlob), eq(BlobWriteOption.encryptionKey(BASE64_KEY)))) - .andReturn(channel) - .times(2); - replay(storage); - initializeBlob(); - assertSame(channel, blob.writer(BlobWriteOption.encryptionKey(BASE64_KEY))); - assertSame(channel, blob.writer(BlobWriteOption.encryptionKey(KEY))); - } - - @Test - public void testWriterWithKmsKeyName() throws Exception { - initializeExpectedBlob(); - BlobWriteChannel channel = createMock(BlobWriteChannel.class); - expect(storage.getOptions()).andReturn(mockOptions).anyTimes(); - expect(storage.writer(eq(expectedBlob), eq(BlobWriteOption.kmsKeyName(KMS_KEY_NAME)))) - .andReturn(channel); - replay(storage); - initializeBlob(); - assertSame(channel, blob.writer(BlobWriteOption.kmsKeyName(KMS_KEY_NAME))); - } - @Test public void testSignUrl() throws Exception { initializeExpectedBlob(); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java deleted file mode 100644 index 2898064aaa..0000000000 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java +++ /dev/null @@ -1,961 +0,0 @@ -/* - * Copyright 2015 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; - -import static org.easymock.EasyMock.anyObject; -import static org.easymock.EasyMock.capture; -import static org.easymock.EasyMock.captureLong; -import static org.easymock.EasyMock.createMock; -import static org.easymock.EasyMock.eq; -import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.expectLastCall; -import static org.easymock.EasyMock.replay; -import static org.easymock.EasyMock.verify; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import com.google.api.gax.retrying.ResultRetryAlgorithm; -import com.google.api.services.storage.model.StorageObject; -import com.google.cloud.NoCredentials; -import com.google.cloud.RestorableState; -import com.google.cloud.WriteChannel; -import com.google.cloud.storage.spi.StorageRpcFactory; -import com.google.cloud.storage.spi.v1.StorageRpc; -import com.google.common.collect.ImmutableMap; -import java.io.IOException; -import java.math.BigInteger; -import java.net.MalformedURLException; -import java.net.SocketException; -import java.net.URL; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.Random; -import org.easymock.Capture; -import org.easymock.CaptureType; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -public class BlobWriteChannelTest { - - private static final String BUCKET_NAME = "b"; - private static final String BLOB_NAME = "n"; - private static final String UPLOAD_ID = "uploadid"; - private static final BlobInfo BLOB_INFO = BlobInfo.newBuilder(BUCKET_NAME, BLOB_NAME).build(); - private static final BlobInfo BLOB_INFO_WITH_GENERATION = - BlobInfo.newBuilder(BUCKET_NAME, BLOB_NAME, 1L).build(); - private static final StorageObject UPDATED_BLOB = new StorageObject(); - private static final Map EMPTY_RPC_OPTIONS = ImmutableMap.of(); - private static final Map RPC_OPTIONS_GENERATION = - ImmutableMap.of(StorageRpc.Option.IF_GENERATION_MATCH, 1L); - private static final int MIN_CHUNK_SIZE = 256 * 1024; - private static final int DEFAULT_CHUNK_SIZE = 60 * MIN_CHUNK_SIZE; // 15MiB - private static final int CUSTOM_CHUNK_SIZE = 4 * MIN_CHUNK_SIZE; - private static final Random RANDOM = new Random(); - private static final String SIGNED_URL = - "http://www.test.com/test-bucket/test1.txt?GoogleAccessId=testClient-test@test.com&Expires=1553839761&Signature=MJUBXAZ7"; - private static final StorageException socketClosedException = - new StorageException(new SocketException("Socket closed")); - private HttpStorageOptions options; - private StorageRpcFactory rpcFactoryMock; - private StorageRpc storageRpcMock; - private BlobWriteChannel writer; - private HttpRetryAlgorithmManager retryAlgorithmManager; - - @Before - public void setUp() { - rpcFactoryMock = createMock(StorageRpcFactory.class); - storageRpcMock = createMock(StorageRpc.class); - expect(rpcFactoryMock.create(anyObject(StorageOptions.class))).andReturn(storageRpcMock); - replay(rpcFactoryMock); - options = - HttpStorageOptions.newBuilder() - .setProjectId("projectid") - .setServiceRpcFactory(rpcFactoryMock) - .setCredentials(NoCredentials.getInstance()) - .build(); - retryAlgorithmManager = options.getRetryAlgorithmManager(); - } - - @After - public void tearDown() throws Exception { - verify(rpcFactoryMock, storageRpcMock); - } - - @Test - public void testCreate() { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - replay(storageRpcMock); - writer = newWriter(); - assertTrue(writer.isOpen()); - assertNull(writer.getStorageObject()); - } - - @Test - public void testCreateRetryableError() { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andThrow(socketClosedException); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - replay(storageRpcMock); - writer = newWriter(true); - assertTrue(writer.isOpen()); - assertNull(writer.getStorageObject()); - } - - @Test - public void testCreateNonRetryableError() { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andThrow(new RuntimeException()); - replay(storageRpcMock); - try { - newWriter(); - Assert.fail(); - } catch (RuntimeException ex) { - assertNotNull(ex.getMessage()); - } - } - - @Test - public void testWriteWithoutFlush() throws Exception { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - replay(storageRpcMock); - writer = newWriter(); - assertEquals(MIN_CHUNK_SIZE, writer.write(ByteBuffer.allocate(MIN_CHUNK_SIZE))); - } - - @Test - public void testWriteWithFlushRetryChunk() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(0L); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andReturn(null); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffer)); - assertTrue(writer.isOpen()); - assertNull(writer.getStorageObject()); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - } - - @Test - public void testWriteWithRetryFullChunk() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), (byte[]) anyObject(), eq(0), eq(0L), eq(MIN_CHUNK_SIZE), eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(0L); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andReturn(null); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - (byte[]) anyObject(), - eq(0), - eq((long) MIN_CHUNK_SIZE), - eq(0), - eq(true))) - .andReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO)); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffer)); - writer.close(); - assertFalse(writer.isOpen()); - assertNotNull(writer.getStorageObject()); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - } - - @Test - public void testWriteWithRemoteProgressMade() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andThrow(socketClosedException); - // Simulate GCS received 10 bytes but not the rest of the chunk - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(10L); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(10), - eq(10L), - eq(MIN_CHUNK_SIZE - 10), - eq(false))) - .andReturn(null); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffer)); - assertTrue(writer.isOpen()); - assertNull(writer.getStorageObject()); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - } - - @Test - public void testWriteWithDriftRetryCase4() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn((long) MIN_CHUNK_SIZE); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq((long) MIN_CHUNK_SIZE), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andReturn(null); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffer)); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - capturedBuffer.reset(); - buffer.rewind(); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffer)); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - assertTrue(writer.isOpen()); - assertNull(writer.getStorageObject()); - } - - @Test - public void testWriteWithUnreachableRemoteOffset() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(MIN_CHUNK_SIZE + 10L); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - try { - writer.write(buffer); - fail("Expected StorageException"); - } catch (StorageException storageException) { - // expected storageException - } - assertTrue(writer.isOpen()); - assertNull(writer.getStorageObject()); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - } - - @Test - public void testWriteWithRetryAndObjectMetadata() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(10L); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(10), - eq(10L), - eq(MIN_CHUNK_SIZE - 10), - eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L); - expect(storageRpcMock.queryCompletedResumableUpload(eq(UPLOAD_ID), eq((long) MIN_CHUNK_SIZE))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L); - expect(storageRpcMock.queryCompletedResumableUpload(eq(UPLOAD_ID), eq((long) MIN_CHUNK_SIZE))) - .andReturn( - Conversions.apiary() - .blobInfo() - .encode(BLOB_INFO) - .setSize(BigInteger.valueOf(MIN_CHUNK_SIZE))); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffer)); - writer.close(); - assertFalse(writer.isOpen()); - assertNotNull(writer.getStorageObject()); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - } - - @Test - public void testWriteWithUploadCompletedByAnotherClient() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andReturn(null); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq((long) MIN_CHUNK_SIZE), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - try { - writer.write(buffer); - buffer.rewind(); - writer.write(buffer); - buffer.rewind(); - writer.write(buffer); - fail("Expected completed exception."); - } catch (StorageException ex) { - - } - assertTrue(writer.isOpen()); - } - - @Test - public void testWriteWithLocalOffsetGoingBeyondRemoteOffset() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andReturn(null); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq((long) MIN_CHUNK_SIZE), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(0L); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - try { - writer.write(buffer); - buffer.rewind(); - writer.write(buffer); - writer.close(); - fail("Expected completed exception."); - } catch (StorageException ex) { - } - assertTrue(writer.isOpen()); - } - - @Test - public void testGetCurrentUploadOffset() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(0L); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(false))) - .andReturn(null); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - (byte[]) anyObject(), - eq(0), - eq((long) MIN_CHUNK_SIZE), - eq(0), - eq(true))) - .andReturn(Conversions.apiary().blobInfo().encode(BLOB_INFO)); - replay(storageRpcMock); - writer = newWriter(true); - writer.setChunkSize(MIN_CHUNK_SIZE); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffer)); - writer.close(); - assertFalse(writer.isOpen()); - assertNotNull(writer.getStorageObject()); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - } - - @Test - public void testWriteWithLastFlushRetryChunkButCompleted() throws Exception { - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO_WITH_GENERATION), - RPC_OPTIONS_GENERATION)) - .andReturn(UPLOAD_ID); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(true))) - .andThrow(socketClosedException); - expect(storageRpcMock.getCurrentUploadOffset(eq(UPLOAD_ID))).andReturn(-1L); - expect(storageRpcMock.queryCompletedResumableUpload(eq(UPLOAD_ID), eq((long) MIN_CHUNK_SIZE))) - .andReturn( - Conversions.apiary() - .blobInfo() - .encode(BLOB_INFO) - .setSize(BigInteger.valueOf(MIN_CHUNK_SIZE))); - replay(storageRpcMock); - writer = newWriter(true); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffer)); - writer.close(); - assertFalse(writer.isRetrying()); - assertFalse(writer.isOpen()); - assertNotNull(writer.getStorageObject()); - // Capture captures entire buffer of a chunk even when not completely used. - // Making assert selective up to the size of MIN_CHUNK_SIZE - assertArrayEquals(Arrays.copyOf(capturedBuffer.getValue(), MIN_CHUNK_SIZE), buffer.array()); - } - - @Test - public void testWriteWithFlush() throws Exception { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(CUSTOM_CHUNK_SIZE), - eq(false))) - .andReturn(null); - replay(storageRpcMock); - writer = newWriter(); - writer.setChunkSize(CUSTOM_CHUNK_SIZE); - ByteBuffer buffer = randomBuffer(CUSTOM_CHUNK_SIZE); - assertEquals(CUSTOM_CHUNK_SIZE, writer.write(buffer)); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - assertNull(writer.getStorageObject()); - } - - @Test - public void testWritesAndFlush() throws Exception { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(DEFAULT_CHUNK_SIZE), - eq(false))) - .andReturn(null); - replay(storageRpcMock); - writer = newWriter(); - ByteBuffer[] buffers = new ByteBuffer[DEFAULT_CHUNK_SIZE / MIN_CHUNK_SIZE]; - for (int i = 0; i < buffers.length; i++) { - buffers[i] = randomBuffer(MIN_CHUNK_SIZE); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffers[i])); - assertNull(writer.getStorageObject()); - } - for (int i = 0; i < buffers.length; i++) { - assertArrayEquals( - buffers[i].array(), - Arrays.copyOfRange( - capturedBuffer.getValue(), MIN_CHUNK_SIZE * i, MIN_CHUNK_SIZE * (i + 1))); - } - } - - @Test - public void testCloseWithoutFlush() throws Exception { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(0), eq(true))) - .andReturn(UPDATED_BLOB); - replay(storageRpcMock); - writer = newWriter(); - assertTrue(writer.isOpen()); - writer.close(); - assertArrayEquals(new byte[0], capturedBuffer.getValue()); - assertFalse(writer.isOpen()); - assertSame(UPDATED_BLOB, writer.getStorageObject()); - } - - @Test - public void testCloseWithFlush() throws Exception { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(true))) - .andReturn(UPDATED_BLOB); - replay(storageRpcMock); - writer = newWriter(); - assertTrue(writer.isOpen()); - writer.write(buffer); - writer.close(); - assertEquals(DEFAULT_CHUNK_SIZE, capturedBuffer.getValue().length); - assertArrayEquals(buffer.array(), Arrays.copyOf(capturedBuffer.getValue(), MIN_CHUNK_SIZE)); - assertFalse(writer.isOpen()); - assertSame(UPDATED_BLOB, writer.getStorageObject()); - } - - @Test - public void testWriteClosed() throws Exception { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(0), eq(true))) - .andReturn(UPDATED_BLOB); - replay(storageRpcMock); - writer = newWriter(); - writer.close(); - try { - writer.write(ByteBuffer.allocate(MIN_CHUNK_SIZE)); - fail("Expected BlobWriteChannel write to throw IOException"); - } catch (IOException ex) { - // expected - } - assertSame(UPDATED_BLOB, writer.getStorageObject()); - } - - @Test - public void testSaveAndRestore() throws Exception { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(CaptureType.ALL); - Capture capturedPosition = Capture.newInstance(CaptureType.ALL); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - captureLong(capturedPosition), - eq(DEFAULT_CHUNK_SIZE), - eq(false))) - .andReturn(null); - expectLastCall().times(2); - replay(storageRpcMock); - ByteBuffer buffer1 = randomBuffer(DEFAULT_CHUNK_SIZE); - ByteBuffer buffer2 = randomBuffer(DEFAULT_CHUNK_SIZE); - writer = newWriter(); - assertEquals(DEFAULT_CHUNK_SIZE, writer.write(buffer1)); - assertArrayEquals(buffer1.array(), capturedBuffer.getValues().get(0)); - assertEquals(new Long(0L), capturedPosition.getValues().get(0)); - RestorableState writerState = writer.capture(); - WriteChannel restoredWriter = writerState.restore(); - assertEquals(DEFAULT_CHUNK_SIZE, restoredWriter.write(buffer2)); - assertArrayEquals(buffer2.array(), capturedBuffer.getValues().get(1)); - assertEquals(new Long(DEFAULT_CHUNK_SIZE), capturedPosition.getValues().get(1)); - } - - @Test - public void testSaveAndRestoreClosed() throws Exception { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(0), eq(true))) - .andReturn(UPDATED_BLOB); - replay(storageRpcMock); - writer = newWriter(); - writer.close(); - RestorableState writerState = writer.capture(); - RestorableState expectedWriterState = - BlobWriteChannel.StateImpl.builder(options, BLOB_INFO, UPLOAD_ID) - .setBuffer(null) - .setChunkSize(DEFAULT_CHUNK_SIZE) - .setIsOpen(false) - .setPosition(0) - .build(); - WriteChannel restoredWriter = writerState.restore(); - assertArrayEquals(new byte[0], capturedBuffer.getValue()); - assertEquals(expectedWriterState, restoredWriter.capture()); - } - - @Test - public void testStateEquals() { - expect( - storageRpcMock.open( - Conversions.apiary().blobInfo().encode(BLOB_INFO), EMPTY_RPC_OPTIONS)) - .andReturn(UPLOAD_ID) - .times(2); - replay(storageRpcMock); - writer = newWriter(); - // avoid closing when you don't want partial writes to GCS upon failure - @SuppressWarnings("resource") - WriteChannel writer2 = newWriter(); - RestorableState state = writer.capture(); - RestorableState state2 = writer2.capture(); - assertEquals(state, state2); - assertEquals(state.hashCode(), state2.hashCode()); - assertEquals(state.toString(), state2.toString()); - } - - @Test - public void testWriteWithSignedURLAndWithFlush() throws Exception { - expect(storageRpcMock.open(SIGNED_URL)).andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(CUSTOM_CHUNK_SIZE), - eq(false))) - .andReturn(null); - replay(storageRpcMock); - writer = newWriterForSignedUrl(); - writer.setChunkSize(CUSTOM_CHUNK_SIZE); - ByteBuffer buffer = randomBuffer(CUSTOM_CHUNK_SIZE); - assertEquals(CUSTOM_CHUNK_SIZE, writer.write(buffer)); - assertArrayEquals(buffer.array(), capturedBuffer.getValue()); - } - - @Test - public void testWriteWithSignedURLAndFlush() throws Exception { - expect(storageRpcMock.open(SIGNED_URL)).andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(DEFAULT_CHUNK_SIZE), - eq(false))) - .andReturn(null); - replay(storageRpcMock); - writer = newWriterForSignedUrl(); - ByteBuffer[] buffers = new ByteBuffer[DEFAULT_CHUNK_SIZE / MIN_CHUNK_SIZE]; - for (int i = 0; i < buffers.length; i++) { - buffers[i] = randomBuffer(MIN_CHUNK_SIZE); - assertEquals(MIN_CHUNK_SIZE, writer.write(buffers[i])); - } - for (int i = 0; i < buffers.length; i++) { - assertArrayEquals( - buffers[i].array(), - Arrays.copyOfRange( - capturedBuffer.getValue(), MIN_CHUNK_SIZE * i, MIN_CHUNK_SIZE * (i + 1))); - } - } - - @Test - public void testCloseWithSignedURLWithoutFlush() throws Exception { - expect(storageRpcMock.open(SIGNED_URL)).andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(0), eq(true))) - .andReturn(UPDATED_BLOB); - replay(storageRpcMock); - writer = newWriterForSignedUrl(); - assertTrue(writer.isOpen()); - writer.close(); - assertArrayEquals(new byte[0], capturedBuffer.getValue()); - assertTrue(!writer.isOpen()); - } - - @Test - public void testCloseWithSignedURLWithFlush() throws Exception { - expect(storageRpcMock.open(SIGNED_URL)).andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - ByteBuffer buffer = randomBuffer(MIN_CHUNK_SIZE); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - eq(0L), - eq(MIN_CHUNK_SIZE), - eq(true))) - .andReturn(UPDATED_BLOB); - replay(storageRpcMock); - writer = newWriterForSignedUrl(); - assertTrue(writer.isOpen()); - writer.write(buffer); - writer.close(); - assertEquals(DEFAULT_CHUNK_SIZE, capturedBuffer.getValue().length); - assertArrayEquals(buffer.array(), Arrays.copyOf(capturedBuffer.getValue(), MIN_CHUNK_SIZE)); - assertTrue(!writer.isOpen()); - } - - @Test - public void testWriteWithSignedURLClosed() throws Exception { - expect(storageRpcMock.open(SIGNED_URL)).andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), capture(capturedBuffer), eq(0), eq(0L), eq(0), eq(true))) - .andReturn(UPDATED_BLOB); - replay(storageRpcMock); - writer = newWriterForSignedUrl(); - writer.close(); - try { - writer.write(ByteBuffer.allocate(MIN_CHUNK_SIZE)); - fail("Expected BlobWriteChannel write to throw IOException"); - } catch (IOException ex) { - // expected - } - } - - @Test - public void testSaveAndRestoreWithSignedURL() throws Exception { - expect(storageRpcMock.open(SIGNED_URL)).andReturn(UPLOAD_ID); - Capture capturedBuffer = Capture.newInstance(CaptureType.ALL); - Capture capturedPosition = Capture.newInstance(CaptureType.ALL); - expect( - storageRpcMock.writeWithResponse( - eq(UPLOAD_ID), - capture(capturedBuffer), - eq(0), - captureLong(capturedPosition), - eq(DEFAULT_CHUNK_SIZE), - eq(false))) - .andReturn(null); - expectLastCall().times(2); - replay(storageRpcMock); - ByteBuffer buffer1 = randomBuffer(DEFAULT_CHUNK_SIZE); - ByteBuffer buffer2 = randomBuffer(DEFAULT_CHUNK_SIZE); - writer = newWriterForSignedUrl(); - assertEquals(DEFAULT_CHUNK_SIZE, writer.write(buffer1)); - assertArrayEquals(buffer1.array(), capturedBuffer.getValues().get(0)); - assertEquals(new Long(0L), capturedPosition.getValues().get(0)); - RestorableState writerState = writer.capture(); - WriteChannel restoredWriter = writerState.restore(); - assertEquals(DEFAULT_CHUNK_SIZE, restoredWriter.write(buffer2)); - assertArrayEquals(buffer2.array(), capturedBuffer.getValues().get(1)); - assertEquals(new Long(DEFAULT_CHUNK_SIZE), capturedPosition.getValues().get(1)); - } - - private BlobWriteChannel newWriter() { - return newWriter(false); - } - - private BlobWriteChannel newWriter(boolean withGeneration) { - Map optionsMap = - withGeneration ? RPC_OPTIONS_GENERATION : EMPTY_RPC_OPTIONS; - ResultRetryAlgorithm createResultAlgorithm = - retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap); - ResultRetryAlgorithm writeResultAlgorithm = - retryAlgorithmManager.getForResumableUploadSessionWrite(optionsMap); - final BlobInfo blobInfo = withGeneration ? BLOB_INFO_WITH_GENERATION : BLOB_INFO; - return BlobWriteChannel.newBuilder() - .setStorageOptions(options) - .setBlobInfo(blobInfo) - .setUploadIdSupplier( - ResumableMedia.startUploadForBlobInfo( - options, blobInfo, optionsMap, createResultAlgorithm)) - .setAlgorithmForWrite(writeResultAlgorithm) - .build(); - } - - private BlobWriteChannel newWriterForSignedUrl() throws MalformedURLException { - Map optionsMap = Collections.emptyMap(); - ResultRetryAlgorithm createResultAlgorithm = - retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap); - ResultRetryAlgorithm writeResultAlgorithm = - retryAlgorithmManager.getForResumableUploadSessionWrite(optionsMap); - return BlobWriteChannel.newBuilder() - .setStorageOptions(options) - .setUploadIdSupplier( - ResumableMedia.startUploadForSignedUrl( - options, new URL(SIGNED_URL), createResultAlgorithm)) - .setAlgorithmForWrite(writeResultAlgorithm) - .build(); - } - - private static ByteBuffer randomBuffer(int size) { - byte[] byteArray = new byte[size]; - RANDOM.nextBytes(byteArray); - return ByteBuffer.wrap(byteArray); - } -} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java index 18704dc288..7ae68773d5 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java @@ -811,4 +811,28 @@ public void jsonDeserializationOnlyAttemptedWhenContentPresent() throws Exceptio assertThat(operationResult.getPersistedSize()).isEqualTo(0L); } } + + @Test + public void attemptToRewindOutOfBoundsThrows_lower() { + RewindableHttpContent content = RewindableHttpContent.of(); + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + null, null, content, HttpContentRange.of(ByteRangeSpec.relativeLength(10L, 10L))); + + IllegalArgumentException iae = + assertThrows(IllegalArgumentException.class, () -> task.rewindTo(9)); + assertThat(iae).hasMessageThat().isEqualTo("Rewind offset is out of bounds. (10 <= 9 < 20)"); + } + + @Test + public void attemptToRewindOutOfBoundsThrows_upper() { + RewindableHttpContent content = RewindableHttpContent.of(); + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + null, null, content, HttpContentRange.of(ByteRangeSpec.relativeLength(10L, 10L))); + + IllegalArgumentException iae = + assertThrows(IllegalArgumentException.class, () -> task.rewindTo(20)); + assertThat(iae).hasMessageThat().isEqualTo("Rewind offset is out of bounds. (10 <= 20 < 20)"); + } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java index a71f5cf493..1cc917e345 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java @@ -16,11 +16,15 @@ package com.google.cloud.storage; +import static com.google.cloud.storage.ByteSizeConstants._256KiB; import static com.google.cloud.storage.ByteSizeConstants._256KiBL; +import static com.google.cloud.storage.ByteSizeConstants._512KiB; import static com.google.cloud.storage.ByteSizeConstants._512KiBL; +import static com.google.cloud.storage.ByteSizeConstants._768KiBL; import static com.google.common.truth.Truth.assertThat; import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.CONTENT_RANGE; import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaderNames.RANGE; +import static io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus.SERVICE_UNAVAILABLE; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonObjectParser; @@ -38,10 +42,12 @@ import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpRequest; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus; import java.net.URI; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Before; @@ -54,8 +60,6 @@ public final class ITJsonResumableSessionTest { private static final NetHttpTransport transport = new NetHttpTransport.Builder().build(); private static final HttpResponseStatus RESUME_INCOMPLETE = HttpResponseStatus.valueOf(308, "Resume Incomplete"); - private static final HttpResponseStatus APPEND_GREATER_THAN_CURRENT_SIZE = - HttpResponseStatus.valueOf(503, ""); private static final RetryingDependencies RETRYING_DEPENDENCIES = new RetryingDependencies() { @Override @@ -91,7 +95,6 @@ public void rewindWillQueryStatusOnlyWhenDirty() throws Exception { req -> { requests.add(req); String contentRange = req.headers().get(CONTENT_RANGE); - System.out.println("contentRange = " + contentRange); DefaultFullHttpResponse resp = new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); if (range1.getHeaderValue().equals(contentRange)) { @@ -122,7 +125,6 @@ public void rewindWillQueryStatusOnlyWhenDirty() throws Exception { assertThat(operationResult.getPersistedSize()).isEqualTo(_512KiBL); } - assertThat(requests).hasSize(3); List actual = requests.stream().map(r -> r.headers().get(CONTENT_RANGE)).collect(Collectors.toList()); @@ -131,4 +133,135 @@ public void rewindWillQueryStatusOnlyWhenDirty() throws Exception { assertThat(actual).isEqualTo(expected); } + + @Test + public void retryAttemptWillReturnQueryResultIfPersistedSizeMatchesSpecifiedEndOffset() + throws Exception { + HttpContentRange range1 = HttpContentRange.of(ByteRangeSpec.explicit(0L, _512KiBL)); + HttpContentRange range2 = HttpContentRange.query(); + HttpContentRange range3 = HttpContentRange.of(ByteRangeSpec.explicit(_512KiBL, _768KiBL)); + + final List requests = Collections.synchronizedList(new ArrayList<>()); + HttpRequestHandler handler = + req -> { + requests.add(req); + String contentRange = req.headers().get(CONTENT_RANGE); + DefaultFullHttpResponse resp; + if (range1.getHeaderValue().equals(contentRange)) { + resp = new DefaultFullHttpResponse(req.protocolVersion(), SERVICE_UNAVAILABLE); + } else if (range2.getHeaderValue().equals(contentRange)) { + resp = new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + resp.headers().set(RANGE, ByteRangeSpec.explicit(0L, _512KiBL).getHttpRangeHeader()); + } else { + resp = new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + resp.headers() + .set(RANGE, ByteRangeSpec.explicit(_512KiBL, _768KiBL).getHttpRangeHeader()); + } + return resp; + }; + + ByteBuffer buf1 = DataGenerator.base64Characters().genByteBuffer(_512KiB); + ByteBuffer buf2 = DataGenerator.base64Characters().genByteBuffer(_256KiB); + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableWrite resumableWrite = JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl); + JsonResumableSession session = + new JsonResumableSession( + httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); + + ResumableOperationResult<@Nullable StorageObject> operationResult1 = + session.put(RewindableHttpContent.of(buf1), range1); + StorageObject call1 = operationResult1.getObject(); + assertThat(call1).isNull(); + assertThat(operationResult1.getPersistedSize()).isEqualTo(_512KiBL); + + ResumableOperationResult<@Nullable StorageObject> operationResult2 = + session.put(RewindableHttpContent.of(buf2), range3); + StorageObject call2 = operationResult2.getObject(); + assertThat(call2).isNull(); + assertThat(operationResult2.getPersistedSize()).isEqualTo(_768KiBL); + } + + List actual = + requests.stream().map(r -> r.headers().get(CONTENT_RANGE)).collect(Collectors.toList()); + + List expected = + ImmutableList.of(range1.getHeaderValue(), range2.getHeaderValue(), range3.getHeaderValue()); + + assertThat(actual).isEqualTo(expected); + } + + @Test + public void rewindOfContentIsRelativeToItsBeginOffsetOfTheOverallObject() throws Exception { + HttpContentRange range1 = HttpContentRange.of(ByteRangeSpec.explicit(0L, _512KiBL)); + HttpContentRange range2 = HttpContentRange.of(ByteRangeSpec.explicit(_512KiBL, _768KiBL)); + HttpContentRange range3 = HttpContentRange.query(); + + final AtomicBoolean fail = new AtomicBoolean(true); + final List requests = Collections.synchronizedList(new ArrayList<>()); + HttpRequestHandler handler = + req -> { + requests.add(req); + String contentRange = req.headers().get(CONTENT_RANGE); + DefaultFullHttpResponse resp; + if (range1.getHeaderValue().equals(contentRange) + || range3.getHeaderValue().equals(contentRange)) { + resp = new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + resp.headers().set(RANGE, ByteRangeSpec.explicit(0L, _512KiBL).getHttpRangeHeader()); + } else if (range2.getHeaderValue().equals(contentRange)) { + if (fail.getAndSet(false)) { + resp = new DefaultFullHttpResponse(req.protocolVersion(), SERVICE_UNAVAILABLE); + } else { + resp = new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + resp.headers() + .set(RANGE, ByteRangeSpec.explicit(_512KiBL, _768KiBL).getHttpRangeHeader()); + } + } else { + resp = new DefaultFullHttpResponse(req.protocolVersion(), RESUME_INCOMPLETE); + resp.headers() + .set(RANGE, ByteRangeSpec.explicit(_512KiBL, _768KiBL).getHttpRangeHeader()); + } + return resp; + }; + + ByteBuffer buf1 = DataGenerator.base64Characters().genByteBuffer(_512KiB); + ByteBuffer buf2 = DataGenerator.base64Characters().genByteBuffer(_256KiB); + + try (FakeHttpServer fakeHttpServer = FakeHttpServer.of(handler)) { + URI endpoint = fakeHttpServer.getEndpoint(); + String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); + + JsonResumableWrite resumableWrite = JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl); + JsonResumableSession session = + new JsonResumableSession( + httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); + + ResumableOperationResult<@Nullable StorageObject> operationResult1 = + session.put(RewindableHttpContent.of(buf1), range1); + StorageObject call1 = operationResult1.getObject(); + assertThat(call1).isNull(); + assertThat(operationResult1.getPersistedSize()).isEqualTo(_512KiBL); + + ResumableOperationResult<@Nullable StorageObject> operationResult2 = + session.put(RewindableHttpContent.of(buf2), range2); + StorageObject call2 = operationResult2.getObject(); + assertThat(call2).isNull(); + assertThat(operationResult2.getPersistedSize()).isEqualTo(_768KiBL); + } + + List actual = + requests.stream().map(r -> r.headers().get(CONTENT_RANGE)).collect(Collectors.toList()); + + List expected = + ImmutableList.of( + range1.getHeaderValue(), + range2.getHeaderValue(), + range3.getHeaderValue(), + range2.getHeaderValue()); + + assertThat(actual).isEqualTo(expected); + } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java index 5f23a5aa19..7f5c7c7ac7 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/JsonResumableSessionFailureScenarioTest.java @@ -72,7 +72,7 @@ public void toStorageException_ioExceptionDuringContentResolutionAddedAsSuppress throw new Kaboom(); }); - assertThat(storageException.getCode()).isEqualTo(400); + assertThat(storageException.getCode()).isEqualTo(0); assertThat(storageException).hasCauseThat().isInstanceOf(Cause.class); assertThat(storageException.getSuppressed()).isNotEmpty(); assertThat(storageException.getSuppressed()[0]).isInstanceOf(StorageException.class); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/PackagePrivateMethodWorkarounds.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/PackagePrivateMethodWorkarounds.java index da5f5310f1..d6c5ad0afc 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/PackagePrivateMethodWorkarounds.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/PackagePrivateMethodWorkarounds.java @@ -22,7 +22,6 @@ import com.google.cloud.WriteChannel; import com.google.cloud.storage.BucketInfo.BuilderImpl; import com.google.common.collect.ImmutableList; -import com.google.storage.v2.WriteObjectResponse; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; @@ -62,14 +61,13 @@ public static Blob blobCopyWithStorage(Blob b, Storage s) { public static Function> maybeGetBlobInfoFunction() { return (w) -> { - BlobWriteChannel blobWriteChannel; - if (w instanceof BlobWriteChannel) { - blobWriteChannel = (BlobWriteChannel) w; - return Optional.of(blobWriteChannel.getStorageObject()) + if (w instanceof BlobWriteChannelV2) { + BlobWriteChannelV2 blobWriteChannel = (BlobWriteChannelV2) w; + return Optional.ofNullable(blobWriteChannel.getResolvedObject()) .map(Conversions.apiary().blobInfo()::decode); } else if (w instanceof GrpcBlobWriteChannel) { GrpcBlobWriteChannel grpcBlobWriteChannel = (GrpcBlobWriteChannel) w; - return Optional.of(grpcBlobWriteChannel.getResults()) + return Optional.of(grpcBlobWriteChannel.getObject()) .map( f -> { try { @@ -77,9 +75,7 @@ public static Function> maybeGetBlobInfoFunctio } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } - }) - .map(WriteObjectResponse::getResource) - .map(Conversions.grpc().blobInfo()::decode); + }); } else { return Optional.empty(); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableByteBufferContentTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableByteBufferContentTest.java new file mode 100644 index 0000000000..9d65a00dcb --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableByteBufferContentTest.java @@ -0,0 +1,160 @@ +/* + * Copyright 2023 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; + +import static com.google.cloud.storage.TestUtils.assertAll; +import static com.google.cloud.storage.TestUtils.xxd; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.cloud.storage.RewindableHttpContentPropertyTest.ErroringOutputStream; +import com.google.protobuf.ByteString; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import org.junit.Before; +import org.junit.Test; + +public final class RewindableByteBufferContentTest { + + private long total; + private ByteBuffer[] buffers; + private String fullXxd; + + @Before + public void setUp() throws Exception { + // full buffer + ByteBuffer bufFull = DataGenerator.base64Characters().genByteBuffer(16); + // limited buffer + ByteBuffer bufLimit = DataGenerator.base64Characters().genByteBuffer(16); + bufLimit.limit(15); + // offset buffer + ByteBuffer bufOffset = DataGenerator.base64Characters().genByteBuffer(16); + bufOffset.position(3); + // offset and limited buffer + ByteBuffer bufLimitAndOffset = DataGenerator.base64Characters().genByteBuffer(16); + bufLimitAndOffset.position(9).limit(12); + + total = + bufFull.remaining() + + bufLimit.remaining() + + bufOffset.remaining() + + bufLimitAndOffset.remaining(); + buffers = new ByteBuffer[] {bufFull, bufLimit, bufOffset, bufLimitAndOffset}; + fullXxd = xxd(false, buffers); + } + + @Test + public void getLength() { + RewindableHttpContent content = RewindableHttpContent.of(buffers); + + assertThat(content.getLength()).isEqualTo(total); + } + + @Test + public void writeTo() throws IOException { + + RewindableHttpContent content = RewindableHttpContent.of(buffers); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + content.writeTo(baos); + + String actual = xxd(baos.toByteArray()); + assertThat(actual).isEqualTo(fullXxd); + } + + @Test + public void rewind() throws IOException { + + RewindableHttpContent content = RewindableHttpContent.of(buffers); + + assertThrows( + IOException.class, + () -> { + try (ErroringOutputStream erroringOutputStream = new ErroringOutputStream(25)) { + content.writeTo(erroringOutputStream); + } + }); + content.rewindTo(0L); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + content.writeTo(baos); + + String actual = xxd(baos.toByteArray()); + assertThat(actual).isEqualTo(fullXxd); + } + + @Test + public void rewindTo() throws Exception { + RewindableHttpContent content = RewindableHttpContent.of(buffers); + + ByteString reduce = + Arrays.stream(buffers) + .map(ByteBuffer::duplicate) + .map(ByteStringStrategy.noCopy()) + .reduce(ByteString.empty(), ByteString::concat, (l, r) -> r); + + assertThat(content.getLength()).isEqualTo(total); + + int readOffset = 37; + ByteString substring = reduce.substring(readOffset); + ByteBuffer readOnlyByteBuffer = substring.asReadOnlyByteBuffer(); + String expected = xxd(false, readOnlyByteBuffer); + long value = total - readOffset; + content.rewindTo(readOffset); + assertThat(content.getLength()).isEqualTo(value); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + content.writeTo(baos); + + String actual = xxd(baos.toByteArray()); + assertAll( + () -> assertThat(baos.toByteArray()).hasLength(Math.toIntExact(value)), + () -> assertThat(actual).isEqualTo(expected)); + } + + @Test + public void rewind_dirtyAware() throws IOException { + + ByteBuffer buf = DataGenerator.base64Characters().genByteBuffer(10); + buf.position(3).limit(7); + + int position = buf.position(); + int limit = buf.limit(); + + RewindableHttpContent content = RewindableHttpContent.of(buf); + int hackPosition = 2; + // after content has initialized, mutate the position underneath it. We're doing this to detect + // if rewind is actually modifying things. It shouldn't until the content is dirtied by calling + // writeTo + buf.position(hackPosition); + + // invoke rewind, and expect it to not do anything + content.rewindTo(0L); + assertThat(buf.position()).isEqualTo(hackPosition); + assertThat(buf.limit()).isEqualTo(limit); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + content.writeTo(baos); + + assertThat(buf.position()).isEqualTo(limit); + assertThat(buf.limit()).isEqualTo(limit); + + content.rewindTo(0L); + assertThat(buf.position()).isEqualTo(position); + assertThat(buf.limit()).isEqualTo(limit); + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java index 5d23537771..f8e18f2866 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertThrows; import com.google.common.base.MoreObjects; +import com.google.protobuf.ByteString; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -30,6 +31,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.stream.Collectors; import net.jqwik.api.Arbitraries; import net.jqwik.api.Arbitrary; import net.jqwik.api.Combinators; @@ -64,6 +66,29 @@ void path(@ForAll("PathScenario") PathScenario pathScenario) throws Exception { } } + @Property + void byteBuffers(@ForAll("ByteBuffersScenario") ByteBuffersScenario s) throws IOException { + RewindableHttpContent content = RewindableHttpContent.of(s.getBuffers()); + assertThat(content.getLength()).isEqualTo(s.getFullLength()); + assertThrows( + IOException.class, + () -> { + try (ErroringOutputStream erroringOutputStream = + new ErroringOutputStream(s.getErrorAtOffset())) { + content.writeTo(erroringOutputStream); + } + }); + content.rewindTo(s.getRewindOffset()); + assertThat(content.getLength()).isEqualTo(s.getPostRewindLength()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + content.writeTo(baos); + + String actual = xxd(baos.toByteArray()); + + assertThat(actual).isEqualTo(s.getExpectedXxd()); + } + @Provide("PathScenario") static Arbitrary pathScenario() { return Arbitraries.lazyOf( @@ -85,6 +110,30 @@ static Arbitrary pathScenario() { .as(PathScenario::of))); } + @Provide("ByteBuffersScenario") + static Arbitrary byteBuffersScenarioArbitrary() { + return Arbitraries.lazyOf( + () -> + Arbitraries.oneOf( + byteBuffers(1, 10), + byteBuffers(10, 100), + byteBuffers(100, 1_000), + byteBuffers(1_000, 10_000), + byteBuffers(10_000, 100_000), + byteBuffers(100_000, 1_000_000))) + .flatMap( + buffers -> { + long totalAvailable = Arrays.stream(buffers).mapToLong(ByteBuffer::remaining).sum(); + + return Combinators.combine( + Arbitraries.longs().between(0, Math.max(0L, totalAvailable - 1)), + Arbitraries.longs().between(0, Math.max(0L, totalAvailable - 1)), + Arbitraries.just(buffers)) + .as(ByteBuffersScenario::of); + }) + .filter(bbs -> bbs.getFullLength() > 0); + } + @NonNull private static Arbitrary bytes(int minFileSize, int maxFileSize) { return Arbitraries.integers() @@ -93,6 +142,43 @@ private static Arbitrary bytes(int minFileSize, int maxFileSize) { .map(DataGenerator.base64Characters()::genBytes); } + @NonNull + private static Arbitrary byteBuffers(int perBufferMinSize, int perBufferMaxSize) { + return byteBuffer(perBufferMinSize, perBufferMaxSize) + .array(ByteBuffer[].class) + .ofMinSize(1) + .ofMaxSize(10); + } + + /** + * Generate a ByteBuffer with size between minSize, maxSize with a random position and random + * limit + */ + @NonNull + private static Arbitrary byteBuffer(int minSize, int maxSize) { + return Arbitraries.integers() + .between(minSize, maxSize) + .withDistribution(RandomDistribution.uniform()) + .withoutEdgeCases() + .map(DataGenerator.base64Characters()::genByteBuffer) + .flatMap( + buf -> + Arbitraries.integers() + .between(0, Math.max(0, buf.capacity() - 1)) + .withoutEdgeCases() + .flatMap( + limit -> + Arbitraries.integers() + .between(0, limit) + .withoutEdgeCases() + .flatMap( + position -> { + buf.limit(limit); + buf.position(position); + return Arbitraries.of(buf); + }))); + } + private static final class PathScenario implements AutoCloseable { private static final Path TMP_DIR = Paths.get(System.getProperty("java.io.tmpdir")); @@ -163,6 +249,81 @@ private static PathScenario of(int rewindOffset, int errorAtOffset, byte[] bytes } } + private static class ByteBuffersScenario { + + private final long rewindOffset; + private final long errorAtOffset; + private final ByteBuffer[] buffers; + private final long fullLength; + private final String expectedXxd; + + private ByteBuffersScenario( + long rewindOffset, + long errorAtOffset, + ByteBuffer[] buffers, + byte[] expectedBytes, + long fullLength) { + this.rewindOffset = rewindOffset; + this.errorAtOffset = errorAtOffset; + this.buffers = buffers; + this.fullLength = fullLength; + this.expectedXxd = xxd(expectedBytes); + } + + public long getRewindOffset() { + return rewindOffset; + } + + public long getErrorAtOffset() { + return errorAtOffset; + } + + public ByteBuffer[] getBuffers() { + // duplicate the buffer so we have stable toString + return Arrays.stream(buffers).map(ByteBuffer::duplicate).toArray(ByteBuffer[]::new); + } + + public String getExpectedXxd() { + return expectedXxd; + } + + public long getFullLength() { + return fullLength; + } + + public long getPostRewindLength() { + return fullLength - rewindOffset; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("\nexpectedXxd", "\n" + expectedXxd) + .add( + "\nbuffers", + Arrays.stream(buffers) + .map(Object::toString) + .collect(Collectors.joining("\n\t", "[\n\t", "\n]"))) + .add("\nrewindOffset", rewindOffset) + .add("\nerrorAtOffset", errorAtOffset) + .toString(); + } + + public static ByteBuffersScenario of( + long rewindOffset, long errorAtOffset, ByteBuffer[] buffers) { + + ByteString reduce = + Arrays.stream(buffers) + .map(ByteBuffer::duplicate) + .map(ByteStringStrategy.noCopy()) + .reduce(ByteString.empty(), ByteString::concat, (l, r) -> r); + + byte[] byteArray = reduce.substring(Math.toIntExact(rewindOffset)).toByteArray(); + return new ByteBuffersScenario( + rewindOffset, errorAtOffset, buffers, byteArray, reduce.size()); + } + } + static final class ErroringOutputStream extends OutputStream { private final long errorAt; private long totalWritten; @@ -189,7 +350,8 @@ public void write(byte[] b) throws IOException { } @Override - public void write(byte[] b, int off, int len) throws IOException { + public void write(@SuppressWarnings("NullableProblems") byte[] b, int off, int len) + throws IOException { int diff = len - off; if (totalWritten + diff >= errorAt) { throw new IOException("Reached errorAt limit"); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java index 9fc4b16f7c..1e5eda2d44 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; -import com.google.api.gax.retrying.ResultRetryAlgorithm; import com.google.api.services.storage.model.StorageObject; import com.google.cloud.BaseSerializationTest; import com.google.cloud.NoCredentials; @@ -28,10 +27,11 @@ import com.google.cloud.ReadChannel; import com.google.cloud.Restorable; import com.google.cloud.RestorableState; +import com.google.cloud.WriteChannel; import com.google.cloud.storage.Acl.Project.ProjectRole; -import com.google.cloud.storage.BlobReadChannel.StateImpl; import com.google.cloud.storage.BlobReadChannelV2.BlobReadChannelContext; import com.google.cloud.storage.BlobReadChannelV2.BlobReadChannelV2State; +import com.google.cloud.storage.BlobWriteChannelV2.BlobWriteChannelV2State; import com.google.cloud.storage.Storage.BucketField; import com.google.cloud.storage.Storage.PredefinedAcl; import com.google.cloud.storage.UnifiedOpts.Opt; @@ -205,16 +205,18 @@ protected Serializable[] serializableObjects() { @SuppressWarnings("resource") protected Restorable[] restorableObjects() { HttpStorageOptions options = HttpStorageOptions.newBuilder().setProjectId("p2").build(); - ResultRetryAlgorithm algorithm = - options.getRetryAlgorithmManager().getForResumableUploadSessionWrite(EMPTY_RPC_OPTIONS); ReadChannel readerV2 = new BlobReadChannelV2( new StorageObject().setBucket("b").setName("n"), EMPTY_RPC_OPTIONS, BlobReadChannelContext.from(options)); - BlobWriteChannel writer = - new BlobWriteChannel( - options, BlobInfo.newBuilder(BlobId.of("b", "n")).build(), "upload-id", algorithm); + WriteChannel writer = + new BlobWriteChannelV2( + BlobReadChannelContext.from(options), + JsonResumableWrite.of( + Conversions.apiary().blobInfo().encode(BlobInfo.newBuilder("b", "n").build()), + ImmutableMap.of(), + "upload-id")); return new Restorable[] {readerV2, writer}; } @@ -227,7 +229,7 @@ public void restoreOfV1BlobReadChannelShouldReturnV2Channel() try (InputStream is = SerializationTest.class .getClassLoader() - .getResourceAsStream("com/google/cloud/storage/blobWriteChannel.ser.properties")) { + .getResourceAsStream("com/google/cloud/storage/blobReadChannel.ser.properties")) { properties.load(is); } String b64bytes = properties.getProperty("b64bytes"); @@ -239,8 +241,8 @@ public void restoreOfV1BlobReadChannelShouldReturnV2Channel() Object o = ois.readObject(); assertThat(o).isInstanceOf(RestorableState.class); RestorableState restorableState = (RestorableState) o; - assertThat(o).isInstanceOf(StateImpl.class); - StateImpl state = (StateImpl) restorableState; + assertThat(o).isInstanceOf(BlobReadChannel.StateImpl.class); + BlobReadChannel.StateImpl state = (BlobReadChannel.StateImpl) restorableState; ReadChannel restore = state.restore(); assertThat(restore).isInstanceOf(BlobReadChannelV2.class); RestorableState capture = restore.capture(); @@ -248,6 +250,36 @@ public void restoreOfV1BlobReadChannelShouldReturnV2Channel() } } + @SuppressWarnings({"deprecation", "rawtypes"}) + @Test + public void restoreOfV1BlobWriteChannelShouldReturnV2Channel() + throws IOException, ClassNotFoundException { + + Properties properties = new Properties(); + try (InputStream is = + SerializationTest.class + .getClassLoader() + .getResourceAsStream("com/google/cloud/storage/blobWriteChannel.ser.properties")) { + properties.load(is); + } + String b64bytes = properties.getProperty("b64bytes"); + assertThat(b64bytes).isNotEmpty(); + + byte[] decode = Base64.getDecoder().decode(b64bytes); + try (ByteArrayInputStream bais = new ByteArrayInputStream(decode); + ObjectInputStream ois = new ObjectInputStream(bais)) { + Object o = ois.readObject(); + assertThat(o).isInstanceOf(RestorableState.class); + RestorableState restorableState = (RestorableState) o; + assertThat(o).isInstanceOf(BlobWriteChannel.StateImpl.class); + BlobWriteChannel.StateImpl state = (BlobWriteChannel.StateImpl) restorableState; + WriteChannel restore = state.restore(); + assertThat(restore).isInstanceOf(BlobWriteChannelV2.class); + RestorableState capture = restore.capture(); + assertThat(capture).isInstanceOf(BlobWriteChannelV2State.class); + } + } + /** * Here we override the super classes implementation to remove the "assertNotSame". * diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java index 8395c6d0a5..1d1453402d 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java @@ -18,7 +18,6 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -32,7 +31,6 @@ import com.google.api.services.storage.model.StorageObject; import com.google.cloud.ServiceOptions; import com.google.cloud.Tuple; -import com.google.cloud.WriteChannel; import com.google.cloud.storage.Storage.BlobTargetOption; import com.google.cloud.storage.spi.StorageRpcFactory; import com.google.cloud.storage.spi.v1.StorageRpc; @@ -42,7 +40,6 @@ import com.google.common.io.BaseEncoding; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; @@ -983,130 +980,6 @@ private BlobInfo initializeUpload( return blobInfo; } - @Test - public void testCreateFromStream() throws Exception { - byte[] dataToSend = {1, 2, 3, 4, 5}; - ByteArrayInputStream stream = new ByteArrayInputStream(dataToSend); - - BlobInfo blobInfo = initializeUpload(dataToSend); - Blob blob = storage.createFrom(blobInfo, stream); - assertEquals(expectedUpdated, blob); - } - - @Test - public void testCreateFromWithOptions() throws Exception { - byte[] dataToSend = {1, 2, 3, 4, 5, 6}; - ByteArrayInputStream stream = new ByteArrayInputStream(dataToSend); - - BlobInfo blobInfo = initializeUpload(dataToSend, DEFAULT_BUFFER_SIZE, KMS_KEY_NAME_OPTIONS); - Blob blob = - storage.createFrom(blobInfo, stream, Storage.BlobWriteOption.kmsKeyName(KMS_KEY_NAME)); - assertEquals(expectedUpdated, blob); - } - - @Test - public void testCreateFromWithBufferSize() throws Exception { - byte[] dataToSend = {1, 2, 3, 4, 5, 6}; - ByteArrayInputStream stream = new ByteArrayInputStream(dataToSend); - int bufferSize = MIN_BUFFER_SIZE * 2; - - BlobInfo blobInfo = initializeUpload(dataToSend, bufferSize); - Blob blob = storage.createFrom(blobInfo, stream, bufferSize); - assertEquals(expectedUpdated, blob); - } - - @Test - public void testCreateFromWithBufferSizeAndOptions() throws Exception { - byte[] dataToSend = {1, 2, 3, 4, 5, 6}; - ByteArrayInputStream stream = new ByteArrayInputStream(dataToSend); - int bufferSize = MIN_BUFFER_SIZE * 2; - - BlobInfo blobInfo = initializeUpload(dataToSend, bufferSize, KMS_KEY_NAME_OPTIONS); - Blob blob = - storage.createFrom( - blobInfo, stream, bufferSize, Storage.BlobWriteOption.kmsKeyName(KMS_KEY_NAME)); - assertEquals(expectedUpdated, blob); - } - - @Test - public void testCreateFromWithSmallBufferSize() throws Exception { - byte[] dataToSend = new byte[100_000]; - ByteArrayInputStream stream = new ByteArrayInputStream(dataToSend); - int smallBufferSize = 100; - - BlobInfo blobInfo = initializeUpload(dataToSend, MIN_BUFFER_SIZE); - Blob blob = storage.createFrom(blobInfo, stream, smallBufferSize); - assertEquals(expectedUpdated, blob); - } - - @Test - public void testCreateFromWithException() throws Exception { - initializeService(); - String uploadId = "id-exception"; - byte[] bytes = new byte[10]; - byte[] buffer = new byte[MIN_BUFFER_SIZE]; - System.arraycopy(bytes, 0, buffer, 0, bytes.length); - BlobInfo info = BLOB_INFO1.toBuilder().setMd5(null).setCrc32c(null).build(); - doReturn(uploadId) - .doThrow(UNEXPECTED_CALL_EXCEPTION) - .when(storageRpcMock) - .open(Conversions.apiary().blobInfo().encode(info), EMPTY_RPC_OPTIONS); - - Exception runtimeException = new RuntimeException("message"); - doThrow(runtimeException) - .when(storageRpcMock) - .writeWithResponse(uploadId, buffer, 0, 0L, bytes.length, true); - - InputStream input = new ByteArrayInputStream(bytes); - try { - storage.createFrom(info, input, MIN_BUFFER_SIZE); - fail(); - } catch (StorageException e) { - assertSame(runtimeException, e.getCause()); - } - } - - @Test - public void testCreateFromMultipleParts() throws Exception { - initializeService(); - String uploadId = "id-multiple-parts"; - int extraBytes = 10; - int totalSize = MIN_BUFFER_SIZE + extraBytes; - byte[] dataToSend = new byte[totalSize]; - dataToSend[0] = 42; - dataToSend[MIN_BUFFER_SIZE + 1] = 43; - - StorageObject storageObject = new StorageObject(); - storageObject.setBucket(BLOB_INFO1.getBucket()); - storageObject.setName(BLOB_INFO1.getName()); - storageObject.setSize(BigInteger.valueOf(totalSize)); - - BlobInfo info = BLOB_INFO1.toBuilder().setMd5(null).setCrc32c(null).build(); - doReturn(uploadId) - .doThrow(UNEXPECTED_CALL_EXCEPTION) - .when(storageRpcMock) - .open(Conversions.apiary().blobInfo().encode(info), EMPTY_RPC_OPTIONS); - - byte[] buffer1 = new byte[MIN_BUFFER_SIZE]; - System.arraycopy(dataToSend, 0, buffer1, 0, MIN_BUFFER_SIZE); - doReturn(null) - .doThrow(UNEXPECTED_CALL_EXCEPTION) - .when(storageRpcMock) - .writeWithResponse(uploadId, buffer1, 0, 0L, MIN_BUFFER_SIZE, false); - - byte[] buffer2 = new byte[MIN_BUFFER_SIZE]; - System.arraycopy(dataToSend, MIN_BUFFER_SIZE, buffer2, 0, extraBytes); - doReturn(storageObject) - .doThrow(UNEXPECTED_CALL_EXCEPTION) - .when(storageRpcMock) - .writeWithResponse(uploadId, buffer2, 0, (long) MIN_BUFFER_SIZE, extraBytes, true); - - InputStream input = new ByteArrayInputStream(dataToSend); - Blob blob = storage.createFrom(info, input, MIN_BUFFER_SIZE); - BlobInfo info1 = Conversions.apiary().blobInfo().decode(storageObject); - assertEquals(info1.asBlob(storage), blob); - } - @Test public void testListBuckets() { String cursor = "cursor"; @@ -1330,85 +1203,6 @@ public void testListBlobsWithException() { } } - @Test - public void testWriter() { - // verify that md5 and crc32c are cleared if present when calling create - doReturn("upload-id") - .doThrow(UNEXPECTED_CALL_EXCEPTION) - .when(storageRpcMock) - .open(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITHOUT_HASHES), EMPTY_RPC_OPTIONS); - initializeService(); - WriteChannel channel = storage.writer(BLOB_INFO_WITH_HASHES); - assertNotNull(channel); - assertTrue(channel.isOpen()); - } - - @Test - public void testWriterWithOptions() { - BlobInfo info = BLOB_INFO1.toBuilder().setMd5(CONTENT_MD5).setCrc32c(CONTENT_CRC32C).build(); - doReturn("upload-id") - .doThrow(UNEXPECTED_CALL_EXCEPTION) - .when(storageRpcMock) - .open(Conversions.apiary().blobInfo().encode(info), BLOB_TARGET_OPTIONS_CREATE); - initializeService(); - WriteChannel channel = - storage.writer( - info, - BLOB_WRITE_METAGENERATION, - BLOB_WRITE_NOT_EXIST, - BLOB_WRITE_PREDEFINED_ACL, - BLOB_WRITE_CRC2C, - BLOB_WRITE_MD5_HASH); - assertNotNull(channel); - assertTrue(channel.isOpen()); - } - - @Test - public void testWriterWithEncryptionKey() { - BlobInfo info = BLOB_INFO1.toBuilder().setMd5(null).setCrc32c(null).build(); - doReturn("upload-id-1", "upload-id-2") - .doThrow(UNEXPECTED_CALL_EXCEPTION) - .when(storageRpcMock) - .open(Conversions.apiary().blobInfo().encode(info), ENCRYPTION_KEY_OPTIONS); - initializeService(); - WriteChannel channel = storage.writer(info, Storage.BlobWriteOption.encryptionKey(KEY)); - assertNotNull(channel); - assertTrue(channel.isOpen()); - channel = storage.writer(info, Storage.BlobWriteOption.encryptionKey(BASE64_KEY)); - assertNotNull(channel); - assertTrue(channel.isOpen()); - } - - @Test - public void testWriterWithKmsKeyName() { - BlobInfo info = BLOB_INFO1.toBuilder().setMd5(null).setCrc32c(null).build(); - doReturn("upload-id-1", "upload-id-2") - .doThrow(UNEXPECTED_CALL_EXCEPTION) - .when(storageRpcMock) - .open(Conversions.apiary().blobInfo().encode(info), KMS_KEY_NAME_OPTIONS); - initializeService(); - WriteChannel channel = storage.writer(info, Storage.BlobWriteOption.kmsKeyName(KMS_KEY_NAME)); - assertNotNull(channel); - assertTrue(channel.isOpen()); - channel = storage.writer(info, Storage.BlobWriteOption.kmsKeyName(KMS_KEY_NAME)); - assertNotNull(channel); - assertTrue(channel.isOpen()); - } - - @Test - public void testWriterFailure() { - doThrow(STORAGE_FAILURE) - .when(storageRpcMock) - .open(Conversions.apiary().blobInfo().encode(BLOB_INFO_WITHOUT_HASHES), EMPTY_RPC_OPTIONS); - initializeService(); - try { - storage.writer(BLOB_INFO_WITH_HASHES); - fail(); - } catch (StorageException e) { - assertSame(STORAGE_FAILURE, e.getCause()); - } - } - @Test public void testCreateNotification() { doReturn(Conversions.apiary().notificationInfo().encode(NOTIFICATION_INFO_01)) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/TestUtils.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/TestUtils.java index 875a3b0d85..af92080a7d 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/TestUtils.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/TestUtils.java @@ -226,11 +226,27 @@ public static String xxd(byte[] bytes) { } public static String xxd(ByteBuffer bytes) { + return xxd(true, bytes); + } + + public static String xxd(boolean flip, ByteBuffer bytes) { ByteBuffer dup = bytes.duplicate(); - dup.flip(); + if (flip) dup.flip(); return ByteBufUtil.prettyHexDump(Unpooled.wrappedBuffer(dup)); } + public static String xxd(boolean flip, ByteBuffer[] buffers) { + ByteBuffer[] dups = + Arrays.stream(buffers) + .map(ByteBuffer::duplicate) + .peek( + byteBuffer -> { + if (flip) byteBuffer.flip(); + }) + .toArray(ByteBuffer[]::new); + return ByteBufUtil.prettyHexDump(Unpooled.wrappedBuffer(dups)); + } + public static void assertAll(ThrowingRunnable... trs) throws Exception { List x = Arrays.stream(trs) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/TmpFile.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/TmpFile.java index a8c846c6c1..eef1b087d6 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/TmpFile.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/TmpFile.java @@ -16,6 +16,7 @@ package com.google.cloud.storage; +import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.channels.SeekableByteChannel; @@ -54,6 +55,11 @@ public void close() throws IOException { Files.delete(path); } + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("path", path).toString(); + } + /** * Create a temporary file, which will be deleted when close is called on the returned {@link * TmpFile} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java index d42755ef07..612879c4fc 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java @@ -397,6 +397,11 @@ static BiPredicate scenarioIdIs(int scenarioId) return (m, trc) -> trc.getScenarioId() == scenarioId; } + static BiPredicate mappingIdIn(Integer... mappingIds) { + ImmutableSet ids = ImmutableSet.copyOf(mappingIds); + return (m, trc) -> ids.contains(trc.getMappingId()); + } + static final class Builder { private String retryTestsJsonResourcePath; private RpcMethodMappings mappings; diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java index e81f63e3c9..9b4ab36689 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import com.google.api.client.json.JsonParser; @@ -38,26 +39,21 @@ import com.google.cloud.storage.PackagePrivateMethodWorkarounds; 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; import com.google.cloud.storage.it.runner.annotations.Backend; import com.google.cloud.storage.it.runner.annotations.Inject; import com.google.cloud.storage.it.runner.annotations.SingleBackend; +import com.google.cloud.storage.it.runner.annotations.StorageFixture; import com.google.cloud.storage.it.runner.registry.Generator; import com.google.cloud.storage.it.runner.registry.TestBench; import com.google.cloud.storage.it.runner.registry.TestBench.RetryTestResource; -import com.google.cloud.storage.spi.StorageRpcFactory; -import com.google.cloud.storage.spi.v1.StorageRpc; -import com.google.cloud.storage.spi.v1.StorageRpc.Option; import com.google.common.collect.ImmutableMap; -import com.google.common.reflect.AbstractInvocationHandler; -import com.google.common.reflect.Reflection; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,6 +80,11 @@ public final class ITBlobWriteChannelTest { @Inject public TestBench testBench; + @Inject + @StorageFixture(Transport.HTTP) + public Storage storage; + + @Inject public BucketInfo bucket; @Inject public Generator generator; /** @@ -110,24 +111,6 @@ public void testJsonEOF_10B() throws IOException { doJsonUnexpectedEOFTest(contentSize, cappedByteCount); } - @Test - public void blobWriteChannel_handlesRecoveryOnLastChunkWhenGenerationIsPresent_multipleChunks() - throws IOException { - int _2MiB = 256 * 1024; - int contentSize = 292_617; - - blobWriteChannel_handlesRecoveryOnLastChunkWhenGenerationIsPresent(_2MiB, contentSize); - } - - @Test - public void blobWriteChannel_handlesRecoveryOnLastChunkWhenGenerationIsPresent_singleChunk() - throws IOException { - int _4MiB = 256 * 1024 * 2; - int contentSize = 292_617; - - blobWriteChannel_handlesRecoveryOnLastChunkWhenGenerationIsPresent(_4MiB, contentSize); - } - @Test public void testWriteChannelExistingBlob() throws IOException { HttpStorageOptions baseStorageOptions = @@ -156,6 +139,20 @@ public void testWriteChannelExistingBlob() throws IOException { assertTrue(storage.delete(bucketInfo.getName(), blobInfo.getName())); } + @Test + public void changeChunkSizeAfterWrite() throws IOException { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + System.out.println("info = " + info); + + int _512KiB = 512 * 1024; + byte[] bytes = DataGenerator.base64Characters().genBytes(_512KiB + 13); + try (WriteChannel writer = storage.writer(info, BlobWriteOption.doesNotExist())) { + writer.setChunkSize(2 * 1024 * 1024); + writer.write(ByteBuffer.wrap(bytes, 0, _512KiB)); + assertThrows(IllegalStateException.class, () -> writer.setChunkSize(768 * 1024)); + } + } + private void doJsonUnexpectedEOFTest(int contentSize, int cappedByteCount) throws IOException { String blobPath = String.format("%s/%s/blob", generator.randomObjectName(), NOW_STRING); @@ -176,47 +173,11 @@ private void doJsonUnexpectedEOFTest(int contentSize, int cappedByteCount) throw .setCredentials(NoCredentials.getInstance()) .setHost(testBench.getBaseUri()) .setProjectId("project-id") - .build(); - StorageRpc noHeader = (StorageRpc) baseOptions.getRpc(); - StorageRpc yesHeader = - (StorageRpc) - baseOptions - .toBuilder() - .setHeaderProvider( - FixedHeaderProvider.create(ImmutableMap.of("x-retry-test-id", retryTest.id))) - .build() - .getRpc(); - - StorageOptions storageOptions = - baseOptions - .toBuilder() - .setServiceRpcFactory( - options -> - Reflection.newProxy( - StorageRpc.class, - (proxy, method, args) -> { - try { - if ("writeWithResponse".equals(method.getName())) { - boolean lastChunk = (boolean) args[5]; - LOGGER.fine( - String.format( - "writeWithResponse called. (lastChunk = %b)", lastChunk)); - if (lastChunk) { - return method.invoke(yesHeader, args); - } - } - return method.invoke(noHeader, args); - } catch (Exception e) { - if (e.getCause() != null) { - throw e.getCause(); - } else { - throw e; - } - } - })) + .setHeaderProvider( + FixedHeaderProvider.create(ImmutableMap.of("x-retry-test-id", retryTest.id))) .build(); - Storage testStorage = storageOptions.getService(); + Storage testStorage = baseOptions.getService(); testStorage.create(bucketInfo); @@ -234,7 +195,7 @@ private void doJsonUnexpectedEOFTest(int contentSize, int cappedByteCount) throw Optional optionalStorageObject = PackagePrivateMethodWorkarounds.maybeGetBlobInfoFunction().apply(w); - assertTrue(optionalStorageObject.isPresent()); + assertThat(optionalStorageObject.isPresent()).isTrue(); BlobInfo internalInfo = optionalStorageObject.get(); assertThat(internalInfo.getName()).isEqualTo(blobInfoGen0.getName()); @@ -248,100 +209,4 @@ private void doJsonUnexpectedEOFTest(int contentSize, int cappedByteCount) throw ByteBuffer actual = ByteBuffer.wrap(actualData.toByteArray()); assertEquals(expected, actual); } - - private void blobWriteChannel_handlesRecoveryOnLastChunkWhenGenerationIsPresent( - int chunkSize, int contentSize) throws IOException { - Instant now = Clock.systemUTC().instant(); - DateTimeFormatter formatter = - DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneId.from(ZoneOffset.UTC)); - String nowString = formatter.format(now); - BucketInfo bucketInfo = BucketInfo.of(generator.randomBucketName()); - String blobPath = String.format("%s/%s/blob", generator.randomObjectName(), nowString); - BlobId blobId = BlobId.of(bucketInfo.getName(), blobPath); - BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); - - ByteBuffer contentGen1 = DataGenerator.base64Characters().genByteBuffer(contentSize); - ByteBuffer contentGen2 = DataGenerator.base64Characters().genByteBuffer(contentSize); - ByteBuffer contentGen2Expected = contentGen2.duplicate(); - HttpStorageOptions baseStorageOptions = - StorageOptions.http() - .setCredentials(NoCredentials.getInstance()) - .setHost(testBench.getBaseUri()) - .setProjectId("test-project-id") - .build(); - Storage storage = baseStorageOptions.getService(); - storage.create(bucketInfo); - WriteChannel ww = storage.writer(blobInfo); - ww.setChunkSize(chunkSize); - ww.write(contentGen1); - ww.close(); - - Blob blobGen1 = storage.get(blobId); - - final AtomicBoolean exceptionThrown = new AtomicBoolean(false); - - Storage testStorage = - baseStorageOptions - .toBuilder() - .setServiceRpcFactory( - new StorageRpcFactory() { - /** - * Here we're creating a proxy of StorageRpc where we can delegate all calls to - * the normal implementation, except in the case of {@link - * StorageRpc#writeWithResponse(String, byte[], int, long, int, boolean)} where - * {@code lastChunk == true}. We allow the call to execute, but instead of - * returning the result we throw an IOException to simulate a prematurely close - * connection. This behavior is to ensure appropriate handling of a completed - * upload where the ACK wasn't received. In particular, if an upload is initiated - * against an object where an {@link Option#IF_GENERATION_MATCH} simply calling - * get on an object can result in a 404 because the object that is created while - * the BlobWriteChannel is executing will be a new generation. - */ - @Override - public StorageRpc create(final StorageOptions options) { - return Reflection.newProxy( - StorageRpc.class, - new AbstractInvocationHandler() { - final StorageRpc delegate = (StorageRpc) baseStorageOptions.getRpc(); - - @Override - protected Object handleInvocation( - Object proxy, java.lang.reflect.Method method, Object[] args) - throws Throwable { - if ("writeWithResponse".equals(method.getName())) { - Object result = method.invoke(delegate, args); - boolean lastChunk = (boolean) args[5]; - // if we're on the lastChunk simulate a connection failure which - // happens after the request was processed but before response could - // be received by the client. - if (lastChunk) { - exceptionThrown.set(true); - throw StorageException.translate( - new IOException("simulated Connection closed prematurely")); - } else { - return result; - } - } - return method.invoke(delegate, args); - } - }); - } - }) - .build() - .getService(); - try (WriteChannel w = testStorage.writer(blobGen1, BlobWriteOption.generationMatch())) { - w.setChunkSize(chunkSize); - - w.write(contentGen2); - } - - assertTrue("Expected an exception to be thrown for the last chunk", exceptionThrown.get()); - - Blob blobGen2 = storage.get(blobId); - assertEquals(contentSize, (long) blobGen2.getSize()); - assertNotEquals(blobInfo.getGeneration(), blobGen2.getGeneration()); - ByteArrayOutputStream actualData = new ByteArrayOutputStream(); - blobGen2.downloadTo(actualData); - assertEquals(contentGen2Expected, ByteBuffer.wrap(actualData.toByteArray())); - } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/TemporaryBucket.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/TemporaryBucket.java index 0f6d255b3c..54ccfd92d9 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/TemporaryBucket.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/TemporaryBucket.java @@ -25,7 +25,7 @@ import com.google.common.base.Preconditions; import java.time.Duration; -final class TemporaryBucket implements AutoCloseable { +public final class TemporaryBucket implements AutoCloseable { private final BucketInfo bucket; private final Storage storage; @@ -44,7 +44,7 @@ private TemporaryBucket( } /** Return the BucketInfo from the created temporary bucket. */ - BucketInfo getBucket() { + public BucketInfo getBucket() { return bucket; } @@ -55,11 +55,11 @@ public void close() throws Exception { } } - static Builder newBuilder() { + public static Builder newBuilder() { return new Builder(); } - static final class Builder { + public static final class Builder { private CleanupStrategy cleanupStrategy; private Duration cleanupTimeoutDuration; @@ -71,27 +71,27 @@ private Builder() { this.cleanupTimeoutDuration = Duration.ofMinutes(1); } - Builder setCleanupStrategy(CleanupStrategy cleanupStrategy) { + public Builder setCleanupStrategy(CleanupStrategy cleanupStrategy) { this.cleanupStrategy = cleanupStrategy; return this; } - Builder setCleanupTimeoutDuration(Duration cleanupTimeoutDuration) { + public Builder setCleanupTimeoutDuration(Duration cleanupTimeoutDuration) { this.cleanupTimeoutDuration = cleanupTimeoutDuration; return this; } - Builder setBucketInfo(BucketInfo bucketInfo) { + public Builder setBucketInfo(BucketInfo bucketInfo) { this.bucketInfo = bucketInfo; return this; } - Builder setStorage(Storage storage) { + public Builder setStorage(Storage storage) { this.storage = storage; return this; } - TemporaryBucket build() { + public TemporaryBucket build() { Preconditions.checkArgument( cleanupStrategy != CleanupStrategy.ONLY_ON_SUCCESS, "Unable to detect success."); Storage s = requireNonNull(storage, "storage must be non null"); diff --git a/google-cloud-storage/src/test/resources/com/google/cloud/storage/blobReadChannel.ser.properties b/google-cloud-storage/src/test/resources/com/google/cloud/storage/blobReadChannel.ser.properties new file mode 100644 index 0000000000..c9d3dc5ff8 --- /dev/null +++ b/google-cloud-storage/src/test/resources/com/google/cloud/storage/blobReadChannel.ser.properties @@ -0,0 +1,70 @@ +# +# Copyright 2022 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. +# + +# Base 64 Encoded bytes of a BlobReadChannel circa v2.16.0 +# Generated using the following snippet: +# +# Storage s = StorageOptions.http() +# .setProjectId("proj") +# .setCredentials(NoCredentials.getInstance()) +# .build() +# .getService(); +# +# ReadChannel reader = s.reader(BlobId.of("buck", "obj", 1L)); +# RestorableState capture = reader.capture(); +# +# ByteArrayOutputStream baos = new ByteArrayOutputStream(); +# try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { +# oos.writeObject(capture); +# } +# +# byte[] bytes = baos.toByteArray(); +# String b64Ser = Base64.getEncoder().encodeToString(bytes); +# +# System.out.println("b64Ser = " + b64Ser); +# +b64bytes=\ + rO0ABXNyADJjb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuQmxvYlJlYWRDaGFubmVsJFN0YXRlSW1wbGwJWjOFWbi1AgAJSQAJY2h1bmtTaXplWgALZW5kT2ZTdHJlYW1a\ + AAZpc09wZW5KAAVsaW1pdEoACHBvc2l0aW9uTAAEYmxvYnQAIUxjb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvQmxvYklkO0wACGxhc3RFdGFndAASTGphdmEvbGFuZy9T\ + dHJpbmc7TAAOcmVxdWVzdE9wdGlvbnN0AA9MamF2YS91dGlsL01hcDtMAA5zZXJ2aWNlT3B0aW9uc3QALUxjb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvSHR0cFN0b3Jh\ + Z2VPcHRpb25zO3hwACAAAAABf/////////8AAAAAAAAAAHNyAB9jb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuQmxvYklkcdHeVjWP2d0CAANMAAZidWNrZXRxAH4AAkwA\ + CmdlbmVyYXRpb250ABBMamF2YS9sYW5nL0xvbmc7TAAEbmFtZXEAfgACeHB0AARidWNrc3IADmphdmEubGFuZy5Mb25nO4vkkMyPI98CAAFKAAV2YWx1ZXhyABBqYXZh\ + LmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAAAAAABdAADb2JqcHNyADVjb20uZ29vZ2xlLmNvbW1vbi5jb2xsZWN0LkltbXV0YWJsZU1hcCRTZXJpYWxpemVkRm9y\ + bQAAAAAAAAAAAgACTAAEa2V5c3QAEkxqYXZhL2xhbmcvT2JqZWN0O0wABnZhbHVlc3EAfgAPeHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAAA\ + dXEAfgARAAAAAHNyACtjb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuSHR0cFN0b3JhZ2VPcHRpb25ztmk+4Fw7cvMCAAFMABVyZXRyeUFsZ29yaXRobU1hbmFnZXJ0ADRM\ + Y29tL2dvb2dsZS9jbG91ZC9zdG9yYWdlL0h0dHBSZXRyeUFsZ29yaXRobU1hbmFnZXI7eHIAJ2NvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5TdG9yYWdlT3B0aW9uc5q/\ + 8jOW5d5PAgAAeHIAH2NvbS5nb29nbGUuY2xvdWQuU2VydmljZU9wdGlvbnN/qQsz9VFyfgIAC0wADmNsaWVudExpYlRva2VucQB+AAJMAAVjbG9ja3QAHkxjb20vZ29v\ + Z2xlL2FwaS9jb3JlL0FwaUNsb2NrO0wAC2NyZWRlbnRpYWxzdAAdTGNvbS9nb29nbGUvYXV0aC9DcmVkZW50aWFscztMAA5oZWFkZXJQcm92aWRlcnQAJ0xjb20vZ29v\ + Z2xlL2FwaS9nYXgvcnBjL0hlYWRlclByb3ZpZGVyO0wABGhvc3RxAH4AAkwACXByb2plY3RJZHEAfgACTAAOcXVvdGFQcm9qZWN0SWRxAH4AAkwADXJldHJ5U2V0dGlu\ + Z3N0ACtMY29tL2dvb2dsZS9hcGkvZ2F4L3JldHJ5aW5nL1JldHJ5U2V0dGluZ3M7TAAXc2VydmljZUZhY3RvcnlDbGFzc05hbWVxAH4AAkwAGnNlcnZpY2VScGNGYWN0\ + b3J5Q2xhc3NOYW1lcQB+AAJMABB0cmFuc3BvcnRPcHRpb25zdAAjTGNvbS9nb29nbGUvY2xvdWQvVHJhbnNwb3J0T3B0aW9uczt4cHQABGdjY2xzcgAmY29tLmdvb2ds\ + ZS5hcGkuY29yZS5DdXJyZW50TWlsbGlzQ2xvY2usd0sHJ9YTCwIAAHhwc3IAHmNvbS5nb29nbGUuY2xvdWQuTm9DcmVkZW50aWFsc6kR5wOeLAxAAgAAeHIAKGNvbS5n\ + b29nbGUuYXV0aC5vYXV0aDIuT0F1dGgyQ3JlZGVudGlhbHM/PX166aVRVwIABEwAEGV4cGlyYXRpb25NYXJnaW50ABRMamF2YS90aW1lL0R1cmF0aW9uO0wABGxvY2tx\ + AH4AD0wADXJlZnJlc2hNYXJnaW5xAH4AI0wABXZhbHVldAA1TGNvbS9nb29nbGUvYXV0aC9vYXV0aDIvT0F1dGgyQ3JlZGVudGlhbHMkT0F1dGhWYWx1ZTt4cgAbY29t\ + Lmdvb2dsZS5hdXRoLkNyZWRlbnRpYWxzCzii14w9kIECAAB4cHNyAA1qYXZhLnRpbWUuU2VylV2EuhsiSLIMAAB4cHcNAQAAAAAAAAEsAAAAAHh1cgACW0Ks8xf4BghU\ + 4AIAAHhwAAAAAHNxAH4AJ3cNAQAAAAAAAAFoAAAAAHhwc3IAJ2NvbS5nb29nbGUuYXBpLmdheC5ycGMuTm9IZWFkZXJQcm92aWRlcmWjEqhqxXthAgAAeHB0AB5odHRw\ + czovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb210AARwcm9qcHNyADNjb20uZ29vZ2xlLmFwaS5nYXgucmV0cnlpbmcuQXV0b1ZhbHVlX1JldHJ5U2V0dGluZ3Nym/9/a0d0\ + swIACVoACGppdHRlcmVkSQALbWF4QXR0ZW1wdHNEABRyZXRyeURlbGF5TXVsdGlwbGllckQAFHJwY1RpbWVvdXRNdWx0aXBsaWVyTAARaW5pdGlhbFJldHJ5RGVsYXl0\ + ABpMb3JnL3RocmVldGVuL2JwL0R1cmF0aW9uO0wAEWluaXRpYWxScGNUaW1lb3V0cQB+ADFMAA1tYXhSZXRyeURlbGF5cQB+ADFMAA1tYXhScGNUaW1lb3V0cQB+ADFM\ + AAx0b3RhbFRpbWVvdXRxAH4AMXhyACljb20uZ29vZ2xlLmFwaS5nYXgucmV0cnlpbmcuUmV0cnlTZXR0aW5nc3Kb/39rR3SzAgAAeHABAAAABkAAAAAAAAAAP/AAAAAA\ + AABzcgATb3JnLnRocmVldGVuLmJwLlNlcpVdhLobIkiyDAAAeHB3DQEAAAAAAAAAAQAAAAB4c3EAfgA0dw0BAAAAAAAAADIAAAAAeHNxAH4ANHcNAQAAAAAAAAAgAAAA\ + AHhzcQB+ADR3DQEAAAAAAAAAMgAAAAB4c3EAfgA0dw0BAAAAAAAAADIAAAAAeHQAPmNvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5IdHRwU3RvcmFnZU9wdGlvbnMkSHR0\ + cFN0b3JhZ2VGYWN0b3J5dABBY29tLmdvb2dsZS5jbG91ZC5zdG9yYWdlLkh0dHBTdG9yYWdlT3B0aW9ucyRIdHRwU3RvcmFnZVJwY0ZhY3RvcnlzcgAqY29tLmdvb2ds\ + ZS5jbG91ZC5odHRwLkh0dHBUcmFuc3BvcnRPcHRpb25zbX9UTb2H/yICAANJAA5jb25uZWN0VGltZW91dEkAC3JlYWRUaW1lb3V0TAAdaHR0cFRyYW5zcG9ydEZhY3Rv\ + cnlDbGFzc05hbWVxAH4AAnhw//////////90AEZjb20uZ29vZ2xlLmNsb3VkLmh0dHAuSHR0cFRyYW5zcG9ydE9wdGlvbnMkRGVmYXVsdEh0dHBUcmFuc3BvcnRGYWN0\ + b3J5c3IAMmNvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5IdHRwUmV0cnlBbGdvcml0aG1NYW5hZ2Vy0i1ymVA0mEUCAAFMAA1yZXRyeVN0cmF0ZWd5dAAvTGNvbS9nb29n\ + bGUvY2xvdWQvc3RvcmFnZS9TdG9yYWdlUmV0cnlTdHJhdGVneTt4cHNyADRjb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuRGVmYXVsdFN0b3JhZ2VSZXRyeVN0cmF0ZWd5\ + bgaLnarjlYkCAAB4cA== diff --git a/google-cloud-storage/src/test/resources/com/google/cloud/storage/blobWriteChannel.ser.properties b/google-cloud-storage/src/test/resources/com/google/cloud/storage/blobWriteChannel.ser.properties index c9d3dc5ff8..d60cb17499 100644 --- a/google-cloud-storage/src/test/resources/com/google/cloud/storage/blobWriteChannel.ser.properties +++ b/google-cloud-storage/src/test/resources/com/google/cloud/storage/blobWriteChannel.ser.properties @@ -1,5 +1,5 @@ # -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ # .build() # .getService(); # -# ReadChannel reader = s.reader(BlobId.of("buck", "obj", 1L)); -# RestorableState capture = reader.capture(); +# WriteChannel reader = s.writer(BlobInfo.newBuilder("buck", "obj").build(), BlobWriteOption.doesNotExist()); +# RestorableState capture = reader.capture(); # # ByteArrayOutputStream baos = new ByteArrayOutputStream(); # try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { @@ -37,34 +37,44 @@ # System.out.println("b64Ser = " + b64Ser); # b64bytes=\ - rO0ABXNyADJjb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuQmxvYlJlYWRDaGFubmVsJFN0YXRlSW1wbGwJWjOFWbi1AgAJSQAJY2h1bmtTaXplWgALZW5kT2ZTdHJlYW1a\ - AAZpc09wZW5KAAVsaW1pdEoACHBvc2l0aW9uTAAEYmxvYnQAIUxjb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvQmxvYklkO0wACGxhc3RFdGFndAASTGphdmEvbGFuZy9T\ - dHJpbmc7TAAOcmVxdWVzdE9wdGlvbnN0AA9MamF2YS91dGlsL01hcDtMAA5zZXJ2aWNlT3B0aW9uc3QALUxjb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvSHR0cFN0b3Jh\ - Z2VPcHRpb25zO3hwACAAAAABf/////////8AAAAAAAAAAHNyAB9jb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuQmxvYklkcdHeVjWP2d0CAANMAAZidWNrZXRxAH4AAkwA\ - CmdlbmVyYXRpb250ABBMamF2YS9sYW5nL0xvbmc7TAAEbmFtZXEAfgACeHB0AARidWNrc3IADmphdmEubGFuZy5Mb25nO4vkkMyPI98CAAFKAAV2YWx1ZXhyABBqYXZh\ - LmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAAAAAABdAADb2JqcHNyADVjb20uZ29vZ2xlLmNvbW1vbi5jb2xsZWN0LkltbXV0YWJsZU1hcCRTZXJpYWxpemVkRm9y\ - bQAAAAAAAAAAAgACTAAEa2V5c3QAEkxqYXZhL2xhbmcvT2JqZWN0O0wABnZhbHVlc3EAfgAPeHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAAA\ - dXEAfgARAAAAAHNyACtjb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuSHR0cFN0b3JhZ2VPcHRpb25ztmk+4Fw7cvMCAAFMABVyZXRyeUFsZ29yaXRobU1hbmFnZXJ0ADRM\ - Y29tL2dvb2dsZS9jbG91ZC9zdG9yYWdlL0h0dHBSZXRyeUFsZ29yaXRobU1hbmFnZXI7eHIAJ2NvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5TdG9yYWdlT3B0aW9uc5q/\ - 8jOW5d5PAgAAeHIAH2NvbS5nb29nbGUuY2xvdWQuU2VydmljZU9wdGlvbnN/qQsz9VFyfgIAC0wADmNsaWVudExpYlRva2VucQB+AAJMAAVjbG9ja3QAHkxjb20vZ29v\ - Z2xlL2FwaS9jb3JlL0FwaUNsb2NrO0wAC2NyZWRlbnRpYWxzdAAdTGNvbS9nb29nbGUvYXV0aC9DcmVkZW50aWFscztMAA5oZWFkZXJQcm92aWRlcnQAJ0xjb20vZ29v\ - Z2xlL2FwaS9nYXgvcnBjL0hlYWRlclByb3ZpZGVyO0wABGhvc3RxAH4AAkwACXByb2plY3RJZHEAfgACTAAOcXVvdGFQcm9qZWN0SWRxAH4AAkwADXJldHJ5U2V0dGlu\ - Z3N0ACtMY29tL2dvb2dsZS9hcGkvZ2F4L3JldHJ5aW5nL1JldHJ5U2V0dGluZ3M7TAAXc2VydmljZUZhY3RvcnlDbGFzc05hbWVxAH4AAkwAGnNlcnZpY2VScGNGYWN0\ - b3J5Q2xhc3NOYW1lcQB+AAJMABB0cmFuc3BvcnRPcHRpb25zdAAjTGNvbS9nb29nbGUvY2xvdWQvVHJhbnNwb3J0T3B0aW9uczt4cHQABGdjY2xzcgAmY29tLmdvb2ds\ - ZS5hcGkuY29yZS5DdXJyZW50TWlsbGlzQ2xvY2usd0sHJ9YTCwIAAHhwc3IAHmNvbS5nb29nbGUuY2xvdWQuTm9DcmVkZW50aWFsc6kR5wOeLAxAAgAAeHIAKGNvbS5n\ - b29nbGUuYXV0aC5vYXV0aDIuT0F1dGgyQ3JlZGVudGlhbHM/PX166aVRVwIABEwAEGV4cGlyYXRpb25NYXJnaW50ABRMamF2YS90aW1lL0R1cmF0aW9uO0wABGxvY2tx\ - AH4AD0wADXJlZnJlc2hNYXJnaW5xAH4AI0wABXZhbHVldAA1TGNvbS9nb29nbGUvYXV0aC9vYXV0aDIvT0F1dGgyQ3JlZGVudGlhbHMkT0F1dGhWYWx1ZTt4cgAbY29t\ - Lmdvb2dsZS5hdXRoLkNyZWRlbnRpYWxzCzii14w9kIECAAB4cHNyAA1qYXZhLnRpbWUuU2VylV2EuhsiSLIMAAB4cHcNAQAAAAAAAAEsAAAAAHh1cgACW0Ks8xf4BghU\ - 4AIAAHhwAAAAAHNxAH4AJ3cNAQAAAAAAAAFoAAAAAHhwc3IAJ2NvbS5nb29nbGUuYXBpLmdheC5ycGMuTm9IZWFkZXJQcm92aWRlcmWjEqhqxXthAgAAeHB0AB5odHRw\ - czovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb210AARwcm9qcHNyADNjb20uZ29vZ2xlLmFwaS5nYXgucmV0cnlpbmcuQXV0b1ZhbHVlX1JldHJ5U2V0dGluZ3Nym/9/a0d0\ - swIACVoACGppdHRlcmVkSQALbWF4QXR0ZW1wdHNEABRyZXRyeURlbGF5TXVsdGlwbGllckQAFHJwY1RpbWVvdXRNdWx0aXBsaWVyTAARaW5pdGlhbFJldHJ5RGVsYXl0\ - ABpMb3JnL3RocmVldGVuL2JwL0R1cmF0aW9uO0wAEWluaXRpYWxScGNUaW1lb3V0cQB+ADFMAA1tYXhSZXRyeURlbGF5cQB+ADFMAA1tYXhScGNUaW1lb3V0cQB+ADFM\ - AAx0b3RhbFRpbWVvdXRxAH4AMXhyACljb20uZ29vZ2xlLmFwaS5nYXgucmV0cnlpbmcuUmV0cnlTZXR0aW5nc3Kb/39rR3SzAgAAeHABAAAABkAAAAAAAAAAP/AAAAAA\ - AABzcgATb3JnLnRocmVldGVuLmJwLlNlcpVdhLobIkiyDAAAeHB3DQEAAAAAAAAAAQAAAAB4c3EAfgA0dw0BAAAAAAAAADIAAAAAeHNxAH4ANHcNAQAAAAAAAAAgAAAA\ - AHhzcQB+ADR3DQEAAAAAAAAAMgAAAAB4c3EAfgA0dw0BAAAAAAAAADIAAAAAeHQAPmNvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5IdHRwU3RvcmFnZU9wdGlvbnMkSHR0\ - cFN0b3JhZ2VGYWN0b3J5dABBY29tLmdvb2dsZS5jbG91ZC5zdG9yYWdlLkh0dHBTdG9yYWdlT3B0aW9ucyRIdHRwU3RvcmFnZVJwY0ZhY3RvcnlzcgAqY29tLmdvb2ds\ - ZS5jbG91ZC5odHRwLkh0dHBUcmFuc3BvcnRPcHRpb25zbX9UTb2H/yICAANJAA5jb25uZWN0VGltZW91dEkAC3JlYWRUaW1lb3V0TAAdaHR0cFRyYW5zcG9ydEZhY3Rv\ - cnlDbGFzc05hbWVxAH4AAnhw//////////90AEZjb20uZ29vZ2xlLmNsb3VkLmh0dHAuSHR0cFRyYW5zcG9ydE9wdGlvbnMkRGVmYXVsdEh0dHBUcmFuc3BvcnRGYWN0\ - b3J5c3IAMmNvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5IdHRwUmV0cnlBbGdvcml0aG1NYW5hZ2Vy0i1ymVA0mEUCAAFMAA1yZXRyeVN0cmF0ZWd5dAAvTGNvbS9nb29n\ - bGUvY2xvdWQvc3RvcmFnZS9TdG9yYWdlUmV0cnlTdHJhdGVneTt4cHNyADRjb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuRGVmYXVsdFN0b3JhZ2VSZXRyeVN0cmF0ZWd5\ - bgaLnarjlYkCAAB4cA== + rO0ABXNyADNjb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuQmxvYldyaXRlQ2hhbm5lbCRTdGF0ZUltcGyjA3jVYuVZZQIAAUwAEWFsZ29yaXRobUZvcldyaXRldAAyTGNv\ + bS9nb29nbGUvYXBpL2dheC9yZXRyeWluZy9SZXN1bHRSZXRyeUFsZ29yaXRobTt4cgArY29tLmdvb2dsZS5jbG91ZC5CYXNlV3JpdGVDaGFubmVsJEJhc2VTdGF0ZXaH\ + 8w86CHBzAgAHSQAJY2h1bmtTaXplWgAGaXNPcGVuSgAIcG9zaXRpb25bAAZidWZmZXJ0AAJbQkwABmVudGl0eXQAFkxqYXZhL2lvL1NlcmlhbGl6YWJsZTtMAA5zZXJ2\ + aWNlT3B0aW9uc3QAIUxjb20vZ29vZ2xlL2Nsb3VkL1NlcnZpY2VPcHRpb25zO0wACHVwbG9hZElkdAASTGphdmEvbGFuZy9TdHJpbmc7eHAA8AAAAQAAAAAAAAAAdXIA\ + AltCrPMX+AYIVOACAAB4cAAAAABwc3IAK2NvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5IdHRwU3RvcmFnZU9wdGlvbnO2aT7gXDty8wIAAUwAFXJldHJ5QWxnb3JpdGht\ + TWFuYWdlcnQANExjb20vZ29vZ2xlL2Nsb3VkL3N0b3JhZ2UvSHR0cFJldHJ5QWxnb3JpdGhtTWFuYWdlcjt4cgAnY29tLmdvb2dsZS5jbG91ZC5zdG9yYWdlLlN0b3Jh\ + Z2VPcHRpb25zmr/yM5bl3k8CAAB4cgAfY29tLmdvb2dsZS5jbG91ZC5TZXJ2aWNlT3B0aW9uc3+pCzP1UXJ+AgALTAAOY2xpZW50TGliVG9rZW5xAH4ABkwABWNsb2Nr\ + dAAeTGNvbS9nb29nbGUvYXBpL2NvcmUvQXBpQ2xvY2s7TAALY3JlZGVudGlhbHN0AB1MY29tL2dvb2dsZS9hdXRoL0NyZWRlbnRpYWxzO0wADmhlYWRlclByb3ZpZGVy\ + dAAnTGNvbS9nb29nbGUvYXBpL2dheC9ycGMvSGVhZGVyUHJvdmlkZXI7TAAEaG9zdHEAfgAGTAAJcHJvamVjdElkcQB+AAZMAA5xdW90YVByb2plY3RJZHEAfgAGTAAN\ + cmV0cnlTZXR0aW5nc3QAK0xjb20vZ29vZ2xlL2FwaS9nYXgvcmV0cnlpbmcvUmV0cnlTZXR0aW5ncztMABdzZXJ2aWNlRmFjdG9yeUNsYXNzTmFtZXEAfgAGTAAac2Vy\ + dmljZVJwY0ZhY3RvcnlDbGFzc05hbWVxAH4ABkwAEHRyYW5zcG9ydE9wdGlvbnN0ACNMY29tL2dvb2dsZS9jbG91ZC9UcmFuc3BvcnRPcHRpb25zO3hwdAAEZ2NjbHNy\ + ACZjb20uZ29vZ2xlLmFwaS5jb3JlLkN1cnJlbnRNaWxsaXNDbG9ja6x3Swcn1hMLAgAAeHBzcgAeY29tLmdvb2dsZS5jbG91ZC5Ob0NyZWRlbnRpYWxzqRHnA54sDEAC\ + AAB4cgAoY29tLmdvb2dsZS5hdXRoLm9hdXRoMi5PQXV0aDJDcmVkZW50aWFscz89fXrppVFXAgAETAAQZXhwaXJhdGlvbk1hcmdpbnQAFExqYXZhL3RpbWUvRHVyYXRp\ + b247TAAEbG9ja3QAEkxqYXZhL2xhbmcvT2JqZWN0O0wADXJlZnJlc2hNYXJnaW5xAH4AGUwABXZhbHVldAA1TGNvbS9nb29nbGUvYXV0aC9vYXV0aDIvT0F1dGgyQ3Jl\ + ZGVudGlhbHMkT0F1dGhWYWx1ZTt4cgAbY29tLmdvb2dsZS5hdXRoLkNyZWRlbnRpYWxzCzii14w9kIECAAB4cHNyAA1qYXZhLnRpbWUuU2VylV2EuhsiSLIMAAB4cHcN\ + AQAAAAAAAAEsAAAAAHh1cQB+AAgAAAAAc3EAfgAedw0BAAAAAAAAAWgAAAAAeHBzcgAnY29tLmdvb2dsZS5hcGkuZ2F4LnJwYy5Ob0hlYWRlclByb3ZpZGVyZaMSqGrF\ + e2ECAAB4cHQAFWh0dHA6Ly9sb2NhbGhvc3Q6OTAwMHQABHByb2pwc3IAM2NvbS5nb29nbGUuYXBpLmdheC5yZXRyeWluZy5BdXRvVmFsdWVfUmV0cnlTZXR0aW5nc3Kb\ + /39rR3SzAgAJWgAIaml0dGVyZWRJAAttYXhBdHRlbXB0c0QAFHJldHJ5RGVsYXlNdWx0aXBsaWVyRAAUcnBjVGltZW91dE11bHRpcGxpZXJMABFpbml0aWFsUmV0cnlE\ + ZWxheXQAGkxvcmcvdGhyZWV0ZW4vYnAvRHVyYXRpb247TAARaW5pdGlhbFJwY1RpbWVvdXRxAH4AJ0wADW1heFJldHJ5RGVsYXlxAH4AJ0wADW1heFJwY1RpbWVvdXRx\ + AH4AJ0wADHRvdGFsVGltZW91dHEAfgAneHIAKWNvbS5nb29nbGUuYXBpLmdheC5yZXRyeWluZy5SZXRyeVNldHRpbmdzcpv/f2tHdLMCAAB4cAEAAAAGQAAAAAAAAAA/\ + 8AAAAAAAAHNyABNvcmcudGhyZWV0ZW4uYnAuU2VylV2EuhsiSLIMAAB4cHcNAQAAAAAAAAABAAAAAHhzcQB+ACp3DQEAAAAAAAAAMgAAAAB4c3EAfgAqdw0BAAAAAAAA\ + ACAAAAAAeHNxAH4AKncNAQAAAAAAAAAyAAAAAHhzcQB+ACp3DQEAAAAAAAAAMgAAAAB4dAA+Y29tLmdvb2dsZS5jbG91ZC5zdG9yYWdlLkh0dHBTdG9yYWdlT3B0aW9u\ + cyRIdHRwU3RvcmFnZUZhY3Rvcnl0AEFjb20uZ29vZ2xlLmNsb3VkLnN0b3JhZ2UuSHR0cFN0b3JhZ2VPcHRpb25zJEh0dHBTdG9yYWdlUnBjRmFjdG9yeXNyACpjb20u\ + Z29vZ2xlLmNsb3VkLmh0dHAuSHR0cFRyYW5zcG9ydE9wdGlvbnNtf1RNvYf/IgIAA0kADmNvbm5lY3RUaW1lb3V0SQALcmVhZFRpbWVvdXRMAB1odHRwVHJhbnNwb3J0\ + RmFjdG9yeUNsYXNzTmFtZXEAfgAGeHD//////////3QARmNvbS5nb29nbGUuY2xvdWQuaHR0cC5IdHRwVHJhbnNwb3J0T3B0aW9ucyREZWZhdWx0SHR0cFRyYW5zcG9y\ + dEZhY3RvcnlzcgAyY29tLmdvb2dsZS5jbG91ZC5zdG9yYWdlLkh0dHBSZXRyeUFsZ29yaXRobU1hbmFnZXLSLXKZUDSYRQIAAUwADXJldHJ5U3RyYXRlZ3l0AC9MY29t\ + L2dvb2dsZS9jbG91ZC9zdG9yYWdlL1N0b3JhZ2VSZXRyeVN0cmF0ZWd5O3hwc3IANGNvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5EZWZhdWx0U3RvcmFnZVJldHJ5U3Ry\ + YXRlZ3luBoudquOViQIAAHhwdACQaHR0cDovL2xvY2FsaG9zdDo5MDAwL3VwbG9hZC9zdG9yYWdlL3YxL2IvYnVjay9vP3VwbG9hZFR5cGU9cmVzdW1hYmxlJnVwbG9h\ + ZF9pZD0xNzcyNzI1NDM5ZDEyZWUzNjNmZmRlNmNiZmNlYjEzMGYzZTIxMWJiM2NjMzBlNjFhNGQ2N2I2MTU0OTUxMjIxc3IAIWNvbS5nb29nbGUuY2xvdWQuRXhjZXB0\ + aW9uSGFuZGxlct3Z0AGsJj+JAgAETAAMaW50ZXJjZXB0b3JzdAApTGNvbS9nb29nbGUvY29tbW9uL2NvbGxlY3QvSW1tdXRhYmxlTGlzdDtMABZub25SZXRyaWFibGVF\ + eGNlcHRpb25zdAAoTGNvbS9nb29nbGUvY29tbW9uL2NvbGxlY3QvSW1tdXRhYmxlU2V0O0wAE3JldHJpYWJsZUV4Y2VwdGlvbnNxAH4APUwACXJldHJ5SW5mb3QAD0xq\ + YXZhL3V0aWwvU2V0O3hwc3IANmNvbS5nb29nbGUuY29tbW9uLmNvbGxlY3QuSW1tdXRhYmxlTGlzdCRTZXJpYWxpemVkRm9ybQAAAAAAAAAAAgABWwAIZWxlbWVudHN0\ + ABNbTGphdmEvbGFuZy9PYmplY3Q7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACc3IAWWNvbS5nb29nbGUuY2xvdWQuc3RvcmFnZS5EZWZh\ + dWx0U3RvcmFnZVJldHJ5U3RyYXRlZ3kkRW1wdHlKc29uUGFyc2luZ0V4Y2VwdGlvbkludGVyY2VwdG9yz+LSc1EB+RsCAAB4cHNyAERjb20uZ29vZ2xlLmNsb3VkLnN0\ + b3JhZ2UuRGVmYXVsdFN0b3JhZ2VSZXRyeVN0cmF0ZWd5JEludGVyY2VwdG9ySW1wbElTPf0EVOdoAgACWgAKaWRlbXBvdGVudEwAD3JldHJ5YWJsZUVycm9yc3EAfgA9\ + eHABc3IANWNvbS5nb29nbGUuY29tbW9uLmNvbGxlY3QuSW1tdXRhYmxlU2V0JFNlcmlhbGl6ZWRGb3JtAAAAAAAAAAACAAFbAAhlbGVtZW50c3EAfgBBeHB1cQB+AEMA\ + AAAIc3IAK2NvbS5nb29nbGUuY2xvdWQuQmFzZVNlcnZpY2VFeGNlcHRpb24kRXJyb3LIN4LqhDNMpwIAA1oACHJlamVjdGVkTAAEY29kZXQAE0xqYXZhL2xhbmcvSW50\ + ZWdlcjtMAAZyZWFzb25xAH4ABnhwAHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAA\ + AAH4cHNxAH4ATABzcQB+AE8AAAH3cHNxAH4ATABzcQB+AE8AAAH2cHNxAH4ATABzcQB+AE8AAAH0cHNxAH4ATABzcQB+AE8AAAGtcHNxAH4ATABzcQB+AE8AAAGYcHNx\ + AH4ATABwdAANaW50ZXJuYWxFcnJvcnNxAH4ATABwdAAbY29ubmVjdGlvbkNsb3NlZFByZW1hdHVyZWx5c3EAfgBJdXEAfgBDAAAAAHEAfgBgc3IAEWphdmEudXRpbC5I\ + YXNoU2V0ukSFlZa4tzQDAAB4cHcMAAAAED9AAAAAAAAAeA== From a1a09f6134b5b64ed847de7d26d01a7467973e03 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Fri, 14 Jul 2023 08:23:13 -0700 Subject: [PATCH 05/20] chore: cleanup branch protection rules from feat/transfer-manager (#2119) --- .github/sync-repo-settings.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 898a8c5794..3a3a5fd9ef 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -112,22 +112,6 @@ branchProtectionRules: - cla/google - 'Kokoro - Test: Java GraalVM Native Image' - 'Kokoro - Test: Java 17 GraalVM Native Image' - - pattern: feat/transfer-manager - isAdminEnforced: true - requiredApprovingReviewCount: 1 - requiresCodeOwnerReviews: true - requiresStrictStatusChecks: false - requiredStatusCheckContexts: - - dependencies (8) - - dependencies (11) - - lint - - clirr - - units (8) - - units (11) - - 'Kokoro - Test: Integration' - - cla/google - - 'Kokoro - Test: Java GraalVM Native Image' - - 'Kokoro - Test: Java 17 GraalVM Native Image' - pattern: 2.22.x isAdminEnforced: true requiredApprovingReviewCount: 1 From ccd19e38e2b4c21062722e30a286dc042d6bfdac Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 14 Jul 2023 17:23:43 +0200 Subject: [PATCH 06/20] test(deps): update dependency com.google.cloud:google-cloud-kms to v2.24.0 (#2118) --- google-cloud-storage/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index d501ed0196..d85094f21c 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -179,7 +179,7 @@ com.google.cloud google-cloud-kms - 2.23.0 + 2.24.0 test From 889e433eed25c1fcaa185c7de0b27ca85c238b9e Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 14 Jul 2023 17:24:02 +0200 Subject: [PATCH 07/20] chore(deps): update dependency com.google.cloud:google-cloud-storage to v2.24.0 (#2115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update dependency com.google.cloud:google-cloud-storage to v2.24.0 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- README.md | 2 +- samples/install-without-bom/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1edd542d18..66fddeaa59 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-storage - 2.23.0 + 2.24.0 ``` diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 770c68667f..ee717f2505 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -30,7 +30,7 @@ com.google.cloud google-cloud-storage - 2.23.0 + 2.24.0 From c80505129baa831e492a5514e937875407211595 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Fri, 14 Jul 2023 16:08:05 -0400 Subject: [PATCH 08/20] fix: update GrpcStorageImpl.createFrom(BlobInfo, Path) to use RewindableContent (#2112) With the introduction of RewindableContent, we can now upload an entire file in a single WriteObjectRequest stream. * chore: rename RewindableHttpContent -> RewindableContent --- .../ApiaryUnbufferedWritableByteChannel.java | 6 +- .../cloud/storage/ByteSizeConstants.java | 3 + ...apicWritableByteChannelSessionBuilder.java | 19 ++- .../cloud/storage/GrpcResumableSession.java | 123 ++++++++++++++++++ .../google/cloud/storage/GrpcStorageImpl.java | 80 +++++------- .../cloud/storage/JsonResumableSession.java | 6 +- .../storage/JsonResumableSessionPutTask.java | 8 +- .../cloud/storage/ResumableSession.java | 27 ++-- ...ttpContent.java => RewindableContent.java} | 71 ++++++++-- .../com/google/cloud/storage/StorageImpl.java | 2 +- .../google/cloud/storage/FakeHttpServer.java | 7 + .../ITGrpcStorageImplUploadRetryTest.java | 14 +- .../ITJsonResumableSessionPutTaskTest.java | 64 +++++++-- .../storage/ITJsonResumableSessionTest.java | 10 +- .../RewindableByteBufferContentTest.java | 12 +- ...ava => RewindableContentPropertyTest.java} | 6 +- .../it/ITObjectChecksumSupportTest.java | 2 +- 17 files changed, 346 insertions(+), 114 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcResumableSession.java rename google-cloud-storage/src/main/java/com/google/cloud/storage/{RewindableHttpContent.java => RewindableContent.java} (72%) rename google-cloud-storage/src/test/java/com/google/cloud/storage/{RewindableHttpContentPropertyTest.java => RewindableContentPropertyTest.java} (98%) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java index 5fce18ae52..35ad97ffea 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java @@ -31,7 +31,7 @@ @ParametersAreNonnullByDefault final class ApiaryUnbufferedWritableByteChannel implements UnbufferedWritableByteChannel { - private final ResumableSession session; + private final JsonResumableSession session; private final SettableApiFuture result; private final LongConsumer committedBytesCallback; @@ -57,7 +57,7 @@ public long write(ByteBuffer[] srcs, int offset, int length) throws IOException if (!open) { throw new ClosedChannelException(); } - RewindableHttpContent content = RewindableHttpContent.of(Utils.subArray(srcs, offset, length)); + RewindableContent content = RewindableContent.of(Utils.subArray(srcs, offset, length)); long available = content.getLength(); long newFinalByteOffset = cumulativeByteCount + available; final HttpContentRange header; @@ -96,7 +96,7 @@ public void close() throws IOException { if (!finished) { try { ResumableOperationResult<@Nullable StorageObject> operationResult = - session.put(RewindableHttpContent.empty(), HttpContentRange.of(cumulativeByteCount)); + session.put(RewindableContent.empty(), HttpContentRange.of(cumulativeByteCount)); long persistedSize = operationResult.getPersistedSize(); committedBytesCallback.accept(persistedSize); result.set(operationResult.getObject()); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteSizeConstants.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteSizeConstants.java index 98b31a3ded..cbdbd94d67 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteSizeConstants.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteSizeConstants.java @@ -28,6 +28,9 @@ final class ByteSizeConstants { static final int _2MiB = 2 * _1MiB; static final int _16MiB = 16 * _1MiB; static final int _32MiB = 32 * _1MiB; + static final long _1GiB = 1024 * _1MiB; + static final long _1TiB = 1024 * _1GiB; + static final long _5TiB = 5 * _1TiB; static final long _128KiBL = 131072L; static final long _256KiBL = 262144L; diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicWritableByteChannelSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicWritableByteChannelSessionBuilder.java index e39bf85d33..80bca1c572 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicWritableByteChannelSessionBuilder.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicWritableByteChannelSessionBuilder.java @@ -20,6 +20,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; +import com.google.api.core.InternalApi; import com.google.api.core.SettableApiFuture; import com.google.api.gax.retrying.ResultRetryAlgorithm; import com.google.api.gax.rpc.ClientStreamingCallable; @@ -219,10 +220,12 @@ final class ResumableUploadBuilder { private RetryingDependencies deps; private ResultRetryAlgorithm alg; + private boolean fsyncEvery; ResumableUploadBuilder() { this.deps = RetryingDependencies.attemptOnce(); this.alg = Retrying.neverRetry(); + this.fsyncEvery = true; } ResumableUploadBuilder withRetryConfig(RetryingDependencies deps, ResultRetryAlgorithm alg) { @@ -231,6 +234,12 @@ ResumableUploadBuilder withRetryConfig(RetryingDependencies deps, ResultRetryAlg return this; } + @InternalApi + ResumableUploadBuilder setFsyncEvery(boolean fsyncEvery) { + this.fsyncEvery = fsyncEvery; + return this; + } + /** * Do not apply any intermediate buffering. Any call to {@link * java.nio.channels.WritableByteChannel#write(ByteBuffer)} will be segmented as is and sent to @@ -281,7 +290,10 @@ UnbufferedWritableByteChannelSession build() { return new UnbufferedWriteSession<>( requireNonNull(start, "start must be non null"), bindFunction( - WriteFlushStrategy.fsyncEveryFlush(write, deps, alg, Retrying::newCallContext), + fsyncEvery + ? WriteFlushStrategy.fsyncEveryFlush( + write, deps, alg, Retrying::newCallContext) + : WriteFlushStrategy.fsyncOnClose(write), ResumableWrite::identity) .andThen(StorageByteChannels.writable()::createSynchronized)); } @@ -310,7 +322,10 @@ BufferedWritableByteChannelSession build() { return new BufferedWriteSession<>( requireNonNull(start, "start must be non null"), bindFunction( - WriteFlushStrategy.fsyncEveryFlush(write, deps, alg, Retrying::newCallContext), + fsyncEvery + ? WriteFlushStrategy.fsyncEveryFlush( + write, deps, alg, Retrying::newCallContext) + : WriteFlushStrategy.fsyncOnClose(write), ResumableWrite::identity) .andThen(c -> new DefaultBufferedWritableByteChannel(bufferHandle, c)) .andThen(StorageByteChannels.writable()::createSynchronized)); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcResumableSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcResumableSession.java new file mode 100644 index 0000000000..8de44fb654 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcResumableSession.java @@ -0,0 +1,123 @@ +/* + * Copyright 2023 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; + +import com.google.api.core.ApiFutures; +import com.google.api.gax.grpc.GrpcCallContext; +import com.google.api.gax.retrying.ResultRetryAlgorithm; +import com.google.api.gax.rpc.ClientStreamingCallable; +import com.google.api.gax.rpc.UnaryCallable; +import com.google.cloud.storage.BufferedWritableByteChannelSession.BufferedWritableByteChannel; +import com.google.cloud.storage.Conversions.Decoder; +import com.google.cloud.storage.Retrying.RetryingDependencies; +import com.google.storage.v2.Object; +import com.google.storage.v2.QueryWriteStatusRequest; +import com.google.storage.v2.QueryWriteStatusResponse; +import com.google.storage.v2.WriteObjectRequest; +import com.google.storage.v2.WriteObjectResponse; +import java.util.concurrent.atomic.AtomicBoolean; +import org.checkerframework.checker.nullness.qual.Nullable; + +final class GrpcResumableSession { + + private final RetryingDependencies deps; + private final ResultRetryAlgorithm alg; + private final ClientStreamingCallable writeCallable; + private final UnaryCallable + queryWriteStatusCallable; + private final ResumableWrite resumableWrite; + private final Hasher hasher; + + GrpcResumableSession( + RetryingDependencies deps, + ResultRetryAlgorithm alg, + ClientStreamingCallable writeCallable, + UnaryCallable queryWriteStatusCallable, + ResumableWrite resumableWrite, + Hasher hasher) { + this.deps = deps; + this.alg = alg; + this.writeCallable = writeCallable; + this.queryWriteStatusCallable = queryWriteStatusCallable; + this.resumableWrite = resumableWrite; + this.hasher = hasher; + } + + ResumableOperationResult<@Nullable Object> query() { + QueryWriteStatusRequest.Builder b = + QueryWriteStatusRequest.newBuilder().setUploadId(resumableWrite.getRes().getUploadId()); + if (resumableWrite.getReq().hasCommonObjectRequestParams()) { + b.setCommonObjectRequestParams(resumableWrite.getReq().getCommonObjectRequestParams()); + } + QueryWriteStatusRequest req = b.build(); + try { + QueryWriteStatusResponse response = queryWriteStatusCallable.call(req); + if (response.hasResource()) { + return ResumableOperationResult.complete( + response.getResource(), response.getResource().getSize()); + } else { + return ResumableOperationResult.incremental(response.getPersistedSize()); + } + } catch (Exception e) { + throw StorageException.coalesce(e); + } + } + + ResumableOperationResult<@Nullable Object> put(RewindableContent content) { + AtomicBoolean dirty = new AtomicBoolean(false); + GrpcCallContext retryingCallContext = Retrying.newCallContext(); + BufferHandle handle = BufferHandle.allocate(ByteSizeConstants._2MiB); + + return Retrying.run( + deps, + alg, + () -> { + if (dirty.getAndSet(true)) { + ResumableOperationResult<@Nullable Object> query = query(); + if (query.getObject() != null) { + return query; + } else { + content.rewindTo(query.getPersistedSize()); + } + } + WritableByteChannelSession session = + ResumableMedia.gapic() + .write() + .byteChannel(writeCallable.withDefaultCallContext(retryingCallContext)) + .setByteStringStrategy(ByteStringStrategy.copy()) + .setHasher(hasher) + .resumable() + .setFsyncEvery(false) + .buffered(handle) + .setStartAsync(ApiFutures.immediateFuture(resumableWrite)) + .build(); + + try (BufferedWritableByteChannel channel = session.open()) { + content.writeTo(channel); + } + + WriteObjectResponse response = session.getResult().get(); + if (response.hasResource()) { + return ResumableOperationResult.complete( + response.getResource(), response.getResource().getSize()); + } else { + return ResumableOperationResult.incremental(response.getPersistedSize()); + } + }, + Decoder.identity()); + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java index 14b4789901..d7d4059196 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java @@ -28,6 +28,7 @@ import static java.util.Objects.requireNonNull; import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; import com.google.api.core.BetaApi; import com.google.api.gax.grpc.GrpcCallContext; import com.google.api.gax.paging.AbstractPage; @@ -35,6 +36,7 @@ import com.google.api.gax.retrying.ResultRetryAlgorithm; import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.ApiExceptions; +import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.NotFoundException; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.UnaryCallable; @@ -72,6 +74,7 @@ import com.google.common.collect.Streams; import com.google.common.io.BaseEncoding; import com.google.common.io.ByteStreams; +import com.google.common.util.concurrent.MoreExecutors; import com.google.iam.v1.GetIamPolicyRequest; import com.google.iam.v1.SetIamPolicyRequest; import com.google.iam.v1.TestIamPermissionsRequest; @@ -123,7 +126,6 @@ import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; -import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.file.Files; import java.nio.file.OpenOption; @@ -138,6 +140,7 @@ import java.util.Spliterator; import java.util.Spliterators.AbstractSpliterator; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Predicate; @@ -285,55 +288,34 @@ public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOp opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); - long size = Files.size(path); - if (size < bufferSize) { - // ignore the bufferSize argument if the file is smaller than it - GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); - return Retrying.run( - getOptions(), - retryAlgorithmManager.getFor(req), - () -> { - BufferedWritableByteChannelSession session = - ResumableMedia.gapic() - .write() - .byteChannel(storageClient.writeObjectCallable().withDefaultCallContext(merge)) - .setHasher(Hasher.enabled()) - .setByteStringStrategy(ByteStringStrategy.noCopy()) - .direct() - .buffered(Buffers.allocate(size)) - .setRequest(req) - .build(); - - try (SeekableByteChannel src = Files.newByteChannel(path, READ_OPS); - BufferedWritableByteChannel dst = session.open()) { - ByteStreams.copy(src, dst); - } catch (Exception e) { - throw StorageException.coalesce(e); - } - return session.getResult(); - }, - this::getBlob); - } else { - ApiFuture start = startResumableWrite(grpcCallContext, req); - BufferedWritableByteChannelSession session = - ResumableMedia.gapic() - .write() - .byteChannel( - storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext)) - .setHasher(Hasher.noop()) - .setByteStringStrategy(ByteStringStrategy.noCopy()) - .resumable() - .withRetryConfig(getOptions(), retryAlgorithmManager.idempotent()) - .buffered(Buffers.allocateAligned(bufferSize, _256KiB)) - .setStartAsync(start) - .build(); - try (SeekableByteChannel src = Files.newByteChannel(path, READ_OPS); - BufferedWritableByteChannel dst = session.open()) { - ByteStreams.copy(src, dst); - } catch (Exception e) { - throw StorageException.coalesce(e); + ClientStreamingCallable write = + storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext); + + ApiFuture start = startResumableWrite(grpcCallContext, req); + ApiFuture session2 = + ApiFutures.transform( + start, + rw -> + ResumableSession.grpc( + getOptions(), + retryAlgorithmManager.idempotent(), + write, + storageClient.queryWriteStatusCallable(), + rw, + Hasher.noop()), + MoreExecutors.directExecutor()); + try { + GrpcResumableSession got = session2.get(); + ResumableOperationResult<@Nullable Object> put = got.put(RewindableContent.of(path)); + Object object = put.getObject(); + if (object == null) { + // if by some odd chance the put didn't get the Object, query for it + ResumableOperationResult<@Nullable Object> query = got.query(); + object = query.getObject(); } - return getBlob(session.getResult()); + return codecs.blobInfo().decode(object).asBlob(this); + } catch (InterruptedException | ExecutionException e) { + throw StorageException.coalesce(e); } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java index debd611b04..7cc2f74e89 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableSession.java @@ -26,7 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.checkerframework.checker.nullness.qual.Nullable; -final class JsonResumableSession extends ResumableSession { +final class JsonResumableSession { static final String SPAN_NAME_WRITE = String.format("Sent.%s.write", HttpStorageRpc.class.getName()); @@ -53,14 +53,12 @@ final class JsonResumableSession extends ResumableSession { * Not automatically retried. Usually called from within another retrying context. We don't yet * have the concept of nested retry handling. */ - @Override ResumableOperationResult<@Nullable StorageObject> query() { return new JsonResumableSessionQueryTask(context, resumableWrite.getUploadId()).call(); } - @Override ResumableOperationResult<@Nullable StorageObject> put( - RewindableHttpContent content, HttpContentRange contentRange) { + RewindableContent content, HttpContentRange contentRange) { JsonResumableSessionPutTask task = new JsonResumableSessionPutTask( context, resumableWrite.getUploadId(), content, contentRange); 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 de905fb48e..5abb43c16b 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 @@ -39,7 +39,7 @@ final class JsonResumableSessionPutTask private final HttpClientContext context; private final String uploadId; - private final RewindableHttpContent content; + private final RewindableContent content; private final HttpContentRange originalContentRange; private HttpContentRange contentRange; @@ -48,7 +48,7 @@ final class JsonResumableSessionPutTask JsonResumableSessionPutTask( HttpClientContext httpClientContext, String uploadId, - RewindableHttpContent content, + RewindableContent content, HttpContentRange originalContentRange) { this.context = httpClientContext; this.uploadId = uploadId; @@ -64,9 +64,9 @@ public void rewindTo(long offset) { long originalBegin = range.beginOffset(); long contentOffset = offset - originalBegin; Preconditions.checkArgument( - 0 <= contentOffset && contentOffset < content.getLength(), + 0 <= contentOffset && contentOffset < range.length(), "Rewind offset is out of bounds. (%s <= %s < %s)", - range.beginOffset(), + originalBegin, offset, range.endOffset()); content.rewindTo(contentOffset); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSession.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSession.java index 7b608c68a4..5c308f7fb9 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSession.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ResumableSession.java @@ -17,17 +17,17 @@ package com.google.cloud.storage; import com.google.api.gax.retrying.ResultRetryAlgorithm; +import com.google.api.gax.rpc.ClientStreamingCallable; +import com.google.api.gax.rpc.UnaryCallable; import com.google.cloud.storage.Retrying.RetryingDependencies; -import org.checkerframework.checker.nullness.qual.Nullable; +import com.google.storage.v2.QueryWriteStatusRequest; +import com.google.storage.v2.QueryWriteStatusResponse; +import com.google.storage.v2.WriteObjectRequest; +import com.google.storage.v2.WriteObjectResponse; -abstract class ResumableSession { +final class ResumableSession { - ResumableSession() {} - - abstract ResumableOperationResult<@Nullable T> put( - RewindableHttpContent content, HttpContentRange contentRange); - - abstract ResumableOperationResult<@Nullable T> query(); + private ResumableSession() {} static JsonResumableSession json( HttpClientContext context, @@ -36,4 +36,15 @@ static JsonResumableSession json( JsonResumableWrite resumableWrite) { return new JsonResumableSession(context, deps, alg, resumableWrite); } + + static GrpcResumableSession grpc( + RetryingDependencies deps, + ResultRetryAlgorithm alg, + ClientStreamingCallable writeCallable, + UnaryCallable queryWriteStatusCallable, + ResumableWrite resumableWrite, + Hasher hasher) { + return new GrpcResumableSession( + deps, alg, writeCallable, queryWriteStatusCallable, resumableWrite, hasher); + } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableContent.java similarity index 72% rename from google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java rename to google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableContent.java index 68bf4e22f0..166ccbc97d 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableHttpContent.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/RewindableContent.java @@ -25,6 +25,7 @@ import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.Channels; +import java.nio.channels.GatheringByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.file.Files; @@ -32,9 +33,9 @@ import java.nio.file.StandardOpenOption; import java.util.Arrays; -abstract class RewindableHttpContent extends AbstractHttpContent { +abstract class RewindableContent extends AbstractHttpContent { - private RewindableHttpContent() { + private RewindableContent() { super((HttpMediaType) null); } @@ -43,24 +44,28 @@ private RewindableHttpContent() { abstract void rewindTo(long offset); + abstract long writeTo(WritableByteChannel gbc) throws IOException; + + abstract long writeTo(GatheringByteChannel gbc) throws IOException; + @Override public final boolean retrySupported() { return false; } - static RewindableHttpContent empty() { + static RewindableContent empty() { return EmptyRewindableContent.INSTANCE; } - static RewindableHttpContent of(ByteBuffer... buffers) { - return new ByteBufferHttpContent(buffers); + static RewindableContent of(ByteBuffer... buffers) { + return new ByteBufferContent(buffers); } - static RewindableHttpContent of(Path path) throws IOException { - return new PathRewindableHttpContent(path); + static RewindableContent of(Path path) throws IOException { + return new PathRewindableContent(path); } - private static final class EmptyRewindableContent extends RewindableHttpContent { + private static final class EmptyRewindableContent extends RewindableContent { private static final EmptyRewindableContent INSTANCE = new EmptyRewindableContent(); @Override @@ -73,18 +78,28 @@ public void writeTo(OutputStream out) throws IOException { out.flush(); } + @Override + long writeTo(WritableByteChannel gbc) { + return 0; + } + + @Override + long writeTo(GatheringByteChannel gbc) { + return 0; + } + @Override protected void rewindTo(long offset) {} } - private static final class PathRewindableHttpContent extends RewindableHttpContent { + private static final class PathRewindableContent extends RewindableContent { private final Path path; private final long size; private long readOffset; - private PathRewindableHttpContent(Path path) throws IOException { + private PathRewindableContent(Path path) throws IOException { this.path = path; this.size = Files.size(path); this.readOffset = 0; @@ -110,9 +125,25 @@ public void writeTo(OutputStream out) throws IOException { out.flush(); } } + + @Override + long writeTo(WritableByteChannel gbc) throws IOException { + try (SeekableByteChannel in = Files.newByteChannel(path, StandardOpenOption.READ)) { + in.position(readOffset); + return ByteStreams.copy(in, gbc); + } + } + + @Override + long writeTo(GatheringByteChannel gbc) throws IOException { + try (SeekableByteChannel in = Files.newByteChannel(path, StandardOpenOption.READ)) { + in.position(readOffset); + return ByteStreams.copy(in, gbc); + } + } } - private static final class ByteBufferHttpContent extends RewindableHttpContent { + private static final class ByteBufferContent extends RewindableContent { private final ByteBuffer[] buffers; // keep an array of the positions in case we need to rewind them for retries @@ -126,7 +157,7 @@ private static final class ByteBufferHttpContent extends RewindableHttpContent { private long offset; - private ByteBufferHttpContent(ByteBuffer[] buffers) { + private ByteBufferContent(ByteBuffer[] buffers) { this.buffers = buffers; this.positions = Arrays.stream(buffers).mapToInt(Buffers::position).toArray(); this.totalLength = Arrays.stream(buffers).mapToLong(Buffer::remaining).sum(); @@ -148,6 +179,22 @@ public void writeTo(OutputStream out) throws IOException { out.flush(); } + @Override + long writeTo(WritableByteChannel gbc) throws IOException { + dirty = true; + int retVal = 0; + for (ByteBuffer buffer : buffers) { + retVal += gbc.write(buffer); + } + return retVal; + } + + @Override + long writeTo(GatheringByteChannel gbc) throws IOException { + dirty = true; + return gbc.write(buffers); + } + @Override void rewindTo(long offset) { Preconditions.checkArgument( diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index fc8601cb6c..fd387717b1 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -255,7 +255,7 @@ public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOp HttpContentRange contentRange = HttpContentRange.of(ByteRangeSpec.relativeLength(0L, size), size); ResumableOperationResult put = - session.put(RewindableHttpContent.of(path), contentRange); + session.put(RewindableContent.of(path), contentRange); // all exception translation is taken care of down in the JsonResumableSession StorageObject object = put.getObject(); if (object == null) { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java index be739ab188..cff9dc4696 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeHttpServer.java @@ -36,6 +36,7 @@ import io.grpc.netty.shaded.io.netty.channel.socket.nio.NioServerSocketChannel; import io.grpc.netty.shaded.io.netty.handler.codec.http.FullHttpResponse; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpHeaders; +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpObjectAggregator; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpRequest; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpServerCodec; import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpServerExpectContinueHandler; @@ -84,6 +85,12 @@ static FakeHttpServer of(HttpRequestHandler server) { protected void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); p.addLast(new HttpServerCodec()); + // Accept a request and content up to 100 MiB + // If we don't do this, sometimes the ordering on the wire will result in the server + // rejecting the request before the client has finished sending. + // While our client can handle this scenario and retry, it makes assertions more + // difficult due to the variability of request counts. + p.addLast(new HttpObjectAggregator(100 * 1024 * 1024)); p.addLast(new HttpServerExpectContinueHandler()); p.addLast(new Handler(server)); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcStorageImplUploadRetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcStorageImplUploadRetryTest.java index ac192a1ff5..12f1bd6b9a 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcStorageImplUploadRetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcStorageImplUploadRetryTest.java @@ -29,6 +29,8 @@ import com.google.storage.v2.ChecksummedData; import com.google.storage.v2.Object; import com.google.storage.v2.ObjectChecksums; +import com.google.storage.v2.QueryWriteStatusRequest; +import com.google.storage.v2.QueryWriteStatusResponse; import com.google.storage.v2.StartResumableWriteRequest; import com.google.storage.v2.StartResumableWriteResponse; import com.google.storage.v2.StorageGrpc.StorageImplBase; @@ -93,7 +95,7 @@ public void create_inputStream() throws Exception { @Test public void createFrom_path_smallerThanBufferSize() throws Exception { - Direct.FakeService service = Direct.FakeService.create(); + Resumable.FakeService service = Resumable.FakeService.create(); try (TmpFile tmpFile = DataGenerator.base64Characters().tempFile(baseDir, objectContentSize); FakeServer server = FakeServer.of(service); @@ -287,8 +289,16 @@ public void startResumableWrite( } } + @Override + public void queryWriteStatus( + QueryWriteStatusRequest request, + StreamObserver responseObserver) { + responseObserver.onNext(QueryWriteStatusResponse.newBuilder().setPersistedSize(0).build()); + responseObserver.onCompleted(); + } + // a bit of constructor lifecycle hackery to appease the compiler - // Even though the thing past to super() is a lazy function, the closing over of the outer + // Even though the thing passed to super() is a lazy function, the closing over of the outer // fields happens earlier than they are available. To side step this fact, we provide the // AtomicBoolean as a constructor argument which can be closed over without issue, and then // bind it to the class field after super(). diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java index 7ae68773d5..b7d9d6c74a 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionPutTaskTest.java @@ -17,6 +17,7 @@ package com.google.cloud.storage; import static com.google.cloud.storage.ByteSizeConstants._128KiBL; +import static com.google.cloud.storage.ByteSizeConstants._256KiB; import static com.google.cloud.storage.ByteSizeConstants._256KiBL; import static com.google.cloud.storage.ByteSizeConstants._512KiBL; import static com.google.cloud.storage.ByteSizeConstants._768KiBL; @@ -45,6 +46,7 @@ import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpResponseStatus; import java.math.BigInteger; import java.net.URI; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; @@ -98,7 +100,7 @@ public void emptyObjectHappyPath() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.empty(), + RewindableContent.empty(), HttpContentRange.of(ByteRangeSpec.explicitClosed(0L, 0L), 0)); ResumableOperationResult<@Nullable StorageObject> operationResult = task.call(); @@ -144,7 +146,7 @@ public void scenario9() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.empty(), + RewindableContent.empty(), HttpContentRange.of(ByteRangeSpec.explicitClosed(0L, 10L))); StorageException se = assertThrows(StorageException.class, task::call); @@ -191,7 +193,7 @@ public void scenario7() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.empty(), + RewindableContent.empty(), HttpContentRange.of(ByteRangeSpec.explicitClosed(0L, 10L))); StorageException se = assertThrows(StorageException.class, task::call); @@ -272,7 +274,7 @@ public void scenario1() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.of(tmpFile.getPath()), + RewindableContent.of(tmpFile.getPath()), HttpContentRange.of(ByteRangeSpec.explicit(0L, _256KiBL))); StorageException se = assertThrows(StorageException.class, task::call); @@ -341,7 +343,7 @@ public void scenario2() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.empty(), + RewindableContent.empty(), HttpContentRange.of(_256KiBL)); StorageException se = assertThrows(StorageException.class, task::call); @@ -410,7 +412,7 @@ public void scenario3() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.empty(), + RewindableContent.empty(), HttpContentRange.of(_512KiBL)); StorageException se = assertThrows(StorageException.class, task::call); @@ -488,7 +490,7 @@ public void scenario4() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.empty(), + RewindableContent.empty(), HttpContentRange.of(_256KiBL)); ResumableOperationResult<@Nullable StorageObject> operationResult = task.call(); @@ -570,7 +572,7 @@ public void scenario4_1() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.empty(), + RewindableContent.empty(), HttpContentRange.of(_512KiBL)); StorageException se = assertThrows(StorageException.class, task::call); @@ -650,7 +652,7 @@ public void scenario4_2() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.empty(), + RewindableContent.empty(), HttpContentRange.of(_128KiBL)); StorageException se = assertThrows(StorageException.class, task::call); @@ -728,7 +730,7 @@ public void scenario5() throws Exception { new JsonResumableSessionPutTask( httpClientContext, uploadUrl, - RewindableHttpContent.of(tmpFile.getPath()), + RewindableContent.of(tmpFile.getPath()), HttpContentRange.of(ByteRangeSpec.explicit(_512KiBL, _768KiBL))); StorageException se = assertThrows(StorageException.class, task::call); @@ -768,7 +770,7 @@ public void jsonParseFailure() throws Exception { JsonResumableSessionPutTask task = new JsonResumableSessionPutTask( - httpClientContext, uploadUrl, RewindableHttpContent.empty(), HttpContentRange.of(0)); + httpClientContext, uploadUrl, RewindableContent.empty(), HttpContentRange.of(0)); StorageException se = assertThrows(StorageException.class, task::call); // the parse error happens while trying to read the success object, make sure we raise it as @@ -803,7 +805,7 @@ public void jsonDeserializationOnlyAttemptedWhenContentPresent() throws Exceptio JsonResumableSessionPutTask task = new JsonResumableSessionPutTask( - httpClientContext, uploadUrl, RewindableHttpContent.empty(), HttpContentRange.of(0)); + httpClientContext, uploadUrl, RewindableContent.empty(), HttpContentRange.of(0)); ResumableOperationResult<@Nullable StorageObject> operationResult = task.call(); StorageObject call = operationResult.getObject(); @@ -814,7 +816,7 @@ public void jsonDeserializationOnlyAttemptedWhenContentPresent() throws Exceptio @Test public void attemptToRewindOutOfBoundsThrows_lower() { - RewindableHttpContent content = RewindableHttpContent.of(); + RewindableContent content = RewindableContent.of(); JsonResumableSessionPutTask task = new JsonResumableSessionPutTask( null, null, content, HttpContentRange.of(ByteRangeSpec.relativeLength(10L, 10L))); @@ -826,7 +828,7 @@ public void attemptToRewindOutOfBoundsThrows_lower() { @Test public void attemptToRewindOutOfBoundsThrows_upper() { - RewindableHttpContent content = RewindableHttpContent.of(); + RewindableContent content = RewindableContent.of(); JsonResumableSessionPutTask task = new JsonResumableSessionPutTask( null, null, content, HttpContentRange.of(ByteRangeSpec.relativeLength(10L, 10L))); @@ -835,4 +837,38 @@ public void attemptToRewindOutOfBoundsThrows_upper() { assertThrows(IllegalArgumentException.class, () -> task.rewindTo(20)); assertThat(iae).hasMessageThat().isEqualTo("Rewind offset is out of bounds. (10 <= 20 < 20)"); } + + @Test + public void repeatedRewindsToTheSameLocationWork() { + ByteBuffer buf1 = DataGenerator.base64Characters().genByteBuffer(_256KiB); + ByteBuffer buf2 = DataGenerator.base64Characters().genByteBuffer(_256KiB); + RewindableContent content = RewindableContent.of(buf1, buf2); + JsonResumableSessionPutTask task = + new JsonResumableSessionPutTask( + null, null, content, HttpContentRange.of(ByteRangeSpec.relativeLength(0L, _512KiBL))); + + task.rewindTo(0); + assertThat(buf1.position()).isEqualTo(0); + assertThat(buf2.position()).isEqualTo(0); + + int last = buf1.capacity(); + buf1.position(last); + buf2.position(last); + + task.rewindTo(_256KiBL); + assertThat(buf1.remaining()).isEqualTo(0); + assertThat(buf2.position()).isEqualTo(0); + + task.rewindTo(_256KiBL); + assertThat(buf1.remaining()).isEqualTo(0); + assertThat(buf2.position()).isEqualTo(0); + + task.rewindTo(_256KiBL + 13); + assertThat(buf1.remaining()).isEqualTo(0); + assertThat(buf2.position()).isEqualTo(13); + + task.rewindTo(_256KiBL + 13); + assertThat(buf1.remaining()).isEqualTo(0); + assertThat(buf2.position()).isEqualTo(13); + } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java index 1cc917e345..108e6d6de4 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java @@ -119,7 +119,7 @@ public void rewindWillQueryStatusOnlyWhenDirty() throws Exception { httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); ResumableOperationResult<@Nullable StorageObject> operationResult = - session.put(RewindableHttpContent.of(tmpFile.getPath()), range1); + session.put(RewindableContent.of(tmpFile.getPath()), range1); StorageObject call = operationResult.getObject(); assertThat(call).isNull(); assertThat(operationResult.getPersistedSize()).isEqualTo(_512KiBL); @@ -173,13 +173,13 @@ public void retryAttemptWillReturnQueryResultIfPersistedSizeMatchesSpecifiedEndO httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); ResumableOperationResult<@Nullable StorageObject> operationResult1 = - session.put(RewindableHttpContent.of(buf1), range1); + session.put(RewindableContent.of(buf1), range1); StorageObject call1 = operationResult1.getObject(); assertThat(call1).isNull(); assertThat(operationResult1.getPersistedSize()).isEqualTo(_512KiBL); ResumableOperationResult<@Nullable StorageObject> operationResult2 = - session.put(RewindableHttpContent.of(buf2), range3); + session.put(RewindableContent.of(buf2), range3); StorageObject call2 = operationResult2.getObject(); assertThat(call2).isNull(); assertThat(operationResult2.getPersistedSize()).isEqualTo(_768KiBL); @@ -240,13 +240,13 @@ public void rewindOfContentIsRelativeToItsBeginOffsetOfTheOverallObject() throws httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); ResumableOperationResult<@Nullable StorageObject> operationResult1 = - session.put(RewindableHttpContent.of(buf1), range1); + session.put(RewindableContent.of(buf1), range1); StorageObject call1 = operationResult1.getObject(); assertThat(call1).isNull(); assertThat(operationResult1.getPersistedSize()).isEqualTo(_512KiBL); ResumableOperationResult<@Nullable StorageObject> operationResult2 = - session.put(RewindableHttpContent.of(buf2), range2); + session.put(RewindableContent.of(buf2), range2); StorageObject call2 = operationResult2.getObject(); assertThat(call2).isNull(); assertThat(operationResult2.getPersistedSize()).isEqualTo(_768KiBL); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableByteBufferContentTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableByteBufferContentTest.java index 9d65a00dcb..fe867e6f2f 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableByteBufferContentTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableByteBufferContentTest.java @@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; -import com.google.cloud.storage.RewindableHttpContentPropertyTest.ErroringOutputStream; +import com.google.cloud.storage.RewindableContentPropertyTest.ErroringOutputStream; import com.google.protobuf.ByteString; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -61,7 +61,7 @@ public void setUp() throws Exception { @Test public void getLength() { - RewindableHttpContent content = RewindableHttpContent.of(buffers); + RewindableContent content = RewindableContent.of(buffers); assertThat(content.getLength()).isEqualTo(total); } @@ -69,7 +69,7 @@ public void getLength() { @Test public void writeTo() throws IOException { - RewindableHttpContent content = RewindableHttpContent.of(buffers); + RewindableContent content = RewindableContent.of(buffers); ByteArrayOutputStream baos = new ByteArrayOutputStream(); content.writeTo(baos); @@ -81,7 +81,7 @@ public void writeTo() throws IOException { @Test public void rewind() throws IOException { - RewindableHttpContent content = RewindableHttpContent.of(buffers); + RewindableContent content = RewindableContent.of(buffers); assertThrows( IOException.class, @@ -100,7 +100,7 @@ public void rewind() throws IOException { @Test public void rewindTo() throws Exception { - RewindableHttpContent content = RewindableHttpContent.of(buffers); + RewindableContent content = RewindableContent.of(buffers); ByteString reduce = Arrays.stream(buffers) @@ -135,7 +135,7 @@ public void rewind_dirtyAware() throws IOException { int position = buf.position(); int limit = buf.limit(); - RewindableHttpContent content = RewindableHttpContent.of(buf); + RewindableContent content = RewindableContent.of(buf); int hackPosition = 2; // after content has initialized, mutate the position underneath it. We're doing this to detect // if rewind is actually modifying things. It shouldn't until the content is dirtied by calling diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableContentPropertyTest.java similarity index 98% rename from google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java rename to google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableContentPropertyTest.java index f8e18f2866..48d29bc8c8 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableHttpContentPropertyTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/RewindableContentPropertyTest.java @@ -41,12 +41,12 @@ import net.jqwik.api.RandomDistribution; import org.checkerframework.checker.nullness.qual.NonNull; -final class RewindableHttpContentPropertyTest { +final class RewindableContentPropertyTest { @Property void path(@ForAll("PathScenario") PathScenario pathScenario) throws Exception { try (PathScenario s = pathScenario) { - RewindableHttpContent content = RewindableHttpContent.of(s.getPath()); + RewindableContent content = RewindableContent.of(s.getPath()); assertThrows( IOException.class, () -> { @@ -68,7 +68,7 @@ void path(@ForAll("PathScenario") PathScenario pathScenario) throws Exception { @Property void byteBuffers(@ForAll("ByteBuffersScenario") ByteBuffersScenario s) throws IOException { - RewindableHttpContent content = RewindableHttpContent.of(s.getBuffers()); + RewindableContent content = RewindableContent.of(s.getBuffers()); assertThat(content.getLength()).isEqualTo(s.getFullLength()); assertThrows( IOException.class, diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectChecksumSupportTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectChecksumSupportTest.java index d8e026cbc0..12bbf3df5d 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectChecksumSupportTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectChecksumSupportTest.java @@ -54,7 +54,7 @@ @RunWith(StorageITRunner.class) @CrossRun( - transports = {Transport.HTTP /*, Transport.GRPC*/}, + transports = {Transport.HTTP, Transport.GRPC}, backends = Backend.PROD) @Parameterized(ChecksummedTestContentProvider.class) public final class ITObjectChecksumSupportTest { From c099a2f4f8ea9afa6953270876653916b021fd9f Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 18 Jul 2023 15:05:23 -0400 Subject: [PATCH 09/20] fix: update BlobWriteChannelV2 to properly carry forward offset after incremental flush (#2125) The tests in ITObjectTest did not perform a flush before capturing the write channel and this flow was not previously validated. --- .../ApiaryUnbufferedWritableByteChannel.java | 7 ++-- .../cloud/storage/BlobWriteChannel.java | 2 +- .../cloud/storage/BlobWriteChannelV2.java | 4 +++ .../storage/JsonResumableSessionPutTask.java | 2 +- .../cloud/storage/JsonResumableWrite.java | 36 ++++++++++++++----- .../com/google/cloud/storage/StorageImpl.java | 6 ++-- .../storage/ITJsonResumableSessionTest.java | 9 +++-- .../cloud/storage/SerializationTest.java | 3 +- .../storage/it/ITBlobWriteChannelTest.java | 36 +++++++++++++++++++ 9 files changed, 86 insertions(+), 19 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java index 35ad97ffea..8201fbc67d 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ApiaryUnbufferedWritableByteChannel.java @@ -36,9 +36,9 @@ final class ApiaryUnbufferedWritableByteChannel implements UnbufferedWritableByt private final SettableApiFuture result; private final LongConsumer committedBytesCallback; - private boolean open = true; + private boolean open; private long cumulativeByteCount; - private boolean finished = false; + private boolean finished; ApiaryUnbufferedWritableByteChannel( HttpClientContext httpClientContext, @@ -50,6 +50,9 @@ final class ApiaryUnbufferedWritableByteChannel implements UnbufferedWritableByt this.session = ResumableSession.json(httpClientContext, deps, alg, resumableWrite); this.result = result; this.committedBytesCallback = committedBytesCallback; + this.open = true; + this.cumulativeByteCount = resumableWrite.getBeginOffset(); + this.finished = false; } @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java index 28c4b8d1e6..c74c94f4cd 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java @@ -82,7 +82,7 @@ public WriteChannel restore() { entity != null ? Conversions.apiary().blobInfo().encode(entity) : null; return new BlobWriteChannelV2.BlobWriteChannelV2State( (HttpStorageOptions) serviceOptions, - JsonResumableWrite.of(encode, ImmutableMap.of(), uploadId), + JsonResumableWrite.of(encode, ImmutableMap.of(), uploadId, position), position, isOpen, chunkSize, diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java index 1bb0742513..48bad4de6c 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannelV2.java @@ -107,6 +107,10 @@ static final class BlobWriteChannelV2State @Override public WriteChannel restore() { + JsonResumableWrite resumableWrite = this.resumableWrite; + if (position != null) { + resumableWrite = resumableWrite.withBeginOffset(position); + } BlobWriteChannelV2 channel = new BlobWriteChannelV2(BlobReadChannelContext.from(options), resumableWrite); if (chunkSize != null) { 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 5abb43c16b..5a4864996f 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 @@ -153,7 +153,7 @@ public void rewindTo(long offset) { success = true; //noinspection DataFlowIssue compareTo result will filter out actualSize == null return ResumableOperationResult.complete(storageObject, actualSize.longValue()); - } else if (compare < 0) { + } else if (compare > 0) { StorageException se = JsonResumableSessionFailureScenario.SCENARIO_4_1.toStorageException( uploadId, response, null, toString(storageObject)); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java index 4ec1fc8919..336ce0e477 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonResumableWrite.java @@ -16,6 +16,8 @@ package com.google.cloud.storage; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.api.services.storage.model.StorageObject; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.base.MoreObjects; @@ -41,6 +43,7 @@ final class JsonResumableWrite implements Serializable { @MonotonicNonNull private final String signedUrl; @NonNull private final String uploadId; + private final long beginOffset; private volatile String objectJson; @@ -48,17 +51,32 @@ private JsonResumableWrite( StorageObject object, Map options, String signedUrl, - @NonNull String uploadId) { + @NonNull String uploadId, + long beginOffset) { this.object = object; this.options = options; this.signedUrl = signedUrl; this.uploadId = uploadId; + this.beginOffset = beginOffset; } public @NonNull String getUploadId() { return uploadId; } + public long getBeginOffset() { + return beginOffset; + } + + public JsonResumableWrite withBeginOffset(long newBeginOffset) { + checkArgument( + newBeginOffset >= beginOffset, + "New beginOffset must be >= existing beginOffset (%s >= %s)", + newBeginOffset, + beginOffset); + return new JsonResumableWrite(object, options, signedUrl, uploadId, newBeginOffset); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -68,15 +86,16 @@ public boolean equals(Object o) { return false; } JsonResumableWrite that = (JsonResumableWrite) o; - return Objects.equals(object, that.object) + return beginOffset == that.beginOffset + && Objects.equals(object, that.object) && Objects.equals(options, that.options) && Objects.equals(signedUrl, that.signedUrl) - && uploadId.equals(that.uploadId); + && Objects.equals(uploadId, that.uploadId); } @Override public int hashCode() { - return Objects.hash(object, options, signedUrl, uploadId); + return Objects.hash(object, options, signedUrl, uploadId, beginOffset); } @Override @@ -86,6 +105,7 @@ public String toString() { .add("options", options) .add("signedUrl", signedUrl) .add("uploadId", uploadId) + .add("beginOffset", beginOffset) .toString(); } @@ -112,11 +132,11 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE } static JsonResumableWrite of( - StorageObject req, Map options, String uploadId) { - return new JsonResumableWrite(req, options, null, uploadId); + StorageObject req, Map options, String uploadId, long beginOffset) { + return new JsonResumableWrite(req, options, null, uploadId, beginOffset); } - static JsonResumableWrite of(String signedUrl, String uploadId) { - return new JsonResumableWrite(null, null, signedUrl, uploadId); + static JsonResumableWrite of(String signedUrl, String uploadId, long beginOffset) { + return new JsonResumableWrite(null, null, signedUrl, uploadId, beginOffset); } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index fd387717b1..d4ad26015f 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -243,7 +243,7 @@ public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOp optionsMap, retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get()); + JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); JsonResumableSession session = ResumableSession.json( @@ -671,7 +671,7 @@ public StorageWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) optionsMap, retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get()); + JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); } @@ -688,7 +688,7 @@ public StorageWriteChannel writer(URL signedURL) { ResumableMedia.startUploadForSignedUrl( getOptions(), signedURL, forResumableUploadSessionCreate); JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(signedUrlString, uploadIdSupplier.get()); + JsonResumableWrite.of(signedUrlString, uploadIdSupplier.get(), 0); return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java index 108e6d6de4..7336749dbb 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITJsonResumableSessionTest.java @@ -113,7 +113,8 @@ public void rewindWillQueryStatusOnlyWhenDirty() throws Exception { URI endpoint = fakeHttpServer.getEndpoint(); String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); - JsonResumableWrite resumableWrite = JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl); + JsonResumableWrite resumableWrite = + JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl, 0); JsonResumableSession session = new JsonResumableSession( httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); @@ -167,7 +168,8 @@ public void retryAttemptWillReturnQueryResultIfPersistedSizeMatchesSpecifiedEndO URI endpoint = fakeHttpServer.getEndpoint(); String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); - JsonResumableWrite resumableWrite = JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl); + JsonResumableWrite resumableWrite = + JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl, 0); JsonResumableSession session = new JsonResumableSession( httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); @@ -234,7 +236,8 @@ public void rewindOfContentIsRelativeToItsBeginOffsetOfTheOverallObject() throws URI endpoint = fakeHttpServer.getEndpoint(); String uploadUrl = String.format("%s/upload/%s", endpoint.toString(), UUID.randomUUID()); - JsonResumableWrite resumableWrite = JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl); + JsonResumableWrite resumableWrite = + JsonResumableWrite.of(null, ImmutableMap.of(), uploadUrl, 0); JsonResumableSession session = new JsonResumableSession( httpClientContext, RETRYING_DEPENDENCIES, RETRY_ALGORITHM, resumableWrite); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java index 1e5eda2d44..87b88a78b8 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java @@ -216,7 +216,8 @@ protected Restorable[] restorableObjects() { JsonResumableWrite.of( Conversions.apiary().blobInfo().encode(BlobInfo.newBuilder("b", "n").build()), ImmutableMap.of(), - "upload-id")); + "upload-id", + 0)); return new Restorable[] {readerV2, writer}; } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java index 9b4ab36689..338f2d9be7 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBlobWriteChannelTest.java @@ -16,6 +16,7 @@ package com.google.cloud.storage.it; +import static com.google.cloud.storage.TestUtils.xxd; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; @@ -27,6 +28,7 @@ import com.google.api.client.json.JsonParser; import com.google.api.gax.rpc.FixedHeaderProvider; import com.google.cloud.NoCredentials; +import com.google.cloud.RestorableState; import com.google.cloud.WriteChannel; import com.google.cloud.conformance.storage.v1.InstructionList; import com.google.cloud.conformance.storage.v1.Method; @@ -53,6 +55,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.Optional; import java.util.logging.Logger; import org.junit.Test; @@ -153,6 +156,39 @@ public void changeChunkSizeAfterWrite() throws IOException { } } + @Test + public void restoreProperlyPlumbsBeginOffset() throws IOException { + BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); + int _256KiB = 256 * 1024; + + byte[] bytes1 = DataGenerator.base64Characters().genBytes(_256KiB); + byte[] bytes2 = DataGenerator.base64Characters().genBytes(73); + + int allLength = bytes1.length + bytes2.length; + byte[] expected = Arrays.copyOf(bytes1, allLength); + System.arraycopy(bytes2, 0, expected, bytes1.length, bytes2.length); + String xxdExpected = xxd(expected); + + RestorableState capture; + { + WriteChannel writer = storage.writer(info, BlobWriteOption.doesNotExist()); + writer.setChunkSize(_256KiB); + writer.write(ByteBuffer.wrap(bytes1)); + // explicitly do not close writer, it will finalize the session + capture = writer.capture(); + } + + assertThat(capture).isNotNull(); + WriteChannel restored = capture.restore(); + restored.write(ByteBuffer.wrap(bytes2)); + restored.close(); + + byte[] readAllBytes = storage.readAllBytes(info.getBlobId()); + assertThat(readAllBytes).hasLength(expected.length); + String xxdActual = xxd(readAllBytes); + assertThat(xxdActual).isEqualTo(xxdExpected); + } + private void doJsonUnexpectedEOFTest(int contentSize, int cappedByteCount) throws IOException { String blobPath = String.format("%s/%s/blob", generator.randomObjectName(), NOW_STRING); From 31ecab7897767a624ef8882969a2132696a6dcdb Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 18 Jul 2023 21:36:54 +0200 Subject: [PATCH 10/20] test(deps): update cross product test dependencies (#2124) --- google-cloud-storage/pom.xml | 2 +- pom.xml | 2 +- samples/install-without-bom/pom.xml | 2 +- samples/native-image-sample/pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- samples/snippets/pom.xml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index d85094f21c..b7d53b478d 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -16,7 +16,7 @@ google-cloud-storage - 1.105.17 + 1.105.18 5.9.3 diff --git a/pom.xml b/pom.xml index af3f0ffde2..61e942eed2 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ com.google.cloud google-cloud-pubsub - 1.123.17 + 1.123.18 test diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index ee717f2505..7b16d416c3 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -61,7 +61,7 @@ com.google.cloud google-cloud-pubsub - 1.123.17 + 1.123.18 test diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml index 841e4a1846..80a4593b8b 100644 --- a/samples/native-image-sample/pom.xml +++ b/samples/native-image-sample/pom.xml @@ -61,7 +61,7 @@ com.google.cloud google-cloud-pubsub - 1.123.17 + 1.123.18 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 3723a7f2eb..7b23b22571 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -52,7 +52,7 @@ com.google.cloud google-cloud-pubsub - 1.123.17 + 1.123.18 test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 3f47056d67..becb9489d4 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -72,7 +72,7 @@ com.google.cloud google-cloud-pubsub - 1.123.17 + 1.123.18 test From de80b6688b92724a77fdd4cb62fc93eaa3cbb3db Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 19 Jul 2023 23:43:45 +0200 Subject: [PATCH 11/20] build(deps): update dependency com.google.cloud:google-cloud-shared-config to v1.5.7 (#2128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build(deps): update dependency com.google.cloud:google-cloud-shared-config to v1.5.7 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- README.md | 2 +- google-cloud-storage-bom/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 66fddeaa59..24d19baaf2 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ If you are using Maven without the BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.18.0') +implementation platform('com.google.cloud:libraries-bom:26.19.0') implementation 'com.google.cloud:google-cloud-storage' ``` diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index 1d0fd5c6a7..bd99c27630 100644 --- a/google-cloud-storage-bom/pom.xml +++ b/google-cloud-storage-bom/pom.xml @@ -24,7 +24,7 @@ com.google.cloud google-cloud-shared-config - 1.5.6 + 1.5.7 diff --git a/pom.xml b/pom.xml index 61e942eed2..cb3a1f77fc 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ com.google.cloud google-cloud-shared-config - 1.5.6 + 1.5.7 From fe9662d7e552aabfc9012e582ae634f46af1f255 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Thu, 20 Jul 2023 13:11:52 -0400 Subject: [PATCH 12/20] docs: fix broken link for TESTING.md (#2126) Fixes b/290932427 --- TESTING.md | 25 +++++++++++++++++++ .../cloud/storage/testing/package-info.java | 5 ++-- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 TESTING.md diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000000..56885a2df3 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,25 @@ +## Testing code that uses Google Cloud Storage + +`RemoteStorageHelper` contains convenience methods to make setting up and cleaning up the test buckets easier. To use this class: + +1. Create a test Google Cloud project. + +2. Download a JSON service account credentials file from the Google Developer's Console. See more about this on the [Google Cloud Platform Storage Authentication page][cloud-platform-storage-authentication]. + +3. Create a `RemoteStorageHelper` object using your project ID and JSON key. + Here is an example that uses the `RemoteStorageHelper` to create a bucket. + ```java + RemoteStorageHelper helper = + RemoteStorageHelper.create(PROJECT_ID, new FileInputStream("/path/to/my/JSON/key.json")); + Storage storage = helper.getOptions().getService(); + String bucket = RemoteStorageHelper.generateBucketName(); + storage.create(BucketInfo.of(bucket)); + ``` + +4. Run your tests. + +5. Clean up the test project by using `forceDelete` to clear any buckets used. + Here is an example that clears the bucket created in Step 3 with a timeout of 5 seconds. + ```java + RemoteStorageHelper.forceDelete(storage, bucket, 5, TimeUnit.SECONDS); + ``` diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/package-info.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/package-info.java index 096e189de1..ae3f262846 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/package-info.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/package-info.java @@ -34,8 +34,7 @@ * RemoteStorageHelper.forceDelete(storage, bucket, 5, TimeUnit.SECONDS); * } * - * @see - * Google Cloud Java tools for testing + * @see Google Cloud + * Storage testing */ package com.google.cloud.storage.testing; From e16595eb087b6adaa04746e6467f596603199d5b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 20 Jul 2023 19:19:38 +0200 Subject: [PATCH 13/20] chore(deps): update dependency com.google.cloud:libraries-bom to v26.19.0 (#2127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update dependency com.google.cloud:libraries-bom to v26.19.0 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- README.md | 2 +- samples/native-image-sample/pom.xml | 2 +- samples/snippets/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 24d19baaf2..ec3dc8c51a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file: com.google.cloud libraries-bom - 26.18.0 + 26.19.0 pom import diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml index 80a4593b8b..785ecae233 100644 --- a/samples/native-image-sample/pom.xml +++ b/samples/native-image-sample/pom.xml @@ -29,7 +29,7 @@ com.google.cloud libraries-bom - 26.18.0 + 26.19.0 pom import diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index becb9489d4..22fdfdb195 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -31,7 +31,7 @@ com.google.cloud libraries-bom - 26.18.0 + 26.19.0 pom import From 9e8b6d324bfef84e2c2ee93c424b2e7fcb601945 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 20 Jul 2023 20:37:04 +0200 Subject: [PATCH 14/20] deps(test): update gcr.io/cloud-devrel-public-resources/storage-testbench docker tag to v0.37.0 (#2130) * test: re-enable tests Fixes #2114 --------- Co-authored-by: BenWhitehead --- .../conformance/retry/RpcMethodMappings.java | 16 ++-------------- .../cloud/storage/it/runner/registry/Dockerfile | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java index 7a87f33048..cc14b21e37 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java @@ -20,7 +20,6 @@ import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.notificationSetup; import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.pubsubTopicSetup; import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.serviceAccount; -import static com.google.cloud.storage.conformance.retry.ITRetryConformanceTest.RetryTestCaseResolver.instructionsAre; import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.not; import static com.google.common.truth.Truth.assertThat; @@ -127,11 +126,6 @@ final class RpcMethodMappings { static final int _2MiB = 2 * 1024 * 1024; private static final ImmutableMap MODIFY = ImmutableMap.of("a", "b"); - private static final CtxFunction skipUntil2114Fixed = - temporarilySkipMapping( - "Skipped until https://github.com/googleapis/java-storage/issues/2114 is fixed", - instructionsAre("return-503-after-8192K", "return-408") - .or(instructionsAre("return-503-after-256K"))); final Multimap funcMap; RpcMethodMappings() { @@ -1534,10 +1528,7 @@ private static void insert(ArrayList a) { a.add( RpcMethodMapping.newBuilder(50, objects.insert) .withApplicable(TestRetryConformance::isPreconditionsProvided) - .withSetup( - defaultSetup - .andThen(Local.blobInfoWithGenerationZero) - .compose(skipUntil2114Fixed)) + .withSetup(defaultSetup.andThen(Local.blobInfoWithGenerationZero)) .withTest( (ctx, c) -> ctx.map( @@ -1552,10 +1543,7 @@ private static void insert(ArrayList a) { a.add( RpcMethodMapping.newBuilder(51, objects.insert) .withApplicable(TestRetryConformance::isPreconditionsProvided) - .withSetup( - defaultSetup - .andThen(Local.blobInfoWithGenerationZero) - .compose(skipUntil2114Fixed)) + .withSetup(defaultSetup.andThen(Local.blobInfoWithGenerationZero)) .withTest( (ctx, c) -> ctx.map( diff --git a/google-cloud-storage/src/test/resources/com/google/cloud/storage/it/runner/registry/Dockerfile b/google-cloud-storage/src/test/resources/com/google/cloud/storage/it/runner/registry/Dockerfile index 35df4e1df3..0b793bc300 100644 --- a/google-cloud-storage/src/test/resources/com/google/cloud/storage/it/runner/registry/Dockerfile +++ b/google-cloud-storage/src/test/resources/com/google/cloud/storage/it/runner/registry/Dockerfile @@ -1 +1 @@ -FROM gcr.io/cloud-devrel-public-resources/storage-testbench:v0.36.0 +FROM gcr.io/cloud-devrel-public-resources/storage-testbench:v0.37.0 From a7e854ecb4d7fa9508a8d0844fc08d9eeab6f653 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 20 Jul 2023 20:37:47 +0200 Subject: [PATCH 15/20] deps: update dependency com.google.cloud:google-cloud-shared-dependencies to v3.13.1 (#2129) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cb3a1f77fc..8192d6eaba 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ UTF-8 github google-cloud-storage-parent - 3.13.0 + 3.13.1 From 9483222d832223de8b318ae7d6f5b51f0dcf96b5 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 21 Jul 2023 14:34:07 -0400 Subject: [PATCH 16/20] chore: Update the Java code generator (gapic-generator-java) to 2.23.1 (#2131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Update the Java code generator (gapic-generator-java) to 2.23.1 PiperOrigin-RevId: 549674836 Source-Link: https://github.com/googleapis/googleapis/commit/3b7a12146fed6cb648046439a2dad0b3ad56d202 Source-Link: https://github.com/googleapis/googleapis-gen/commit/2d643a4eff8c8ef4c9d5a2e59d2a0ca7c991eab6 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMmQ2NDNhNGVmZjhjOGVmNGM5ZDVhMmU1OWQyYTBjYTdjOTkxZWFiNiJ9 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../com.google.storage.v2/reflect-config.json | 2648 +++++++++++++++++ .../com/google/storage/v2/StorageGrpc.java | 2 +- .../java/com/google/storage/v2/Bucket.java | 2 +- .../storage/v2/BucketAccessControl.java | 2 +- .../v2/BucketAccessControlOrBuilder.java | 2 +- .../google/storage/v2/BucketOrBuilder.java | 2 +- .../v2/CancelResumableWriteRequest.java | 2 +- .../CancelResumableWriteRequestOrBuilder.java | 2 +- .../v2/CancelResumableWriteResponse.java | 2 +- ...CancelResumableWriteResponseOrBuilder.java | 2 +- .../google/storage/v2/ChecksummedData.java | 2 +- .../storage/v2/ChecksummedDataOrBuilder.java | 2 +- .../storage/v2/CommonObjectRequestParams.java | 2 +- .../CommonObjectRequestParamsOrBuilder.java | 2 +- .../storage/v2/ComposeObjectRequest.java | 2 +- .../v2/ComposeObjectRequestOrBuilder.java | 2 +- .../com/google/storage/v2/ContentRange.java | 2 +- .../storage/v2/ContentRangeOrBuilder.java | 2 +- .../storage/v2/CreateBucketRequest.java | 2 +- .../v2/CreateBucketRequestOrBuilder.java | 2 +- .../storage/v2/CreateHmacKeyRequest.java | 2 +- .../v2/CreateHmacKeyRequestOrBuilder.java | 2 +- .../storage/v2/CreateHmacKeyResponse.java | 2 +- .../v2/CreateHmacKeyResponseOrBuilder.java | 2 +- .../v2/CreateNotificationConfigRequest.java | 2 +- ...ateNotificationConfigRequestOrBuilder.java | 2 +- .../google/storage/v2/CustomerEncryption.java | 2 +- .../v2/CustomerEncryptionOrBuilder.java | 2 +- .../storage/v2/DeleteBucketRequest.java | 2 +- .../v2/DeleteBucketRequestOrBuilder.java | 2 +- .../storage/v2/DeleteHmacKeyRequest.java | 2 +- .../v2/DeleteHmacKeyRequestOrBuilder.java | 2 +- .../v2/DeleteNotificationConfigRequest.java | 2 +- ...eteNotificationConfigRequestOrBuilder.java | 2 +- .../storage/v2/DeleteObjectRequest.java | 2 +- .../v2/DeleteObjectRequestOrBuilder.java | 2 +- .../google/storage/v2/GetBucketRequest.java | 2 +- .../storage/v2/GetBucketRequestOrBuilder.java | 2 +- .../google/storage/v2/GetHmacKeyRequest.java | 2 +- .../v2/GetHmacKeyRequestOrBuilder.java | 2 +- .../v2/GetNotificationConfigRequest.java | 2 +- ...GetNotificationConfigRequestOrBuilder.java | 2 +- .../google/storage/v2/GetObjectRequest.java | 2 +- .../storage/v2/GetObjectRequestOrBuilder.java | 2 +- .../storage/v2/GetServiceAccountRequest.java | 2 +- .../v2/GetServiceAccountRequestOrBuilder.java | 2 +- .../google/storage/v2/HmacKeyMetadata.java | 2 +- .../storage/v2/HmacKeyMetadataOrBuilder.java | 2 +- .../google/storage/v2/ListBucketsRequest.java | 2 +- .../v2/ListBucketsRequestOrBuilder.java | 2 +- .../storage/v2/ListBucketsResponse.java | 2 +- .../v2/ListBucketsResponseOrBuilder.java | 2 +- .../storage/v2/ListHmacKeysRequest.java | 2 +- .../v2/ListHmacKeysRequestOrBuilder.java | 2 +- .../storage/v2/ListHmacKeysResponse.java | 2 +- .../v2/ListHmacKeysResponseOrBuilder.java | 2 +- .../v2/ListNotificationConfigsRequest.java | 2 +- ...stNotificationConfigsRequestOrBuilder.java | 2 +- .../v2/ListNotificationConfigsResponse.java | 2 +- ...tNotificationConfigsResponseOrBuilder.java | 2 +- .../google/storage/v2/ListObjectsRequest.java | 2 +- .../v2/ListObjectsRequestOrBuilder.java | 2 +- .../storage/v2/ListObjectsResponse.java | 2 +- .../v2/ListObjectsResponseOrBuilder.java | 2 +- .../v2/LockBucketRetentionPolicyRequest.java | 2 +- ...BucketRetentionPolicyRequestOrBuilder.java | 2 +- .../google/storage/v2/NotificationConfig.java | 2 +- .../v2/NotificationConfigOrBuilder.java | 2 +- .../java/com/google/storage/v2/Object.java | 2 +- .../storage/v2/ObjectAccessControl.java | 2 +- .../v2/ObjectAccessControlOrBuilder.java | 2 +- .../google/storage/v2/ObjectChecksums.java | 2 +- .../storage/v2/ObjectChecksumsOrBuilder.java | 2 +- .../google/storage/v2/ObjectOrBuilder.java | 2 +- .../java/com/google/storage/v2/Owner.java | 2 +- .../com/google/storage/v2/OwnerOrBuilder.java | 2 +- .../com/google/storage/v2/ProjectTeam.java | 2 +- .../storage/v2/ProjectTeamOrBuilder.java | 2 +- .../storage/v2/QueryWriteStatusRequest.java | 2 +- .../v2/QueryWriteStatusRequestOrBuilder.java | 2 +- .../storage/v2/QueryWriteStatusResponse.java | 2 +- .../v2/QueryWriteStatusResponseOrBuilder.java | 2 +- .../google/storage/v2/ReadObjectRequest.java | 2 +- .../v2/ReadObjectRequestOrBuilder.java | 2 +- .../google/storage/v2/ReadObjectResponse.java | 2 +- .../v2/ReadObjectResponseOrBuilder.java | 2 +- .../storage/v2/RewriteObjectRequest.java | 2 +- .../v2/RewriteObjectRequestOrBuilder.java | 2 +- .../google/storage/v2/RewriteResponse.java | 2 +- .../storage/v2/RewriteResponseOrBuilder.java | 2 +- .../com/google/storage/v2/ServiceAccount.java | 2 +- .../storage/v2/ServiceAccountOrBuilder.java | 2 +- .../google/storage/v2/ServiceConstants.java | 2 +- .../storage/v2/ServiceConstantsOrBuilder.java | 2 +- .../v2/StartResumableWriteRequest.java | 2 +- .../StartResumableWriteRequestOrBuilder.java | 2 +- .../v2/StartResumableWriteResponse.java | 2 +- .../StartResumableWriteResponseOrBuilder.java | 2 +- .../com/google/storage/v2/StorageProto.java | 2 +- .../storage/v2/UpdateBucketRequest.java | 2 +- .../v2/UpdateBucketRequestOrBuilder.java | 2 +- .../storage/v2/UpdateHmacKeyRequest.java | 2 +- .../v2/UpdateHmacKeyRequestOrBuilder.java | 2 +- .../storage/v2/UpdateObjectRequest.java | 2 +- .../v2/UpdateObjectRequestOrBuilder.java | 2 +- .../google/storage/v2/WriteObjectRequest.java | 2 +- .../v2/WriteObjectRequestOrBuilder.java | 2 +- .../storage/v2/WriteObjectResponse.java | 2 +- .../v2/WriteObjectResponseOrBuilder.java | 2 +- .../google/storage/v2/WriteObjectSpec.java | 2 +- .../storage/v2/WriteObjectSpecOrBuilder.java | 2 +- 111 files changed, 2758 insertions(+), 110 deletions(-) create mode 100644 gapic-google-cloud-storage-v2/src/main/resources/META-INF/native-image/com.google.storage.v2/reflect-config.json diff --git a/gapic-google-cloud-storage-v2/src/main/resources/META-INF/native-image/com.google.storage.v2/reflect-config.json b/gapic-google-cloud-storage-v2/src/main/resources/META-INF/native-image/com.google.storage.v2/reflect-config.json new file mode 100644 index 0000000000..8a8ab17145 --- /dev/null +++ b/gapic-google-cloud-storage-v2/src/main/resources/META-INF/native-image/com.google.storage.v2/reflect-config.json @@ -0,0 +1,2648 @@ +[ + { + "name": "com.google.api.ClientLibraryDestination", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ClientLibraryOrganization", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ClientLibrarySettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ClientLibrarySettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.CommonLanguageSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.CommonLanguageSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.CppSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.CppSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.CustomHttpPattern", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.CustomHttpPattern$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.DotnetSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.DotnetSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.FieldBehavior", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.GoSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.GoSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.Http", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.Http$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.HttpRule", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.HttpRule$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.JavaSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.JavaSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.LaunchStage", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.MethodSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.MethodSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.MethodSettings$LongRunning", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.MethodSettings$LongRunning$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.NodeSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.NodeSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.PhpSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.PhpSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.Publishing", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.Publishing$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.PythonSettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.PythonSettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ResourceDescriptor", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ResourceDescriptor$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ResourceDescriptor$History", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ResourceDescriptor$Style", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ResourceReference", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.ResourceReference$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.RoutingParameter", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.RoutingParameter$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.RoutingRule", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.RoutingRule$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.RubySettings", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.api.RubySettings$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.AuditConfig", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.AuditConfig$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.AuditConfigDelta", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.AuditConfigDelta$Action", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.AuditConfigDelta$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.AuditLogConfig", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.AuditLogConfig$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.AuditLogConfig$LogType", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.Binding", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.Binding$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.BindingDelta", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.BindingDelta$Action", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.BindingDelta$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.GetIamPolicyRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.GetIamPolicyRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.GetPolicyOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.GetPolicyOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.Policy", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.Policy$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.PolicyDelta", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.PolicyDelta$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.SetIamPolicyRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.SetIamPolicyRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.TestIamPermissionsRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.TestIamPermissionsRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.TestIamPermissionsResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.iam.v1.TestIamPermissionsResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ExtensionRange", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ExtensionRange$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ReservedRange", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ReservedRange$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$EnumReservedRange", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$EnumReservedRange$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueDescriptorProto", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueDescriptorProto$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Declaration", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Declaration$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$VerificationState", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Label", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Type", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$CType", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$JSType", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionRetention", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionTargetType", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorProto", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorProto$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorSet", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorSet$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileOptions$OptimizeMode", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$Semantic", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MessageOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MessageOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodDescriptorProto", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodDescriptorProto$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodOptions$IdempotencyLevel", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofDescriptorProto", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofDescriptorProto$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceDescriptorProto", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceDescriptorProto$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceOptions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceOptions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Location", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Location$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$NamePart", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$NamePart$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.Duration", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.Duration$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.Empty", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.Empty$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.FieldMask", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.FieldMask$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.Timestamp", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.protobuf.Timestamp$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Autoclass", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Autoclass$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Billing", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Billing$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Cors", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Cors$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$CustomPlacementConfig", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$CustomPlacementConfig$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Encryption", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Encryption$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$IamConfig", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$IamConfig$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$IamConfig$UniformBucketLevelAccess", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$IamConfig$UniformBucketLevelAccess$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Lifecycle", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Lifecycle$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Lifecycle$Rule", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Lifecycle$Rule$Action", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Lifecycle$Rule$Action$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Lifecycle$Rule$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Lifecycle$Rule$Condition", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Lifecycle$Rule$Condition$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Logging", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Logging$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$RetentionPolicy", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$RetentionPolicy$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Versioning", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Versioning$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Website", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Bucket$Website$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.BucketAccessControl", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.BucketAccessControl$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CancelResumableWriteRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CancelResumableWriteRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CancelResumableWriteResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CancelResumableWriteResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ChecksummedData", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ChecksummedData$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CommonObjectRequestParams", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CommonObjectRequestParams$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ComposeObjectRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ComposeObjectRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ComposeObjectRequest$SourceObject", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ComposeObjectRequest$SourceObject$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ComposeObjectRequest$SourceObject$ObjectPreconditions", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ComposeObjectRequest$SourceObject$ObjectPreconditions$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ContentRange", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ContentRange$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CreateBucketRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CreateBucketRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CreateHmacKeyRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CreateHmacKeyRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CreateHmacKeyResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CreateHmacKeyResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CreateNotificationConfigRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CreateNotificationConfigRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CustomerEncryption", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.CustomerEncryption$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.DeleteBucketRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.DeleteBucketRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.DeleteHmacKeyRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.DeleteHmacKeyRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.DeleteNotificationConfigRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.DeleteNotificationConfigRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.DeleteObjectRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.DeleteObjectRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetBucketRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetBucketRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetHmacKeyRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetHmacKeyRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetNotificationConfigRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetNotificationConfigRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetObjectRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetObjectRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetServiceAccountRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.GetServiceAccountRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.HmacKeyMetadata", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.HmacKeyMetadata$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListBucketsRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListBucketsRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListBucketsResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListBucketsResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListHmacKeysRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListHmacKeysRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListHmacKeysResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListHmacKeysResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListNotificationConfigsRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListNotificationConfigsRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListNotificationConfigsResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListNotificationConfigsResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListObjectsRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListObjectsRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListObjectsResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ListObjectsResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.LockBucketRetentionPolicyRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.LockBucketRetentionPolicyRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.NotificationConfig", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.NotificationConfig$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Object", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Object$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ObjectAccessControl", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ObjectAccessControl$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ObjectChecksums", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ObjectChecksums$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Owner", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.Owner$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ProjectTeam", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ProjectTeam$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.QueryWriteStatusRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.QueryWriteStatusRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.QueryWriteStatusResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.QueryWriteStatusResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ReadObjectRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ReadObjectRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ReadObjectResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ReadObjectResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.RewriteObjectRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.RewriteObjectRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.RewriteResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.RewriteResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ServiceAccount", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ServiceAccount$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ServiceConstants", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ServiceConstants$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.ServiceConstants$Values", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.StartResumableWriteRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.StartResumableWriteRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.StartResumableWriteResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.StartResumableWriteResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.UpdateBucketRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.UpdateBucketRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.UpdateHmacKeyRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.UpdateHmacKeyRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.UpdateObjectRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.UpdateObjectRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.WriteObjectRequest", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.WriteObjectRequest$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.WriteObjectResponse", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.WriteObjectResponse$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.WriteObjectSpec", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.storage.v2.WriteObjectSpec$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.type.Date", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.type.Date$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.type.Expr", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + }, + { + "name": "com.google.type.Expr$Builder", + "queryAllDeclaredConstructors": true, + "queryAllPublicConstructors": true, + "queryAllDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredClasses": true, + "allPublicClasses": true + } +] \ No newline at end of file diff --git a/grpc-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageGrpc.java b/grpc-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageGrpc.java index b8f55baf9c..979f20d958 100644 --- a/grpc-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageGrpc.java +++ b/grpc-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageGrpc.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Bucket.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Bucket.java index 00d1991f86..ab05c01fb6 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Bucket.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Bucket.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketAccessControl.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketAccessControl.java index 7c7d29c46b..ebb4a5a1f2 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketAccessControl.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketAccessControl.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketAccessControlOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketAccessControlOrBuilder.java index 6b7573f3dc..95779435cb 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketAccessControlOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketAccessControlOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketOrBuilder.java index f5f20e13d2..626f06ffe4 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/BucketOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteRequest.java index dc7c0173dc..c2aaa53c65 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteRequestOrBuilder.java index 11d2a69182..891dd597af 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteResponse.java index 4d7301e19a..3bf5e08167 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteResponseOrBuilder.java index f192ac86a8..59a085ee31 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CancelResumableWriteResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ChecksummedData.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ChecksummedData.java index 8c85c618b4..8284640922 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ChecksummedData.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ChecksummedData.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ChecksummedDataOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ChecksummedDataOrBuilder.java index 9b8aa6001b..cac4e13c5d 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ChecksummedDataOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ChecksummedDataOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CommonObjectRequestParams.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CommonObjectRequestParams.java index 54a47e276b..659a27f6c5 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CommonObjectRequestParams.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CommonObjectRequestParams.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CommonObjectRequestParamsOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CommonObjectRequestParamsOrBuilder.java index 327ae41411..9182e76351 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CommonObjectRequestParamsOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CommonObjectRequestParamsOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ComposeObjectRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ComposeObjectRequest.java index 6aede839b8..51000d502d 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ComposeObjectRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ComposeObjectRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ComposeObjectRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ComposeObjectRequestOrBuilder.java index 9e95be1591..07a5239e04 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ComposeObjectRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ComposeObjectRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ContentRange.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ContentRange.java index 1560e20aab..25119c1320 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ContentRange.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ContentRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ContentRangeOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ContentRangeOrBuilder.java index 969abe6d3b..da81df1ddf 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ContentRangeOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ContentRangeOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateBucketRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateBucketRequest.java index 82c72702bd..959131014f 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateBucketRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateBucketRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateBucketRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateBucketRequestOrBuilder.java index aafdbc708a..967621444a 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateBucketRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateBucketRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyRequest.java index 24e0a7b9e1..619a750496 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyRequestOrBuilder.java index 67b9cd539f..d35e121799 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyResponse.java index c1fcf5088f..d893e943dd 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyResponseOrBuilder.java index 0a77982ef8..1dbe0d5467 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateHmacKeyResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateNotificationConfigRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateNotificationConfigRequest.java index 1a3f99cec7..e6258c8719 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateNotificationConfigRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateNotificationConfigRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateNotificationConfigRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateNotificationConfigRequestOrBuilder.java index 73344ab699..1ef0465349 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateNotificationConfigRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CreateNotificationConfigRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CustomerEncryption.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CustomerEncryption.java index 27f9c05f5b..de15dd6a16 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CustomerEncryption.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CustomerEncryption.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CustomerEncryptionOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CustomerEncryptionOrBuilder.java index 42a2b1effc..4effd943b4 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CustomerEncryptionOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/CustomerEncryptionOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteBucketRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteBucketRequest.java index 3287d8c3df..420d964461 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteBucketRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteBucketRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteBucketRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteBucketRequestOrBuilder.java index 7c2e583d25..fa645400a8 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteBucketRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteBucketRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteHmacKeyRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteHmacKeyRequest.java index 408e9ff934..7361ab0de8 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteHmacKeyRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteHmacKeyRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteHmacKeyRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteHmacKeyRequestOrBuilder.java index 7d11ae9286..184aff6b63 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteHmacKeyRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteHmacKeyRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteNotificationConfigRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteNotificationConfigRequest.java index 95989710d6..1d2062f8b9 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteNotificationConfigRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteNotificationConfigRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteNotificationConfigRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteNotificationConfigRequestOrBuilder.java index 6098d96ff3..1f118d6166 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteNotificationConfigRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteNotificationConfigRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteObjectRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteObjectRequest.java index b3a9c7ccbe..d704ca0794 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteObjectRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteObjectRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteObjectRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteObjectRequestOrBuilder.java index c93ab4af16..f746d4410c 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteObjectRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/DeleteObjectRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetBucketRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetBucketRequest.java index 2238f8e0ae..6de2a5ae7d 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetBucketRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetBucketRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetBucketRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetBucketRequestOrBuilder.java index 3b0735f116..85cdbc5738 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetBucketRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetBucketRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetHmacKeyRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetHmacKeyRequest.java index c09b83335c..2ebd135484 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetHmacKeyRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetHmacKeyRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetHmacKeyRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetHmacKeyRequestOrBuilder.java index e7ba7a09bc..88d37d0e7f 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetHmacKeyRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetHmacKeyRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetNotificationConfigRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetNotificationConfigRequest.java index 4ad192896e..b29323e6d7 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetNotificationConfigRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetNotificationConfigRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetNotificationConfigRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetNotificationConfigRequestOrBuilder.java index 9a2f81896b..6005568821 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetNotificationConfigRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetNotificationConfigRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetObjectRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetObjectRequest.java index f89f58e8e8..2b87c35ae1 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetObjectRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetObjectRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetObjectRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetObjectRequestOrBuilder.java index fd35680f3a..d1a2614b4f 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetObjectRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetObjectRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetServiceAccountRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetServiceAccountRequest.java index aeb0778c74..14506aa3e4 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetServiceAccountRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetServiceAccountRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetServiceAccountRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetServiceAccountRequestOrBuilder.java index 74f04937a8..be3deb7152 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetServiceAccountRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/GetServiceAccountRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/HmacKeyMetadata.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/HmacKeyMetadata.java index fa7d9d7cfa..49e782f61a 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/HmacKeyMetadata.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/HmacKeyMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/HmacKeyMetadataOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/HmacKeyMetadataOrBuilder.java index 8dfc5ee628..9544912199 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/HmacKeyMetadataOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/HmacKeyMetadataOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsRequest.java index 3cf97c6082..fe4c7ff4e9 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsRequestOrBuilder.java index 0afd2226ce..c51a104f90 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsResponse.java index bf6dc24f68..ac23896f3c 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsResponseOrBuilder.java index cdc9f1f78e..926e975043 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListBucketsResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysRequest.java index e7815eefba..0349cec433 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysRequestOrBuilder.java index 3dbfdaea55..820453db94 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysResponse.java index 076cab3182..1dec8bbaa7 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysResponseOrBuilder.java index fc777c3430..39bc3f176f 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListHmacKeysResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsRequest.java index eab8c7183a..591c8ac417 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsRequestOrBuilder.java index f654e44dc8..67c71c39aa 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsResponse.java index 9ccb82351b..8f3d92f77d 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsResponseOrBuilder.java index f1fe02eeff..9c8e9954dd 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListNotificationConfigsResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsRequest.java index 0096b212a3..300a8dc1fd 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsRequestOrBuilder.java index 187cfa59cf..888aab23bd 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsResponse.java index 16c00c8954..3a75847a8f 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsResponseOrBuilder.java index 6fa2582940..53bdacfa40 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ListObjectsResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/LockBucketRetentionPolicyRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/LockBucketRetentionPolicyRequest.java index 3b6d80020c..8d04f29691 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/LockBucketRetentionPolicyRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/LockBucketRetentionPolicyRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/LockBucketRetentionPolicyRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/LockBucketRetentionPolicyRequestOrBuilder.java index 7a5eae42f8..0d0afef1a8 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/LockBucketRetentionPolicyRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/LockBucketRetentionPolicyRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfig.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfig.java index e94f8e124a..2199bda4c4 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfig.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfigOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfigOrBuilder.java index 8a65871053..7684b36963 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfigOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/NotificationConfigOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Object.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Object.java index 336e85ad65..6416b817d7 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Object.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Object.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectAccessControl.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectAccessControl.java index cd90c6035b..e5d8d2dcc8 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectAccessControl.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectAccessControl.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectAccessControlOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectAccessControlOrBuilder.java index 20ee2c6a38..ff444643f1 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectAccessControlOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectAccessControlOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectChecksums.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectChecksums.java index 0ed622e295..50e15ab395 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectChecksums.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectChecksums.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectChecksumsOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectChecksumsOrBuilder.java index f65c591723..81db876211 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectChecksumsOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectChecksumsOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectOrBuilder.java index 852b2160bb..e921910569 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ObjectOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Owner.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Owner.java index f46be86bb0..96b5a50330 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Owner.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/Owner.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/OwnerOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/OwnerOrBuilder.java index 4caffe13d6..0cfbeb1148 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/OwnerOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/OwnerOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectTeam.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectTeam.java index 5097014e8b..327cdef4fd 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectTeam.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectTeam.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectTeamOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectTeamOrBuilder.java index 3af551ee1e..bf4ae60213 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectTeamOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ProjectTeamOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusRequest.java index db5525627d..0a16b8cb86 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusRequestOrBuilder.java index f56814f69d..9807a260e6 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusResponse.java index cc6b965655..6fcbefe3d7 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusResponseOrBuilder.java index d793262ac1..31e1b79742 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/QueryWriteStatusResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectRequest.java index ea126599a9..fae2e1c5cc 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectRequestOrBuilder.java index 365dbdbce6..2ef4fe8a8e 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectResponse.java index f360288f1a..0150ff5e09 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectResponseOrBuilder.java index f8c717e1d4..420883d870 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ReadObjectResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteObjectRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteObjectRequest.java index d2127f0c50..9ad0ba000d 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteObjectRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteObjectRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteObjectRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteObjectRequestOrBuilder.java index 808c42ad55..dc5beddfce 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteObjectRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteObjectRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteResponse.java index fe54b1b6f0..ac31614a86 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteResponseOrBuilder.java index 3e38750e35..b556f3b6c9 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/RewriteResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceAccount.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceAccount.java index 0632bcab4b..20742ebc07 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceAccount.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceAccount.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceAccountOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceAccountOrBuilder.java index d6637ee207..880c17100e 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceAccountOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceAccountOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceConstants.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceConstants.java index 7b0cc49b69..b80de230f6 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceConstants.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceConstants.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceConstantsOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceConstantsOrBuilder.java index 9722940ed4..8468f8550d 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceConstantsOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/ServiceConstantsOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteRequest.java index 37082d1535..30b9435ea7 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteRequestOrBuilder.java index fb2c88680d..edd9a323ab 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteResponse.java index ed5db155e2..1d8e46aa1c 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteResponseOrBuilder.java index 4936df9ca2..decbf93374 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StartResumableWriteResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageProto.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageProto.java index 26f3553645..561de97d9b 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageProto.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageProto.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateBucketRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateBucketRequest.java index 10568499da..4152fc7326 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateBucketRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateBucketRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateBucketRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateBucketRequestOrBuilder.java index 663a58a992..5917c1c8ac 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateBucketRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateBucketRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateHmacKeyRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateHmacKeyRequest.java index 2af93d09a2..37fcd90c0c 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateHmacKeyRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateHmacKeyRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateHmacKeyRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateHmacKeyRequestOrBuilder.java index 6dddfc06ed..f276a58154 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateHmacKeyRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateHmacKeyRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateObjectRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateObjectRequest.java index e3b1ba4de7..f1fb42789e 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateObjectRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateObjectRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateObjectRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateObjectRequestOrBuilder.java index 85a5d228db..e87e0c1dd9 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateObjectRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/UpdateObjectRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectRequest.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectRequest.java index aa01ce4c5a..51a90b30e6 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectRequest.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectRequestOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectRequestOrBuilder.java index 04b60183cd..3d78815c6d 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectRequestOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectRequestOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectResponse.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectResponse.java index c99f4fafc0..d5f611a4b2 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectResponse.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectResponseOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectResponseOrBuilder.java index fcc3a606b1..1a27b9ac88 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectResponseOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectResponseOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectSpec.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectSpec.java index bc3715f726..e01485764e 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectSpec.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectSpec.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectSpecOrBuilder.java b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectSpecOrBuilder.java index 1797218b77..554af4dfbd 100644 --- a/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectSpecOrBuilder.java +++ b/proto-google-cloud-storage-v2/src/main/java/com/google/storage/v2/WriteObjectSpecOrBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From b6efb50c60ed7b1be1bb8c3a9033927fcfef330c Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:17:21 -0400 Subject: [PATCH 17/20] chore: update requirements.txt to address dependabot security alerts (#1829) (#2134) * chore: update dependencies in requirements.txt to address dependabot security alerts * update dependencies within gcp/templates --------- Source-Link: https://github.com/googleapis/synthtool/commit/af76aa51249c5f6d8efb66e8ac390ec651f9f2f6 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-java:latest@sha256:46d2d262cd285c638656c8bde468011b723dc0c7ffd6a5ecc2650fe639c82e8f Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- .github/.OwlBot.lock.yaml | 4 +- .kokoro/requirements.txt | 573 ++++++++++++++++++++++---------------- 2 files changed, 332 insertions(+), 245 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 258389d1aa..d5500ef442 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-java:latest - digest: sha256:31c8276a1bfb43766597d32645721c029cb94571f1b8d996cb2c290744fe52f9 -# created: 2023-06-27T18:51:58.922150125Z + digest: sha256:46d2d262cd285c638656c8bde468011b723dc0c7ffd6a5ecc2650fe639c82e8f +# created: 2023-07-24T14:21:17.707234503Z diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt index c80f0a87cc..32989051e7 100644 --- a/.kokoro/requirements.txt +++ b/.kokoro/requirements.txt @@ -1,20 +1,20 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --generate-hashes requirements.in +# pip-compile requirements.in --generate-hashes --upgrade # -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c +attrs==23.1.0 \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 # via gcp-releasetool -cachetools==4.2.4 \ - --hash=sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693 \ - --hash=sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1 +cachetools==5.3.1 \ + --hash=sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590 \ + --hash=sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b # via google-auth -certifi==2022.12.7 \ - --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ - --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 +certifi==2023.5.7 \ + --hash=sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7 \ + --hash=sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716 # via requests cffi==1.15.1 \ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ @@ -82,9 +82,82 @@ cffi==1.15.1 \ --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 # via cryptography -charset-normalizer==2.0.12 \ - --hash=sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597 \ - --hash=sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df +charset-normalizer==3.2.0 \ + --hash=sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96 \ + --hash=sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c \ + --hash=sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710 \ + --hash=sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706 \ + --hash=sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020 \ + --hash=sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252 \ + --hash=sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad \ + --hash=sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329 \ + --hash=sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a \ + --hash=sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f \ + --hash=sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6 \ + --hash=sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4 \ + --hash=sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a \ + --hash=sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46 \ + --hash=sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2 \ + --hash=sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23 \ + --hash=sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace \ + --hash=sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd \ + --hash=sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982 \ + --hash=sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10 \ + --hash=sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2 \ + --hash=sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea \ + --hash=sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09 \ + --hash=sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5 \ + --hash=sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149 \ + --hash=sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489 \ + --hash=sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9 \ + --hash=sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80 \ + --hash=sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592 \ + --hash=sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3 \ + --hash=sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6 \ + --hash=sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed \ + --hash=sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c \ + --hash=sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200 \ + --hash=sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a \ + --hash=sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e \ + --hash=sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d \ + --hash=sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6 \ + --hash=sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623 \ + --hash=sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669 \ + --hash=sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3 \ + --hash=sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa \ + --hash=sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9 \ + --hash=sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2 \ + --hash=sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f \ + --hash=sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1 \ + --hash=sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4 \ + --hash=sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a \ + --hash=sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8 \ + --hash=sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3 \ + --hash=sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029 \ + --hash=sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f \ + --hash=sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959 \ + --hash=sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22 \ + --hash=sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7 \ + --hash=sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952 \ + --hash=sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346 \ + --hash=sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e \ + --hash=sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d \ + --hash=sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299 \ + --hash=sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd \ + --hash=sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a \ + --hash=sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3 \ + --hash=sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037 \ + --hash=sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94 \ + --hash=sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c \ + --hash=sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858 \ + --hash=sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a \ + --hash=sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449 \ + --hash=sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c \ + --hash=sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918 \ + --hash=sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1 \ + --hash=sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c \ + --hash=sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac \ + --hash=sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa # via requests click==8.0.4 \ --hash=sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1 \ @@ -97,121 +170,152 @@ colorlog==6.7.0 \ --hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \ --hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5 # via gcp-docuploader -cryptography==39.0.1 \ - --hash=sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4 \ - --hash=sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f \ - --hash=sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502 \ - --hash=sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41 \ - --hash=sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965 \ - --hash=sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e \ - --hash=sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc \ - --hash=sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad \ - --hash=sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505 \ - --hash=sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388 \ - --hash=sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6 \ - --hash=sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2 \ - --hash=sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac \ - --hash=sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695 \ - --hash=sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6 \ - --hash=sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336 \ - --hash=sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0 \ - --hash=sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c \ - --hash=sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106 \ - --hash=sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a \ - --hash=sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8 +cryptography==41.0.2 \ + --hash=sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711 \ + --hash=sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7 \ + --hash=sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd \ + --hash=sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e \ + --hash=sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58 \ + --hash=sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0 \ + --hash=sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d \ + --hash=sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83 \ + --hash=sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831 \ + --hash=sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766 \ + --hash=sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b \ + --hash=sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c \ + --hash=sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182 \ + --hash=sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f \ + --hash=sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa \ + --hash=sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4 \ + --hash=sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a \ + --hash=sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2 \ + --hash=sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76 \ + --hash=sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5 \ + --hash=sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee \ + --hash=sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f \ + --hash=sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14 # via # gcp-releasetool # secretstorage -gcp-docuploader==0.6.4 \ - --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ - --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf +gcp-docuploader==0.6.5 \ + --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ + --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==1.10.5 \ - --hash=sha256:174b7b102d704b254f2a26a3eda2c684fd3543320ec239baf771542a2e58e109 \ - --hash=sha256:e29d29927fe2ca493105a82958c6873bb2b90d503acac56be2c229e74de0eec9 +gcp-releasetool==1.16.0 \ + --hash=sha256:27bf19d2e87aaa884096ff941aa3c592c482be3d6a2bfe6f06afafa6af2353e3 \ + --hash=sha256:a316b197a543fd036209d0caba7a8eb4d236d8e65381c80cbc6d7efaa7606d63 # via -r requirements.in -google-api-core==2.8.2 \ - --hash=sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc \ - --hash=sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50 +google-api-core==2.11.1 \ + --hash=sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a \ + --hash=sha256:d92a5a92dc36dd4f4b9ee4e55528a90e432b059f93aee6ad857f9de8cc7ae94a # via # google-cloud-core # google-cloud-storage -google-auth==2.14.1 \ - --hash=sha256:ccaa901f31ad5cbb562615eb8b664b3dd0bf5404a67618e642307f00613eda4d \ - --hash=sha256:f5d8701633bebc12e0deea4df8abd8aff31c28b355360597f7f2ee60f2e4d016 +google-auth==2.22.0 \ + --hash=sha256:164cba9af4e6e4e40c3a4f90a1a6c12ee56f14c0b4868d1ca91b32826ab334ce \ + --hash=sha256:d61d1b40897407b574da67da1a833bdc10d5a11642566e506565d1b1a46ba873 # via # gcp-releasetool # google-api-core # google-cloud-core # google-cloud-storage -google-cloud-core==2.3.1 \ - --hash=sha256:113ba4f492467d5bd442c8d724c1a25ad7384045c3178369038840ecdd19346c \ - --hash=sha256:34334359cb04187bdc80ddcf613e462dfd7a3aabbc3fe4d118517ab4b9303d53 +google-cloud-core==2.3.3 \ + --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ + --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 # via google-cloud-storage -google-cloud-storage==2.0.0 \ - --hash=sha256:a57a15aead0f9dfbd4381f1bfdbe8bf89818a4bd75bab846cafcefb2db846c47 \ - --hash=sha256:ec4be60bb223a3a960f0d01697d849b86d91cad815a84915a32ed3635e93a5e7 +google-cloud-storage==2.10.0 \ + --hash=sha256:934b31ead5f3994e5360f9ff5750982c5b6b11604dc072bc452c25965e076dc7 \ + --hash=sha256:9433cf28801671de1c80434238fb1e7e4a1ba3087470e90f70c928ea77c2b9d7 # via gcp-docuploader -google-crc32c==1.3.0 \ - --hash=sha256:04e7c220798a72fd0f08242bc8d7a05986b2a08a0573396187fd32c1dcdd58b3 \ - --hash=sha256:05340b60bf05b574159e9bd940152a47d38af3fb43803ffe71f11d704b7696a6 \ - --hash=sha256:12674a4c3b56b706153a358eaa1018c4137a5a04635b92b4652440d3d7386206 \ - --hash=sha256:127f9cc3ac41b6a859bd9dc4321097b1a4f6aa7fdf71b4f9227b9e3ebffb4422 \ - --hash=sha256:13af315c3a0eec8bb8b8d80b8b128cb3fcd17d7e4edafc39647846345a3f003a \ - --hash=sha256:1926fd8de0acb9d15ee757175ce7242e235482a783cd4ec711cc999fc103c24e \ - --hash=sha256:226f2f9b8e128a6ca6a9af9b9e8384f7b53a801907425c9a292553a3a7218ce0 \ - --hash=sha256:276de6273eb074a35bc598f8efbc00c7869c5cf2e29c90748fccc8c898c244df \ - --hash=sha256:318f73f5484b5671f0c7f5f63741ab020a599504ed81d209b5c7129ee4667407 \ - --hash=sha256:3bbce1be3687bbfebe29abdb7631b83e6b25da3f4e1856a1611eb21854b689ea \ - --hash=sha256:42ae4781333e331a1743445931b08ebdad73e188fd554259e772556fc4937c48 \ - --hash=sha256:58be56ae0529c664cc04a9c76e68bb92b091e0194d6e3c50bea7e0f266f73713 \ - --hash=sha256:5da2c81575cc3ccf05d9830f9e8d3c70954819ca9a63828210498c0774fda1a3 \ - --hash=sha256:6311853aa2bba4064d0c28ca54e7b50c4d48e3de04f6770f6c60ebda1e975267 \ - --hash=sha256:650e2917660e696041ab3dcd7abac160b4121cd9a484c08406f24c5964099829 \ - --hash=sha256:6a4db36f9721fdf391646685ecffa404eb986cbe007a3289499020daf72e88a2 \ - --hash=sha256:779cbf1ce375b96111db98fca913c1f5ec11b1d870e529b1dc7354b2681a8c3a \ - --hash=sha256:7f6fe42536d9dcd3e2ffb9d3053f5d05221ae3bbcefbe472bdf2c71c793e3183 \ - --hash=sha256:891f712ce54e0d631370e1f4997b3f182f3368179198efc30d477c75d1f44942 \ - --hash=sha256:95c68a4b9b7828ba0428f8f7e3109c5d476ca44996ed9a5f8aac6269296e2d59 \ - --hash=sha256:96a8918a78d5d64e07c8ea4ed2bc44354e3f93f46a4866a40e8db934e4c0d74b \ - --hash=sha256:9c3cf890c3c0ecfe1510a452a165431b5831e24160c5fcf2071f0f85ca5a47cd \ - --hash=sha256:9f58099ad7affc0754ae42e6d87443299f15d739b0ce03c76f515153a5cda06c \ - --hash=sha256:a0b9e622c3b2b8d0ce32f77eba617ab0d6768b82836391e4f8f9e2074582bf02 \ - --hash=sha256:a7f9cbea4245ee36190f85fe1814e2d7b1e5f2186381b082f5d59f99b7f11328 \ - --hash=sha256:bab4aebd525218bab4ee615786c4581952eadc16b1ff031813a2fd51f0cc7b08 \ - --hash=sha256:c124b8c8779bf2d35d9b721e52d4adb41c9bfbde45e6a3f25f0820caa9aba73f \ - --hash=sha256:c9da0a39b53d2fab3e5467329ed50e951eb91386e9d0d5b12daf593973c3b168 \ - --hash=sha256:ca60076c388728d3b6ac3846842474f4250c91efbfe5afa872d3ffd69dd4b318 \ - --hash=sha256:cb6994fff247987c66a8a4e550ef374671c2b82e3c0d2115e689d21e511a652d \ - --hash=sha256:d1c1d6236feab51200272d79b3d3e0f12cf2cbb12b208c835b175a21efdb0a73 \ - --hash=sha256:dd7760a88a8d3d705ff562aa93f8445ead54f58fd482e4f9e2bafb7e177375d4 \ - --hash=sha256:dda4d8a3bb0b50f540f6ff4b6033f3a74e8bf0bd5320b70fab2c03e512a62812 \ - --hash=sha256:e0f1ff55dde0ebcfbef027edc21f71c205845585fffe30d4ec4979416613e9b3 \ - --hash=sha256:e7a539b9be7b9c00f11ef16b55486141bc2cdb0c54762f84e3c6fc091917436d \ - --hash=sha256:eb0b14523758e37802f27b7f8cd973f5f3d33be7613952c0df904b68c4842f0e \ - --hash=sha256:ed447680ff21c14aaceb6a9f99a5f639f583ccfe4ce1a5e1d48eb41c3d6b3217 \ - --hash=sha256:f52a4ad2568314ee713715b1e2d79ab55fab11e8b304fd1462ff5cccf4264b3e \ - --hash=sha256:fbd60c6aaa07c31d7754edbc2334aef50601b7f1ada67a96eb1eb57c7c72378f \ - --hash=sha256:fc28e0db232c62ca0c3600884933178f0825c99be4474cdd645e378a10588125 \ - --hash=sha256:fe31de3002e7b08eb20823b3735b97c86c5926dd0581c7710a680b418a8709d4 \ - --hash=sha256:fec221a051150eeddfdfcff162e6db92c65ecf46cb0f7bb1bf812a1520ec026b \ - --hash=sha256:ff71073ebf0e42258a42a0b34f2c09ec384977e7f6808999102eedd5b49920e3 +google-crc32c==1.5.0 \ + --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ + --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ + --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ + --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ + --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ + --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ + --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ + --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ + --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ + --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ + --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ + --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ + --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ + --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ + --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ + --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ + --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ + --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ + --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ + --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ + --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ + --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ + --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ + --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ + --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ + --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ + --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ + --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ + --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ + --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ + --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ + --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ + --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ + --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ + --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ + --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ + --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ + --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ + --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ + --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ + --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ + --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ + --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ + --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ + --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ + --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ + --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ + --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ + --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ + --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ + --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ + --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ + --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ + --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ + --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ + --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ + --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ + --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ + --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ + --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ + --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ + --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ + --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ + --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ + --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ + --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ + --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ + --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 # via google-resumable-media -google-resumable-media==2.3.3 \ - --hash=sha256:27c52620bd364d1c8116eaac4ea2afcbfb81ae9139fb3199652fcac1724bfb6c \ - --hash=sha256:5b52774ea7a829a8cdaa8bd2d4c3d4bc660c91b30857ab2668d0eb830f4ea8c5 +google-resumable-media==2.5.0 \ + --hash=sha256:218931e8e2b2a73a58eb354a288e03a0fd5fb1c4583261ac6e4c078666468c93 \ + --hash=sha256:da1bd943e2e114a56d85d6848497ebf9be6a14d3db23e9fc57581e7c3e8170ec # via google-cloud-storage -googleapis-common-protos==1.56.3 \ - --hash=sha256:6f1369b58ed6cf3a4b7054a44ebe8d03b29c309257583a2bbdc064cd1e4a1442 \ - --hash=sha256:87955d7b3a73e6e803f2572a33179de23989ebba725e05ea42f24838b792e461 +googleapis-common-protos==1.59.1 \ + --hash=sha256:0cbedb6fb68f1c07e18eb4c48256320777707e7d0c55063ae56c15db3224a61e \ + --hash=sha256:b35d530fe825fb4227857bc47ad84c33c809ac96f312e13182bdeaa2abe1178a # via google-api-core idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests -importlib-metadata==4.8.3 \ - --hash=sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e \ - --hash=sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668 +importlib-metadata==6.8.0 \ + --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ + --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 + # via keyring +jaraco-classes==3.3.0 \ + --hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \ + --hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621 # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -219,139 +323,119 @@ jeepney==0.8.0 \ # via # keyring # secretstorage -jinja2==3.0.3 \ - --hash=sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 \ - --hash=sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7 +jinja2==3.1.2 \ + --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ + --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 # via gcp-releasetool -keyring==23.4.1 \ - --hash=sha256:17e49fb0d6883c2b4445359434dba95aad84aabb29bbff044ad0ed7100232eca \ - --hash=sha256:89cbd74d4683ed164c8082fb38619341097741323b3786905c6dac04d6915a55 +keyring==24.2.0 \ + --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \ + --hash=sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509 # via gcp-releasetool -markupsafe==2.0.1 \ - --hash=sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298 \ - --hash=sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64 \ - --hash=sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b \ - --hash=sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194 \ - --hash=sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567 \ - --hash=sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff \ - --hash=sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724 \ - --hash=sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74 \ - --hash=sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646 \ - --hash=sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35 \ - --hash=sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6 \ - --hash=sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a \ - --hash=sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6 \ - --hash=sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad \ - --hash=sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26 \ - --hash=sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38 \ - --hash=sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac \ - --hash=sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7 \ - --hash=sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6 \ - --hash=sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047 \ - --hash=sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75 \ - --hash=sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f \ - --hash=sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b \ - --hash=sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135 \ - --hash=sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8 \ - --hash=sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a \ - --hash=sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a \ - --hash=sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1 \ - --hash=sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9 \ - --hash=sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864 \ - --hash=sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914 \ - --hash=sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee \ - --hash=sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f \ - --hash=sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18 \ - --hash=sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8 \ - --hash=sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2 \ - --hash=sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d \ - --hash=sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b \ - --hash=sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b \ - --hash=sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86 \ - --hash=sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6 \ - --hash=sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f \ - --hash=sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb \ - --hash=sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833 \ - --hash=sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28 \ - --hash=sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e \ - --hash=sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415 \ - --hash=sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902 \ - --hash=sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f \ - --hash=sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d \ - --hash=sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9 \ - --hash=sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d \ - --hash=sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145 \ - --hash=sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066 \ - --hash=sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c \ - --hash=sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1 \ - --hash=sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a \ - --hash=sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207 \ - --hash=sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f \ - --hash=sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53 \ - --hash=sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd \ - --hash=sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134 \ - --hash=sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85 \ - --hash=sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9 \ - --hash=sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5 \ - --hash=sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94 \ - --hash=sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509 \ - --hash=sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51 \ - --hash=sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872 +markupsafe==2.1.3 \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 # via jinja2 -packaging==21.3 \ - --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ - --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 +more-itertools==9.1.0 \ + --hash=sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d \ + --hash=sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3 + # via jaraco-classes +packaging==23.1 \ + --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ + --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f # via gcp-releasetool -protobuf==3.20.2 \ - --hash=sha256:03d76b7bd42ac4a6e109742a4edf81ffe26ffd87c5993126d894fe48a120396a \ - --hash=sha256:09e25909c4297d71d97612f04f41cea8fa8510096864f2835ad2f3b3df5a5559 \ - --hash=sha256:18e34a10ae10d458b027d7638a599c964b030c1739ebd035a1dfc0e22baa3bfe \ - --hash=sha256:291fb4307094bf5ccc29f424b42268640e00d5240bf0d9b86bf3079f7576474d \ - --hash=sha256:2c0b040d0b5d5d207936ca2d02f00f765906622c07d3fa19c23a16a8ca71873f \ - --hash=sha256:384164994727f274cc34b8abd41a9e7e0562801361ee77437099ff6dfedd024b \ - --hash=sha256:3cb608e5a0eb61b8e00fe641d9f0282cd0eedb603be372f91f163cbfbca0ded0 \ - --hash=sha256:5d9402bf27d11e37801d1743eada54372f986a372ec9679673bfcc5c60441151 \ - --hash=sha256:712dca319eee507a1e7df3591e639a2b112a2f4a62d40fe7832a16fd19151750 \ - --hash=sha256:7a5037af4e76c975b88c3becdf53922b5ffa3f2cddf657574a4920a3b33b80f3 \ - --hash=sha256:8228e56a865c27163d5d1d1771d94b98194aa6917bcfb6ce139cbfa8e3c27334 \ - --hash=sha256:84a1544252a933ef07bb0b5ef13afe7c36232a774affa673fc3636f7cee1db6c \ - --hash=sha256:84fe5953b18a383fd4495d375fe16e1e55e0a3afe7b4f7b4d01a3a0649fcda9d \ - --hash=sha256:9c673c8bfdf52f903081816b9e0e612186684f4eb4c17eeb729133022d6032e3 \ - --hash=sha256:a9e5ae5a8e8985c67e8944c23035a0dff2c26b0f5070b2f55b217a1c33bbe8b1 \ - --hash=sha256:b4fdb29c5a7406e3f7ef176b2a7079baa68b5b854f364c21abe327bbeec01cdb \ - --hash=sha256:c184485e0dfba4dfd451c3bd348c2e685d6523543a0f91b9fd4ae90eb09e8422 \ - --hash=sha256:c9cdf251c582c16fd6a9f5e95836c90828d51b0069ad22f463761d27c6c19019 \ - --hash=sha256:e39cf61bb8582bda88cdfebc0db163b774e7e03364bbf9ce1ead13863e81e359 \ - --hash=sha256:e8fbc522303e09036c752a0afcc5c0603e917222d8bedc02813fd73b4b4ed804 \ - --hash=sha256:f34464ab1207114e73bba0794d1257c150a2b89b7a9faf504e00af7c9fd58978 \ - --hash=sha256:f52dabc96ca99ebd2169dadbe018824ebda08a795c7684a0b7d203a290f3adb0 +protobuf==3.20.3 \ + --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ + --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ + --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ + --hash=sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b \ + --hash=sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050 \ + --hash=sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9 \ + --hash=sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7 \ + --hash=sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454 \ + --hash=sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480 \ + --hash=sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469 \ + --hash=sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c \ + --hash=sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e \ + --hash=sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db \ + --hash=sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905 \ + --hash=sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b \ + --hash=sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86 \ + --hash=sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4 \ + --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \ + --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \ + --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \ + --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \ + --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee # via # gcp-docuploader # gcp-releasetool # google-api-core - # google-cloud-storage -pyasn1==0.4.8 \ - --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba +pyasn1==0.5.0 \ + --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ + --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde # via # pyasn1-modules # rsa -pyasn1-modules==0.2.8 \ - --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 +pyasn1-modules==0.3.0 \ + --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ + --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d # via google-auth pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pyjwt==2.4.0 \ - --hash=sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf \ - --hash=sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba +pyjwt==2.7.0 \ + --hash=sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1 \ + --hash=sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074 # via gcp-releasetool -pyparsing==3.0.9 \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc - # via packaging pyperclip==1.8.2 \ --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57 # via gcp-releasetool @@ -359,9 +443,9 @@ python-dateutil==2.8.2 \ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 # via gcp-releasetool -requests==2.27.1 \ - --hash=sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61 \ - --hash=sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via # gcp-releasetool # google-api-core @@ -374,10 +458,6 @@ secretstorage==3.3.3 \ --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 # via keyring -setuptools==67.3.2 \ - --hash=sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012 \ - --hash=sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48 - # via -r requirements.in six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 @@ -385,19 +465,26 @@ six==1.16.0 \ # gcp-docuploader # google-auth # python-dateutil -typing-extensions==4.4.0 \ - --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ - --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e +typing-extensions==4.7.1 \ + --hash=sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36 \ + --hash=sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2 # via -r requirements.in -urllib3==1.26.12 \ - --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ - --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 - # via requests -wheel==0.38.4 \ - --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \ - --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8 +urllib3==1.26.16 \ + --hash=sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f \ + --hash=sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14 + # via + # google-auth + # requests +wheel==0.40.0 \ + --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \ + --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247 # via -r requirements.in -zipp==3.6.0 \ - --hash=sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832 \ - --hash=sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc +zipp==3.16.1 \ + --hash=sha256:0b37c326d826d5ca35f2b9685cd750292740774ef16190008b00a0227c256fe0 \ + --hash=sha256:857b158da2cbf427b376da1c24fd11faecbac5a4ac7523c3607f8a01f94c2ec0 # via importlib-metadata + +# WARNING: The following packages were not pinned, but pip requires them to be +# pinned when the requirements file includes hashes and the requirement is not +# satisfied by a package already installed. Consider using the --allow-unsafe flag. +# setuptools From f5477e4eb168dfd3151d0e2c0462e5b35eb3de37 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 24 Jul 2023 17:18:16 +0200 Subject: [PATCH 18/20] deps: update dependency org.junit.vintage:junit-vintage-engine to v5.10.0 (#2132) --- samples/native-image-sample/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml index 785ecae233..d2ab7237c8 100644 --- a/samples/native-image-sample/pom.xml +++ b/samples/native-image-sample/pom.xml @@ -114,7 +114,7 @@ org.junit.vintage junit-vintage-engine - 5.9.3 + 5.10.0 test From 3d2268642dcecea8ca55e78479ad4e4390075814 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 24 Jul 2023 17:18:39 +0200 Subject: [PATCH 19/20] deps: update junit-platform.version to v5.10.0 (#2133) --- google-cloud-storage/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index b7d53b478d..d3bba3cd72 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -17,7 +17,7 @@ google-cloud-storage 1.105.18 - 5.9.3 + 5.10.0 From aebe26f770ea873601d43a9d7f489d577c279438 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 09:55:37 -0700 Subject: [PATCH 20/20] chore(main): release 2.25.0 (#2117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(main): release 2.25.0 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Owl Bot --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ gapic-google-cloud-storage-v2/pom.xml | 4 ++-- google-cloud-storage-bom/pom.xml | 10 +++++----- google-cloud-storage/pom.xml | 4 ++-- grpc-google-cloud-storage-v2/pom.xml | 4 ++-- pom.xml | 10 +++++----- proto-google-cloud-storage-v2/pom.xml | 4 ++-- samples/snapshot/pom.xml | 2 +- versions.txt | 8 ++++---- 9 files changed, 50 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c01d978859..d9a4c0aa01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [2.25.0](https://github.com/googleapis/java-storage/compare/v2.24.0...v2.25.0) (2023-07-24) + + +### Features + +* BlobWriteChannelV2 - same throughput less GC ([#2110](https://github.com/googleapis/java-storage/issues/2110)) ([1b52a10](https://github.com/googleapis/java-storage/commit/1b52a1053130620011515060787bada10c324c0b)) +* Update Storage.createFrom(BlobInfo, Path) to have 150% higher throughput ([#2059](https://github.com/googleapis/java-storage/issues/2059)) ([4c2f44e](https://github.com/googleapis/java-storage/commit/4c2f44e28a1ff19ffb2a02e3cefc062a1dd98fdc)) + + +### Bug Fixes + +* Update BlobWriteChannelV2 to properly carry forward offset after incremental flush ([#2125](https://github.com/googleapis/java-storage/issues/2125)) ([c099a2f](https://github.com/googleapis/java-storage/commit/c099a2f4f8ea9afa6953270876653916b021fd9f)) +* Update GrpcStorageImpl.createFrom(BlobInfo, Path) to use RewindableContent ([#2112](https://github.com/googleapis/java-storage/issues/2112)) ([c805051](https://github.com/googleapis/java-storage/commit/c80505129baa831e492a5514e937875407211595)) + + +### Documentation + +* Fix broken link for TESTING.md ([#2126](https://github.com/googleapis/java-storage/issues/2126)) ([fe9662d](https://github.com/googleapis/java-storage/commit/fe9662d7e552aabfc9012e582ae634f46af1f255)) + + +### Dependencies + +* **test:** Update gcr.io/cloud-devrel-public-resources/storage-testbench docker tag to v0.37.0 ([#2130](https://github.com/googleapis/java-storage/issues/2130)) ([9e8b6d3](https://github.com/googleapis/java-storage/commit/9e8b6d324bfef84e2c2ee93c424b2e7fcb601945)) +* Update dependency com.google.cloud:google-cloud-shared-dependencies to v3.13.1 ([#2129](https://github.com/googleapis/java-storage/issues/2129)) ([a7e854e](https://github.com/googleapis/java-storage/commit/a7e854ecb4d7fa9508a8d0844fc08d9eeab6f653)) +* Update dependency org.junit.vintage:junit-vintage-engine to v5.10.0 ([#2132](https://github.com/googleapis/java-storage/issues/2132)) ([f5477e4](https://github.com/googleapis/java-storage/commit/f5477e4eb168dfd3151d0e2c0462e5b35eb3de37)) +* Update junit-platform.version to v5.10.0 ([#2133](https://github.com/googleapis/java-storage/issues/2133)) ([3d22686](https://github.com/googleapis/java-storage/commit/3d2268642dcecea8ca55e78479ad4e4390075814)) + ## [2.24.0](https://github.com/googleapis/java-storage/compare/v2.23.0...v2.24.0) (2023-07-11) diff --git a/gapic-google-cloud-storage-v2/pom.xml b/gapic-google-cloud-storage-v2/pom.xml index cb6b23a34b..83590fc99c 100644 --- a/gapic-google-cloud-storage-v2/pom.xml +++ b/gapic-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha gapic-google-cloud-storage-v2 GRPC library for gapic-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.24.1-SNAPSHOT + 2.25.0 diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index bd99c27630..9df55b375b 100644 --- a/google-cloud-storage-bom/pom.xml +++ b/google-cloud-storage-bom/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.google.cloud google-cloud-storage-bom - 2.24.1-SNAPSHOT + 2.25.0 pom com.google.cloud @@ -69,22 +69,22 @@ com.google.cloud google-cloud-storage - 2.24.1-SNAPSHOT + 2.25.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha com.google.api.grpc grpc-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha com.google.api.grpc proto-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index d3bba3cd72..2a1fc31718 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-storage - 2.24.1-SNAPSHOT + 2.25.0 jar Google Cloud Storage https://github.com/googleapis/java-storage @@ -12,7 +12,7 @@ com.google.cloud google-cloud-storage-parent - 2.24.1-SNAPSHOT + 2.25.0 google-cloud-storage diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml index e51dfb903e..825c606a69 100644 --- a/grpc-google-cloud-storage-v2/pom.xml +++ b/grpc-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha grpc-google-cloud-storage-v2 GRPC library for grpc-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.24.1-SNAPSHOT + 2.25.0 diff --git a/pom.xml b/pom.xml index 8192d6eaba..837179197b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-storage-parent pom - 2.24.1-SNAPSHOT + 2.25.0 Storage Parent https://github.com/googleapis/java-storage @@ -83,7 +83,7 @@ com.google.cloud google-cloud-storage - 2.24.1-SNAPSHOT + 2.25.0 com.google.apis @@ -124,17 +124,17 @@ com.google.api.grpc proto-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha com.google.api.grpc grpc-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha com.google.api.grpc gapic-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha com.google.cloud diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml index 9068b9edec..19bbbf8f7c 100644 --- a/proto-google-cloud-storage-v2/pom.xml +++ b/proto-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-storage-v2 - 2.24.1-alpha-SNAPSHOT + 2.25.0-alpha proto-google-cloud-storage-v2 PROTO library for proto-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.24.1-SNAPSHOT + 2.25.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 7b23b22571..26760883f1 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-storage - 2.24.1-SNAPSHOT + 2.25.0 diff --git a/versions.txt b/versions.txt index 1cd08c2324..03bad26208 100644 --- a/versions.txt +++ b/versions.txt @@ -1,7 +1,7 @@ # Format: # module:released-version:current-version -google-cloud-storage:2.24.0:2.24.1-SNAPSHOT -gapic-google-cloud-storage-v2:2.24.0-alpha:2.24.1-alpha-SNAPSHOT -grpc-google-cloud-storage-v2:2.24.0-alpha:2.24.1-alpha-SNAPSHOT -proto-google-cloud-storage-v2:2.24.0-alpha:2.24.1-alpha-SNAPSHOT +google-cloud-storage:2.25.0:2.25.0 +gapic-google-cloud-storage-v2:2.25.0-alpha:2.25.0-alpha +grpc-google-cloud-storage-v2:2.25.0-alpha:2.25.0-alpha +proto-google-cloud-storage-v2:2.25.0-alpha:2.25.0-alpha