From f6dd4ab6c8de2168600aa7e2d7a833e1d46e2d4e Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:51:57 -0700 Subject: [PATCH 01/28] feat: Instrument HTTP with OpenTelemetry (#2780) --- .../clirr-ignored-differences.xml | 13 ++ google-cloud-storage/pom.xml | 9 + .../cloud/storage/GrpcStorageOptions.java | 11 + .../cloud/storage/HttpStorageOptions.java | 26 +++ .../google/cloud/storage/StorageOptions.java | 6 + .../otel/NoOpOpenTelemetryInstance.java | 96 +++++++++ .../storage/otel/OpenTelemetryInstance.java | 191 ++++++++++++++++++ .../storage/otel/OpenTelemetryTraceUtil.java | 94 +++++++++ .../cloud/storage/spi/v1/HttpStorageRpc.java | 11 +- .../cloud/storage/ITOpenTelemetryTest.java | 105 ++++++++++ 10 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml index d81c37bb24..03e37c174e 100644 --- a/google-cloud-storage/clirr-ignored-differences.xml +++ b/google-cloud-storage/clirr-ignored-differences.xml @@ -96,4 +96,17 @@ <method>boolean equals(java.lang.Object)</method> </difference> + <difference> + <differenceType>7013</differenceType> + <className>com/google/cloud/storage/StorageOptions$Builder</className> + <method>com.google.cloud.storage.StorageOptions$Builder setOpenTelemetrySdk(io.opentelemetry.sdk.OpenTelemetrySdk)</method> + </difference> + + <difference> + <differenceType>7013</differenceType> + <className>com/google/cloud/storage/StorageOptions</className> + <method>io.opentelemetry.sdk.OpenTelemetrySdk getOpenTelemetrySdk()</method> + </difference> + + </differences> diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 2b3413e7f3..02ef4f2611 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -83,6 +83,10 @@ <groupId>io.opencensus</groupId> <artifactId>opencensus-api</artifactId> </dependency> + <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-context</artifactId> + </dependency> <dependency> <groupId>com.google.api.grpc</groupId> <artifactId>proto-google-iam-v1</artifactId> @@ -205,6 +209,11 @@ <artifactId>grpc-googleapis</artifactId> <scope>runtime</scope> </dependency> + <dependency> + <groupId>io.opentelemetry</groupId> + <artifactId>opentelemetry-sdk-trace</artifactId> + <scope>test</scope> + </dependency> <!-- We're not using this directly, however there appears to be some resolution diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index 10d7066987..cd574e97bc 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -78,6 +78,7 @@ import io.grpc.MethodDescriptor; import io.grpc.Status; import io.grpc.protobuf.ProtoUtils; +import io.opentelemetry.sdk.OpenTelemetrySdk; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; @@ -347,6 +348,11 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw return Tuple.of(builder.build(), defaultOpts); } + @Override + public OpenTelemetrySdk getOpenTelemetrySdk() { + return null; + } + /** @since 2.14.0 This new api is in preview and is subject to breaking changes. */ @BetaApi @Override @@ -628,6 +634,11 @@ public GrpcStorageOptions.Builder setBlobWriteSessionConfig( return this; } + @Override + public StorageOptions.Builder setOpenTelemetrySdk(@NonNull OpenTelemetrySdk openTelemetrySdk) { + return null; + } + /** @since 2.14.0 This new api is in preview and is subject to breaking changes. */ @BetaApi @Override 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 9f6781f6c4..f58da29e18 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 @@ -38,6 +38,7 @@ import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; +import io.opentelemetry.sdk.OpenTelemetrySdk; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; @@ -60,6 +61,8 @@ public class HttpStorageOptions extends StorageOptions { private transient RetryDependenciesAdapter retryDepsAdapter; private final BlobWriteSessionConfig blobWriteSessionConfig; + private OpenTelemetrySdk openTelemetrySdk; + private HttpStorageOptions(Builder builder, StorageDefaults serviceDefaults) { super(builder, serviceDefaults); this.retryAlgorithmManager = @@ -68,6 +71,7 @@ private HttpStorageOptions(Builder builder, StorageDefaults serviceDefaults) { builder.storageRetryStrategy, defaults().getStorageRetryStrategy())); retryDepsAdapter = new RetryDependenciesAdapter(); blobWriteSessionConfig = builder.blobWriteSessionConfig; + openTelemetrySdk = builder.openTelemetrySdk; } @Override @@ -85,6 +89,11 @@ StorageRpc getStorageRpcV1() { return (StorageRpc) getRpc(); } + @Override + public OpenTelemetrySdk getOpenTelemetrySdk() { + return openTelemetrySdk; + } + @Override public HttpStorageOptions.Builder toBuilder() { return new HttpStorageOptions.Builder(this); @@ -131,11 +140,16 @@ RetryingDependencies asRetryDependencies() { return retryDepsAdapter; } + public void setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { + this.openTelemetrySdk = openTelemetrySdk; + } + public static class Builder extends StorageOptions.Builder { private StorageRetryStrategy storageRetryStrategy; private BlobWriteSessionConfig blobWriteSessionConfig = HttpStorageDefaults.INSTANCE.getDefaultStorageWriterConfig(); + private OpenTelemetrySdk openTelemetrySdk; Builder() {} @@ -144,6 +158,7 @@ public static class Builder extends StorageOptions.Builder { HttpStorageOptions hso = (HttpStorageOptions) options; this.storageRetryStrategy = hso.retryAlgorithmManager.retryStrategy; this.blobWriteSessionConfig = hso.blobWriteSessionConfig; + this.openTelemetrySdk = hso.getOpenTelemetrySdk(); } @Override @@ -269,6 +284,17 @@ public HttpStorageOptions build() { } return options; } + + /** + * Enable OpenTelemetry Tracing and provide an instance for the client to use. + * + * @param openTelemetrySdk + */ + public HttpStorageOptions.Builder setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { + requireNonNull(openTelemetrySdk, "openTelemetry must be non null"); + this.openTelemetrySdk = openTelemetrySdk; + return this; + } } public static final class HttpStorageDefaults extends StorageDefaults { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java index ab32532ae1..703f927509 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java @@ -29,6 +29,7 @@ import com.google.cloud.storage.Storage.BlobWriteOption; import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.spi.StorageRpcFactory; +import io.opentelemetry.sdk.OpenTelemetrySdk; import java.io.IOException; import java.io.InputStream; import java.util.Properties; @@ -110,6 +111,9 @@ public abstract static class Builder public abstract StorageOptions.Builder setBlobWriteSessionConfig( @NonNull BlobWriteSessionConfig blobWriteSessionConfig); + public abstract StorageOptions.Builder setOpenTelemetrySdk( + @NonNull OpenTelemetrySdk openTelemetrySdk); + @Override public abstract StorageOptions build(); } @@ -144,6 +148,8 @@ public static String version() { return VERSION; } + public abstract OpenTelemetrySdk getOpenTelemetrySdk(); + @SuppressWarnings("unchecked") @Override public abstract StorageOptions.Builder toBuilder(); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java new file mode 100644 index 0000000000..814dbafdc4 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage.otel; + +import com.google.api.core.ApiFuture; +import io.opentelemetry.api.trace.StatusCode; +import java.util.Map; +import javax.annotation.Nonnull; + +class NoOpOpenTelemetryInstance implements OpenTelemetryTraceUtil { + + @Override + public OpenTelemetryTraceUtil.Span startSpan(String spanName) { + return new Span(); + } + + @Override + public OpenTelemetryTraceUtil.Span startSpan( + String spanName, OpenTelemetryTraceUtil.Context parent) { + return new Span(); + } + + @Nonnull + @Override + public Span currentSpan() { + return new Span(); + } + + @Nonnull + @Override + public Context currentContext() { + return new Context(); + } + + static class Span implements OpenTelemetryTraceUtil.Span { + @Override + public void end() {} + + @Override + public void end(Throwable error) {} + + @Override + public <T> void endAtFuture(ApiFuture<T> futureValue) {} + + @Override + public OpenTelemetryTraceUtil.Span recordException(Throwable error) { + return this; + } + + @Override + public OpenTelemetryTraceUtil.Span setStatus(StatusCode status, String name) { + return this; + } + + @Override + public OpenTelemetryTraceUtil.Span addEvent(String name) { + return this; + } + + @Override + public OpenTelemetryTraceUtil.Span addEvent(String name, Map<String, Object> attributes) { + return this; + } + + @Override + public Scope makeCurrent() { + return new Scope(); + } + } + + static class Context implements OpenTelemetryTraceUtil.Context { + @Override + public Scope makeCurrent() { + return new Scope(); + } + } + + static class Scope implements OpenTelemetryTraceUtil.Scope { + @Override + public void close() {} + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java new file mode 100644 index 0000000000..4fd1f0b6ff --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java @@ -0,0 +1,191 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage.otel; + +import com.google.api.core.ApiFuture; +import com.google.api.gax.core.GaxProperties; +import com.google.cloud.storage.StorageOptions; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Context; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Span; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import java.util.Map; +import javax.annotation.Nonnull; + +class OpenTelemetryInstance implements OpenTelemetryTraceUtil { + private final Tracer tracer; + private final OpenTelemetry openTelemetry; + private final StorageOptions storageOptions; + + private static final String LIBRARY_NAME = "cloud.google.com/java/storage"; + + public OpenTelemetryInstance(StorageOptions storageOptions) { + this.storageOptions = storageOptions; + this.openTelemetry = storageOptions.getOpenTelemetrySdk(); + this.tracer = + openTelemetry.getTracer(LIBRARY_NAME, GaxProperties.getLibraryVersion(this.getClass())); + } + + static class Span implements OpenTelemetryTraceUtil.Span { + private final io.opentelemetry.api.trace.Span span; + private final String spanName; + + Span(io.opentelemetry.api.trace.Span span, String spanName) { + this.span = span; + this.spanName = spanName; + } + + @Override + public OpenTelemetryTraceUtil.Span recordException(Throwable error) { + span.recordException(error); + return this; + } + + @Override + public OpenTelemetryTraceUtil.Span setStatus(StatusCode status, String name) { + span.setStatus(status, name); + return this; + } + + @Override + public OpenTelemetryTraceUtil.Span addEvent(String name) { + span.addEvent(name); + return this; + } + + @Override + public OpenTelemetryTraceUtil.Span addEvent(String name, Map<String, Object> attributes) { + AttributesBuilder attributesBuilder = Attributes.builder(); + attributes.forEach( + (key, value) -> { + if (value instanceof Integer) { + attributesBuilder.put(key, (int) value); + } else if (value instanceof Long) { + attributesBuilder.put(key, (long) value); + } else if (value instanceof Double) { + attributesBuilder.put(key, (double) value); + } else if (value instanceof Float) { + attributesBuilder.put(key, (float) value); + } else if (value instanceof Boolean) { + attributesBuilder.put(key, (boolean) value); + } else if (value instanceof String) { + attributesBuilder.put(key, (String) value); + } else { + // OpenTelemetry APIs do not support any other type. + throw new IllegalArgumentException( + "Unknown attribute type:" + value.getClass().getSimpleName()); + } + }); + span.addEvent(name, attributesBuilder.build()); + return this; + } + + @Override + public Scope makeCurrent() { + return new Scope(span.makeCurrent()); + } + + @Override + public void end() { + span.end(); + } + + @Override + public void end(Throwable error) {} + + @Override + public <T> void endAtFuture(ApiFuture<T> futureValue) {} + } + + static class Scope implements OpenTelemetryTraceUtil.Scope { + private final io.opentelemetry.context.Scope scope; + + Scope(io.opentelemetry.context.Scope scope) { + this.scope = scope; + } + + @Override + public void close() { + scope.close(); + } + } + + static class Context implements OpenTelemetryTraceUtil.Context { + private final io.opentelemetry.context.Context context; + + Context(io.opentelemetry.context.Context context) { + this.context = context; + } + + @Override + public Scope makeCurrent() { + return new Scope(context.makeCurrent()); + } + } + + @Override + public OpenTelemetryTraceUtil.Span startSpan(String methodName) { + String formatSpanName = String.format("%s.%s/%s", "storage", "client", methodName); + SpanBuilder spanBuilder = tracer.spanBuilder(formatSpanName).setSpanKind(SpanKind.CLIENT); + io.opentelemetry.api.trace.Span span = + addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); + return new Span(span, formatSpanName); + } + + @Override + public OpenTelemetryTraceUtil.Span startSpan( + String spanName, OpenTelemetryTraceUtil.Context parent) { + assert (parent instanceof OpenTelemetryInstance.Context); + SpanBuilder spanBuilder = + tracer + .spanBuilder(spanName) + .setSpanKind(SpanKind.CLIENT) + .setParent(((OpenTelemetryInstance.Context) parent).context); + io.opentelemetry.api.trace.Span span = + addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); + return new Span(span, spanName); + } + + @Nonnull + @Override + public OpenTelemetryTraceUtil.Span currentSpan() { + return new Span(io.opentelemetry.api.trace.Span.current(), ""); + } + + @Nonnull + @Override + public OpenTelemetryTraceUtil.Context currentContext() { + return new Context(io.opentelemetry.context.Context.current()); + } + + private SpanBuilder addSettingsAttributesToCurrentSpan(SpanBuilder spanBuilder) { + spanBuilder = spanBuilder.setAttribute("gcp.client.service", "Storage"); + spanBuilder = + spanBuilder.setAllAttributes( + Attributes.builder() + .put("gcp.client.version", GaxProperties.getLibraryVersion(this.getClass())) + .put("gcp.client.repo", "googleapis/java-storage") + .put("gcp.client.artifact", "com.google.cloud.google-cloud-storage") + .build()); + return spanBuilder; + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java new file mode 100644 index 0000000000..932457d05e --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage.otel; + +import com.google.api.core.ApiFuture; +import com.google.cloud.storage.StorageOptions; +import io.opentelemetry.api.trace.StatusCode; +import java.util.Map; +import javax.annotation.Nonnull; + +public interface OpenTelemetryTraceUtil { + + static OpenTelemetryTraceUtil getInstance(@Nonnull StorageOptions storageOptions) { + boolean createNoOp = storageOptions.getOpenTelemetrySdk() == null; + + if (createNoOp) { + return new NoOpOpenTelemetryInstance(); + } else { + return new OpenTelemetryInstance(storageOptions); + } + } + + /** Represents a trace span. */ + interface Span { + Span recordException(Throwable error); + + Span setStatus(StatusCode status, String name); + /** Adds the given event to this span. */ + Span addEvent(String name); + + /** Adds the given event with the given attributes to this span. */ + Span addEvent(String name, Map<String, Object> attributes); + + /** Marks this span as the current span. */ + Scope makeCurrent(); + + /** Ends this span. */ + void end(); + + /** Ends this span in an error. */ + void end(Throwable error); + + /** + * If an operation ends in the future, its relevant span should end _after_ the future has been + * completed. This method "appends" the span completion code at the completion of the given + * future. In order for telemetry info to be recorded, the future returned by this method should + * be completed. + */ + <T> void endAtFuture(ApiFuture<T> futureValue); + } + + /** Represents a trace context. */ + interface Context { + /** Makes this context the current context. */ + Scope makeCurrent(); + } + + /** Represents a trace scope. */ + interface Scope extends AutoCloseable { + /** Closes the current scope. */ + void close(); + } + + /** Starts a new span with the given name, sets it as the current span, and returns it. */ + Span startSpan(String spanName); + + /** + * Starts a new span with the given name and the given context as its parent, sets it as the + * current span, and returns it. + */ + Span startSpan(String spanName, Context parent); + + /** Returns the current span. */ + @Nonnull + Span currentSpan(); + + /** Returns the current Context. */ + @Nonnull + Context currentContext(); +} 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 5341051a25..fdfec46199 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 @@ -67,6 +67,7 @@ import com.google.cloud.http.HttpTransportOptions; import com.google.cloud.storage.StorageException; import com.google.cloud.storage.StorageOptions; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -80,6 +81,7 @@ import io.opencensus.trace.Status; import io.opencensus.trace.Tracer; import io.opencensus.trace.Tracing; +import io.opentelemetry.api.trace.StatusCode; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -113,6 +115,7 @@ public class HttpStorageRpc implements StorageRpc { private final Storage storage; private final Tracer tracer = Tracing.getTracer(); private final HttpRequestInitializer batchRequestInitializer; + private final OpenTelemetryTraceUtil openTelemetryTraceUtil; private static final long MEGABYTE = 1024L * 1024L; private static final FileNameMap FILE_NAME_MAP = URLConnection.getFileNameMap(); @@ -144,6 +147,8 @@ public HttpStorageRpc(StorageOptions options, JsonFactory jsonFactory) { .setRootUrl(options.getHost()) .setApplicationName(applicationName) .build(); + // Get instance of OpenTelemetry + openTelemetryTraceUtil = OpenTelemetryTraceUtil.getInstance(options); } public Storage getStorage() { @@ -355,9 +360,10 @@ private Span startSpan(String spanName) { @Override public Bucket create(Bucket bucket, Map<Option, ?> options) { + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create(Bucket,Map)"); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_BUCKET); Scope scope = tracer.withSpan(span); - try { + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { return storage .buckets() .insert(this.options.getProjectId(), bucket) @@ -368,9 +374,12 @@ public Bucket create(Bucket bucket, Map<Option, ?> options) { .setEnableObjectRetention(Option.ENABLE_OBJECT_RETENTION.getBoolean(options)) .execute(); } catch (IOException ex) { + otelSpan.recordException(ex); + otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); } finally { + otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java new file mode 100644 index 0000000000..e07cf659cc --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +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.CrossRun; +import com.google.cloud.storage.it.runner.annotations.Inject; +import com.google.cloud.storage.it.runner.registry.Generator; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@CrossRun( + backends = {Backend.PROD}, + transports = {Transport.HTTP}) +public final class ITOpenTelemetryTest { + @Inject public Generator generator; + + @Test + public void checkInstrumentation() { + SpanExporter exporter = new TestExporter(); + + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(exporter)) + .build()) + .build(); + StorageOptions storageOptions = + StorageOptions.http().setOpenTelemetrySdk(openTelemetrySdk).build(); + Storage storage = storageOptions.getService(); + storage.create(BucketInfo.of(generator.randomBucketName())); + TestExporter testExported = (TestExporter) exporter; + SpanData spanData = testExported.getExportedSpans().get(0); + Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); + Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); + Assert.assertEquals( + "com.google.cloud.google-cloud-storage", + getAttributeValue(spanData, "gcp.client.artifact")); + } + + @Test + public void noOpDoesNothing() { + StorageOptions storageOptions = StorageOptions.http().build(); + Storage storage = storageOptions.getService(); + storage.create(BucketInfo.of(generator.randomBucketName())); + } + + private String getAttributeValue(SpanData spanData, String key) { + return spanData.getAttributes().get(AttributeKey.stringKey(key)).toString(); + } +} + +class TestExporter implements SpanExporter { + public final List<SpanData> exportedSpans = Collections.synchronizedList(new ArrayList<>()); + + @Override + public CompletableResultCode export(Collection<SpanData> spans) { + exportedSpans.addAll(spans); + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return null; + } + + @Override + public CompletableResultCode shutdown() { + return null; + } + + public List<SpanData> getExportedSpans() { + return exportedSpans; + } +} From 5e6eb84a7720dd08517ea79afc28ef66ed52489a Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:34:10 -0700 Subject: [PATCH 02/28] feat: Add OpenTelemetry Traces to GRPC (#2783) * feat: Add OpenTelemetry to GRPC * adding rpc.system value * refactor test for both transports * lint * add branch protection rules to otel feature branch * pr comment --- .github/sync-repo-settings.yaml | 16 ++++ .../google/cloud/storage/GrpcStorageImpl.java | 24 ++++-- .../cloud/storage/GrpcStorageOptions.java | 19 ++++- .../storage/otel/OpenTelemetryInstance.java | 13 +++- .../cloud/storage/ITOpenTelemetryTest.java | 73 +++++++++++++++---- 5 files changed, 119 insertions(+), 26 deletions(-) diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index f55594b387..f28f5575df 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -162,6 +162,22 @@ branchProtectionRules: - 'Kokoro - Test: Java GraalVM Native Image' - 'Kokoro - Test: Java 17 GraalVM Native Image' - javadoc + - pattern: otel-v1-branch + isAdminEnforced: true + requiredApprovingReviewCount: 1 + requiresCodeOwnerReviews: true + requiresStrictStatusChecks: false + requiredStatusCheckContexts: + - dependencies (17) + - lint + - clirr + - units (8) + - units (11) + - 'Kokoro - Test: Integration' + - cla/google + - 'Kokoro - Test: Java GraalVM Native Image' + - 'Kokoro - Test: Java 17 GraalVM Native Image' + - javadoc permissionRules: - team: yoshi-admins permission: admin 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 6dff8996e9..e139220de2 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 @@ -68,6 +68,7 @@ import com.google.cloud.storage.UnifiedOpts.Opts; import com.google.cloud.storage.UnifiedOpts.ProjectId; import com.google.cloud.storage.UnifiedOpts.UserProject; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -168,6 +169,7 @@ final class GrpcStorageImpl extends BaseService<StorageOptions> // workaround for https://github.com/googleapis/java-storage/issues/1736 private final Opts<UserProject> defaultOpts; @Deprecated private final ProjectId defaultProjectId; + private final OpenTelemetryTraceUtil openTelemetryTraceUtil; GrpcStorageImpl( GrpcStorageOptions options, @@ -184,6 +186,7 @@ final class GrpcStorageImpl extends BaseService<StorageOptions> this.retryAlgorithmManager = options.getRetryAlgorithmManager(); this.syntaxDecoders = new SyntaxDecoders(); this.defaultProjectId = UnifiedOpts.projectId(options.getProjectId()); + this.openTelemetryTraceUtil = OpenTelemetryTraceUtil.getInstance(options); } @Override @@ -198,6 +201,8 @@ public void close() throws Exception { @Override public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create(Bucket, BucketTargetOption"); Opts<BucketTargetOpt> opts = Opts.unwrap(options).resolveFrom(bucketInfo).prepend(defaultOpts); GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); @@ -212,11 +217,20 @@ public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { .setParent("projects/_"); CreateBucketRequest req = opts.createBucketsRequest().apply(builder).build(); GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); - return Retrying.run( - getOptions(), - retryAlgorithmManager.getFor(req), - () -> storageClient.createBucketCallable().call(req, merge), - syntaxDecoders.bucket); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + return Retrying.run( + getOptions(), + retryAlgorithmManager.getFor(req), + () -> storageClient.createBucketCallable().call(req, merge), + syntaxDecoders.bucket); + } catch (Exception ex) { + otelSpan.recordException(ex); + otelSpan.setStatus( + io.opentelemetry.api.trace.StatusCode.ERROR, ex.getClass().getSimpleName()); + throw StorageException.coalesce(ex); + } finally { + otelSpan.end(); + } } @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index cd574e97bc..92d05403ee 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -119,6 +119,7 @@ public final class GrpcStorageOptions extends StorageOptions private final boolean grpcClientMetricsManuallyEnabled; private final GrpcInterceptorProvider grpcInterceptorProvider; private final BlobWriteSessionConfig blobWriteSessionConfig; + private OpenTelemetrySdk openTelemetrySdk; private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) { super(builder, serviceDefaults); @@ -134,6 +135,7 @@ private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) this.grpcClientMetricsManuallyEnabled = builder.grpcMetricsManuallyEnabled; this.grpcInterceptorProvider = builder.grpcInterceptorProvider; this.blobWriteSessionConfig = builder.blobWriteSessionConfig; + this.openTelemetrySdk = builder.openTelemetrySdk; } @Override @@ -350,7 +352,7 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw @Override public OpenTelemetrySdk getOpenTelemetrySdk() { - return null; + return openTelemetrySdk; } /** @since 2.14.0 This new api is in preview and is subject to breaking changes. */ @@ -435,6 +437,8 @@ public static final class Builder extends StorageOptions.Builder { private boolean grpcMetricsManuallyEnabled = false; + private OpenTelemetrySdk openTelemetrySdk; + Builder() {} Builder(StorageOptions options) { @@ -446,6 +450,7 @@ public static final class Builder extends StorageOptions.Builder { this.enableGrpcClientMetrics = gso.enableGrpcClientMetrics; this.grpcInterceptorProvider = gso.grpcInterceptorProvider; this.blobWriteSessionConfig = gso.blobWriteSessionConfig; + this.openTelemetrySdk = gso.openTelemetrySdk; } /** @@ -634,9 +639,15 @@ public GrpcStorageOptions.Builder setBlobWriteSessionConfig( return this; } - @Override - public StorageOptions.Builder setOpenTelemetrySdk(@NonNull OpenTelemetrySdk openTelemetrySdk) { - return null; + /** + * Enable OpenTelemetry Tracing and provide an instance for the client to use. + * + * @param openTelemetrySdk User defined instance of OpenTelemetry SDK to be used by the library + */ + public GrpcStorageOptions.Builder setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { + requireNonNull(openTelemetrySdk, "openTelemetry must be non null"); + this.openTelemetrySdk = openTelemetrySdk; + return this; } /** @since 2.14.0 This new api is in preview and is subject to breaking changes. */ diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java index 4fd1f0b6ff..9c71f4842a 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java @@ -18,10 +18,12 @@ import com.google.api.core.ApiFuture; import com.google.api.gax.core.GaxProperties; +import com.google.cloud.storage.GrpcStorageOptions; import com.google.cloud.storage.StorageOptions; import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Context; import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Span; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.trace.SpanBuilder; @@ -38,11 +40,14 @@ class OpenTelemetryInstance implements OpenTelemetryTraceUtil { private static final String LIBRARY_NAME = "cloud.google.com/java/storage"; + private final String transport; + public OpenTelemetryInstance(StorageOptions storageOptions) { this.storageOptions = storageOptions; this.openTelemetry = storageOptions.getOpenTelemetrySdk(); this.tracer = openTelemetry.getTracer(LIBRARY_NAME, GaxProperties.getLibraryVersion(this.getClass())); + this.transport = storageOptions instanceof GrpcStorageOptions ? "grpc" : "http"; } static class Span implements OpenTelemetryTraceUtil.Span { @@ -56,7 +61,12 @@ static class Span implements OpenTelemetryTraceUtil.Span { @Override public OpenTelemetryTraceUtil.Span recordException(Throwable error) { - span.recordException(error); + span.recordException( + error, + Attributes.of( + AttributeKey.stringKey("exception.message"), error.getMessage(), + AttributeKey.stringKey("exception.type"), error.getClass().getName(), + AttributeKey.stringKey("exception.stacktrace"), error.getStackTrace().toString())); return this; } @@ -146,6 +156,7 @@ public Scope makeCurrent() { public OpenTelemetryTraceUtil.Span startSpan(String methodName) { String formatSpanName = String.format("%s.%s/%s", "storage", "client", methodName); SpanBuilder spanBuilder = tracer.spanBuilder(formatSpanName).setSpanKind(SpanKind.CLIENT); + spanBuilder.setAttribute("rpc.system", transport); io.opentelemetry.api.trace.Span span = addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); return new Span(span, formatSpanName); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java index e07cf659cc..c3261de542 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java @@ -16,12 +16,7 @@ package com.google.cloud.storage; -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.CrossRun; -import com.google.cloud.storage.it.runner.annotations.Inject; -import com.google.cloud.storage.it.runner.registry.Generator; +import com.google.cloud.storage.testing.RemoteStorageHelper; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.CompletableResultCode; @@ -33,16 +28,11 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.UUID; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -@RunWith(StorageITRunner.class) -@CrossRun( - backends = {Backend.PROD}, - transports = {Transport.HTTP}) public final class ITOpenTelemetryTest { - @Inject public Generator generator; @Test public void checkInstrumentation() { @@ -58,7 +48,8 @@ public void checkInstrumentation() { StorageOptions storageOptions = StorageOptions.http().setOpenTelemetrySdk(openTelemetrySdk).build(); Storage storage = storageOptions.getService(); - storage.create(BucketInfo.of(generator.randomBucketName())); + String bucket = randomBucketName(); + storage.create(BucketInfo.of(bucket)); TestExporter testExported = (TestExporter) exporter; SpanData spanData = testExported.getExportedSpans().get(0); Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); @@ -66,21 +57,71 @@ public void checkInstrumentation() { Assert.assertEquals( "com.google.cloud.google-cloud-storage", getAttributeValue(spanData, "gcp.client.artifact")); + Assert.assertEquals("http", getAttributeValue(spanData, "rpc.system")); + + // Cleanup + RemoteStorageHelper.forceDelete(storage, bucket); } @Test - public void noOpDoesNothing() { - StorageOptions storageOptions = StorageOptions.http().build(); + public void checkInstrumentationGrpc() { + SpanExporter exporter = new TestExporter(); + + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(exporter)) + .build()) + .build(); + StorageOptions storageOptions = + StorageOptions.grpc().setOpenTelemetrySdk(openTelemetrySdk).build(); Storage storage = storageOptions.getService(); - storage.create(BucketInfo.of(generator.randomBucketName())); + String bucket = randomBucketName(); + storage.create(BucketInfo.of(bucket)); + TestExporter testExported = (TestExporter) exporter; + SpanData spanData = testExported.getExportedSpans().get(0); + Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); + Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); + Assert.assertEquals( + "com.google.cloud.google-cloud-storage", + getAttributeValue(spanData, "gcp.client.artifact")); + Assert.assertEquals("grpc", getAttributeValue(spanData, "rpc.system")); + + // Cleanup + RemoteStorageHelper.forceDelete(storage, bucket); + } + + @Test + public void noOpDoesNothing() { + String httpBucket = randomBucketName(); + String grpcBucket = randomBucketName(); + // NoOp for HTTP + StorageOptions storageOptionsHttp = StorageOptions.http().build(); + Storage storageHttp = storageOptionsHttp.getService(); + storageHttp.create(BucketInfo.of(httpBucket)); + + // NoOp for Grpc + StorageOptions storageOptionsGrpc = StorageOptions.grpc().build(); + Storage storageGrpc = storageOptionsGrpc.getService(); + storageGrpc.create(BucketInfo.of(grpcBucket)); + + // cleanup + RemoteStorageHelper.forceDelete(storageHttp, httpBucket); + RemoteStorageHelper.forceDelete(storageGrpc, grpcBucket); } private String getAttributeValue(SpanData spanData, String key) { return spanData.getAttributes().get(AttributeKey.stringKey(key)).toString(); } + + public String randomBucketName() { + return "java-storage-grpc-rand-" + UUID.randomUUID(); + } } class TestExporter implements SpanExporter { + public final List<SpanData> exportedSpans = Collections.synchronizedList(new ArrayList<>()); @Override From c0abdf91198ee114674904e7d8894451158e4094 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:10:15 -0700 Subject: [PATCH 03/28] feat: create(Blob) instrumentation (#2792) --- .../google/cloud/storage/GrpcStorageImpl.java | 91 ++++++++++----- ...lelCompositeUploadWritableByteChannel.java | 2 + .../com/google/cloud/storage/StorageImpl.java | 7 +- .../google/cloud/storage/StorageInternal.java | 12 +- .../storage/otel/OpenTelemetryInstance.java | 9 +- .../storage/ITGrpcOpenTelemetryTest.java | 107 ++++++++++++++++++ .../cloud/storage/otel/TestExporter.java | 50 ++++++++ 7 files changed, 245 insertions(+), 33 deletions(-) create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/otel/TestExporter.java 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 e139220de2..5a16b6c299 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 @@ -201,8 +201,7 @@ public void close() throws Exception { @Override public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create(Bucket, BucketTargetOption"); + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); Opts<BucketTargetOpt> opts = Opts.unwrap(options).resolveFrom(bucketInfo).prepend(defaultOpts); GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); @@ -248,13 +247,28 @@ public Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... option public Blob create( BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options) { Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); - return internalDirectUpload(blobInfo, opts, ByteBuffer.wrap(content, offset, length)) - .asBlob(this); + // Start the otel span to retain information of the origin of the request + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + return internalDirectUpload( + blobInfo, + opts, + ByteBuffer.wrap(content, offset, length), + openTelemetryTraceUtil.currentContext()) + .asBlob(this); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } finally { + otelSpan.end(); + } } @Override public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { - try { + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); + try (OpenTelemetryTraceUtil.Scope ununsed = otelSpan.makeCurrent()) { requireNonNull(blobInfo, "blobInfo must be non null"); InputStream inputStreamParam = firstNonNull(content, new ByteArrayInputStream(ZERO_BYTES)); @@ -281,7 +295,11 @@ public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... op ApiFuture<WriteObjectResponse> responseApiFuture = session.getResult(); return this.getBlob(responseApiFuture); } catch (IOException | ApiException e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); + } finally { + otelSpan.end(); } } @@ -800,8 +818,19 @@ public GrpcBlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options @Override public BlobInfo internalDirectUpload( BlobInfo blobInfo, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { + return internalDirectUpload(blobInfo, opts, buf, null); + } + + @Override + public BlobInfo internalDirectUpload( + BlobInfo blobInfo, + Opts<ObjectTargetOpt> opts, + ByteBuffer buf, + OpenTelemetryTraceUtil.Context ctx) { requireNonNull(blobInfo, "blobInfo must be non null"); requireNonNull(buf, "content must be non null"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("internalDirectUpload(BlobInfo)", ctx); Opts<ObjectTargetOpt> optsWithDefaults = opts.prepend(defaultOpts); GrpcCallContext grpcCallContext = optsWithDefaults.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); @@ -809,28 +838,36 @@ public BlobInfo internalDirectUpload( Hasher hasher = Hasher.enabled(); GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); RewindableContent content = RewindableContent.of(buf); - return Retrying.run( - getOptions(), - retryAlgorithmManager.getFor(req), - () -> { - content.rewindTo(0); - UnbufferedWritableByteChannelSession<WriteObjectResponse> session = - ResumableMedia.gapic() - .write() - .byteChannel(storageClient.writeObjectCallable().withDefaultCallContext(merge)) - .setByteStringStrategy(ByteStringStrategy.noCopy()) - .setHasher(hasher) - .direct() - .unbuffered() - .setRequest(req) - .build(); - - try (UnbufferedWritableByteChannel c = session.open()) { - content.writeTo(c); - } - return session.getResult(); - }, - this::getBlob); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + return Retrying.run( + getOptions(), + retryAlgorithmManager.getFor(req), + () -> { + content.rewindTo(0); + UnbufferedWritableByteChannelSession<WriteObjectResponse> session = + ResumableMedia.gapic() + .write() + .byteChannel(storageClient.writeObjectCallable().withDefaultCallContext(merge)) + .setByteStringStrategy(ByteStringStrategy.noCopy()) + .setHasher(hasher) + .direct() + .unbuffered() + .setRequest(req) + .build(); + + try (UnbufferedWritableByteChannel c = session.open()) { + content.writeTo(c); + } + return session.getResult(); + }, + this::getBlob); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } finally { + otelSpan.end(); + } } @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java index db807ce6ce..0d08759d1f 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java @@ -219,6 +219,7 @@ public void close() throws IOException { // We never created any parts // create an empty object try { + // TODO: Add in Otel context when available BlobInfo blobInfo = storage.internalDirectUpload(ultimateObject, opts, Buffers.allocate(0)); finalObject.set(blobInfo); return; @@ -285,6 +286,7 @@ private void internalFlush(ByteBuffer buf) { ApiFutures.immediateFuture(partInfo), info -> { try { + // TODO: Add in Otel context when available return storage.internalDirectUpload(info, partOpts, buf); } catch (StorageException e) { // a precondition failure usually means the part was created, but we didn't get the 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 ee538bcde8..1726660ca9 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 @@ -50,6 +50,7 @@ import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt; import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt; import com.google.cloud.storage.UnifiedOpts.Opts; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.cloud.storage.spi.v1.StorageRpc.RewriteRequest; import com.google.common.base.CharMatcher; @@ -1737,7 +1738,11 @@ public BlobInfo internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOp } @Override - public BlobInfo internalDirectUpload(BlobInfo info, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { + public BlobInfo internalDirectUpload( + BlobInfo info, + Opts<ObjectTargetOpt> opts, + ByteBuffer buf, + OpenTelemetryTraceUtil.Context ctx) { BlobInfo.Builder builder = info.toBuilder() diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java index 0d700c46df..403f03e9ee 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java @@ -20,6 +20,7 @@ import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt; import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt; import com.google.cloud.storage.UnifiedOpts.Opts; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -31,7 +32,16 @@ default BlobInfo internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetO throw new UnsupportedOperationException("not implemented"); } - default BlobInfo internalDirectUpload(BlobInfo info, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { + default BlobInfo internalDirectUpload( + BlobInfo blobInfo, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { + throw new UnsupportedOperationException("not implemented"); + } + + default BlobInfo internalDirectUpload( + BlobInfo info, + Opts<ObjectTargetOpt> opts, + ByteBuffer buf, + OpenTelemetryTraceUtil.Context ctx) { throw new UnsupportedOperationException("not implemented"); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java index 9c71f4842a..d7ce734c07 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java @@ -156,7 +156,6 @@ public Scope makeCurrent() { public OpenTelemetryTraceUtil.Span startSpan(String methodName) { String formatSpanName = String.format("%s.%s/%s", "storage", "client", methodName); SpanBuilder spanBuilder = tracer.spanBuilder(formatSpanName).setSpanKind(SpanKind.CLIENT); - spanBuilder.setAttribute("rpc.system", transport); io.opentelemetry.api.trace.Span span = addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); return new Span(span, formatSpanName); @@ -164,16 +163,17 @@ public OpenTelemetryTraceUtil.Span startSpan(String methodName) { @Override public OpenTelemetryTraceUtil.Span startSpan( - String spanName, OpenTelemetryTraceUtil.Context parent) { + String methodName, OpenTelemetryTraceUtil.Context parent) { assert (parent instanceof OpenTelemetryInstance.Context); + String formatSpanName = String.format("%s.%s/%s", "storage", "client", methodName); SpanBuilder spanBuilder = tracer - .spanBuilder(spanName) + .spanBuilder(formatSpanName) .setSpanKind(SpanKind.CLIENT) .setParent(((OpenTelemetryInstance.Context) parent).context); io.opentelemetry.api.trace.Span span = addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); - return new Span(span, spanName); + return new Span(span, formatSpanName); } @Nonnull @@ -196,6 +196,7 @@ private SpanBuilder addSettingsAttributesToCurrentSpan(SpanBuilder spanBuilder) .put("gcp.client.version", GaxProperties.getLibraryVersion(this.getClass())) .put("gcp.client.repo", "googleapis/java-storage") .put("gcp.client.artifact", "com.google.cloud.google-cloud-storage") + .put("rpc.system", transport) .build()); return spanBuilder; } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java new file mode 100644 index 0000000000..98d24ba159 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.cloud.NoCredentials; +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.registry.Generator; +import com.google.cloud.storage.it.runner.registry.TestBench; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.List; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@SingleBackend(Backend.TEST_BENCH) +public class ITGrpcOpenTelemetryTest { + @Inject public TestBench testBench; + private StorageOptions options; + private SpanExporter exporter; + private Storage storage; + @Inject public Generator generator; + @Inject public BucketInfo testBucket; + + @Before + public void setUp() { + exporter = new TestExporter(); + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(exporter)) + .build()) + .build(); + options = + StorageOptions.grpc() + .setHost(testBench.getGRPCBaseUri()) + .setProjectId("projectId") + .setCredentials(NoCredentials.getInstance()) + .setOpenTelemetrySdk(openTelemetrySdk) + .build(); + storage = options.getService(); + } + + @Test + public void runCreateBucket() { + String bucket = "random-bucket"; + storage.create(BucketInfo.of(bucket)); + TestExporter testExported = (TestExporter) exporter; + SpanData spanData = testExported.getExportedSpans().get(0); + Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); + Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); + Assert.assertEquals( + "com.google.cloud.google-cloud-storage", + getAttributeValue(spanData, "gcp.client.artifact")); + Assert.assertEquals("grpc", getAttributeValue(spanData, "rpc.system")); + } + + @Test + public void runCreateBlob() { + byte[] content = "Hello, World!".getBytes(UTF_8); + BlobId toCreate = BlobId.of(testBucket.getName(), generator.randomObjectName()); + storage.create(BlobInfo.newBuilder(toCreate).build(), content); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + for (SpanData span : spanData) { + Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); + Assert.assertEquals("googleapis/java-storage", getAttributeValue(span, "gcp.client.repo")); + Assert.assertEquals( + "com.google.cloud.google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); + Assert.assertEquals("grpc", getAttributeValue(span, "rpc.system")); + } + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("create"))); + Assert.assertTrue( + spanData.stream().anyMatch(x -> x.getName().contains("internalDirectUpload"))); + Assert.assertEquals(spanData.get(1).getSpanContext(), spanData.get(0).getParentSpanContext()); + } + + private String getAttributeValue(SpanData spanData, String key) { + return spanData.getAttributes().get(AttributeKey.stringKey(key)).toString(); + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/otel/TestExporter.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/otel/TestExporter.java new file mode 100644 index 0000000000..93ac4b3483 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/otel/TestExporter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage.otel; + +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +class TestExporter implements SpanExporter { + + public final List<SpanData> exportedSpans = Collections.synchronizedList(new ArrayList<>()); + + @Override + public CompletableResultCode export(Collection<SpanData> spans) { + exportedSpans.addAll(spans); + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + return null; + } + + @Override + public CompletableResultCode shutdown() { + return null; + } + + public List<SpanData> getExportedSpans() { + return exportedSpans; + } +} From d9fd4bdc526ba9eb86e59596746a967b93daf07a Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Tue, 29 Oct 2024 12:35:06 -0700 Subject: [PATCH 04/28] feat: Add CreateBlob for HTTP (#2803) --- .../cloud/storage/spi/v1/HttpStorageRpc.java | 8 +- .../storage/ITHttpOpenTelemetryTest.java | 103 ++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java 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 fdfec46199..e244903d09 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 @@ -360,7 +360,7 @@ private Span startSpan(String spanName) { @Override public Bucket create(Bucket bucket, Map<Option, ?> options) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create(Bucket,Map)"); + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_BUCKET); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { @@ -388,9 +388,10 @@ public Bucket create(Bucket bucket, Map<Option, ?> options) { @Override public StorageObject create( StorageObject storageObject, final InputStream content, Map<Option, ?> options) { + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_OBJECT); Scope scope = tracer.withSpan(span); - try { + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { Storage.Objects.Insert insert = storage .objects() @@ -415,9 +416,12 @@ public StorageObject create( .setKmsKeyName(Option.KMS_KEY_NAME.getString(options)) .execute(); } catch (IOException ex) { + otelSpan.recordException(ex); + otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); } finally { + otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java new file mode 100644 index 0000000000..c4b43fd7ad --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.cloud.NoCredentials; +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.registry.Generator; +import com.google.cloud.storage.it.runner.registry.TestBench; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.util.List; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@SingleBackend(Backend.TEST_BENCH) +public class ITHttpOpenTelemetryTest { + @Inject public TestBench testBench; + private StorageOptions options; + private SpanExporter exporter; + private Storage storage; + @Inject public Generator generator; + @Inject public BucketInfo testBucket; + + @Before + public void setUp() { + exporter = new TestExporter(); + OpenTelemetrySdk openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(exporter)) + .build()) + .build(); + options = + StorageOptions.http() + .setHost(testBench.getBaseUri()) + .setProjectId("projectId") + .setCredentials(NoCredentials.getInstance()) + .setOpenTelemetrySdk(openTelemetrySdk) + .build(); + storage = options.getService(); + } + + @Test + public void runCreateBucket() { + String bucket = "random-bucket"; + storage.create(BucketInfo.of(bucket)); + TestExporter testExported = (TestExporter) exporter; + SpanData spanData = testExported.getExportedSpans().get(0); + Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); + Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); + Assert.assertEquals( + "com.google.cloud.google-cloud-storage", + getAttributeValue(spanData, "gcp.client.artifact")); + Assert.assertEquals("http", getAttributeValue(spanData, "rpc.system")); + } + + @Test + public void runCreateBlob() { + byte[] content = "Hello, World!".getBytes(UTF_8); + BlobId toCreate = BlobId.of(testBucket.getName(), generator.randomObjectName()); + storage.create(BlobInfo.newBuilder(toCreate).build(), content); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + for (SpanData span : spanData) { + Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); + Assert.assertEquals("googleapis/java-storage", getAttributeValue(span, "gcp.client.repo")); + Assert.assertEquals( + "com.google.cloud.google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); + Assert.assertEquals("http", getAttributeValue(span, "rpc.system")); + } + } + + private String getAttributeValue(SpanData spanData, String key) { + return spanData.getAttributes().get(AttributeKey.stringKey(key)).toString(); + } +} From 4538213c3f63c8e96101897aea97a436741202aa Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:19:56 -0800 Subject: [PATCH 05/28] feat: Instrument HTTP Reads and Rewrites (#2808) * feat: Instrument HTTP Reads and Writes --- .../cloud/storage/spi/v1/HttpStorageRpc.java | 37 +++++++--- .../storage/ITHttpOpenTelemetryTest.java | 68 +++++++++++++++++-- 2 files changed, 90 insertions(+), 15 deletions(-) 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 e244903d09..bb69d83000 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 @@ -848,9 +848,10 @@ private Get createReadRequest(StorageObject from, Map<Option, ?> options) throws @Override public long read( StorageObject from, Map<Option, ?> options, long position, OutputStream outputStream) { + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("read"); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_READ); Scope scope = tracer.withSpan(span); - try { + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { Get req = createReadRequest(from, options); Boolean shouldReturnRawInputStream = Option.RETURN_RAW_INPUT_STREAM.getBoolean(options); if (shouldReturnRawInputStream != null) { @@ -867,6 +868,8 @@ public long read( req.executeMedia().download(outputStream); return mediaHttpDownloader.getNumBytesDownloaded(); } catch (IOException ex) { + otelSpan.recordException(ex); + otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = translate(ex); if (serviceException.getCode() == SC_REQUESTED_RANGE_NOT_SATISFIABLE) { @@ -874,6 +877,7 @@ public long read( } throw serviceException; } finally { + otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -882,9 +886,10 @@ public long read( @Override public Tuple<String, byte[]> read( StorageObject from, Map<Option, ?> options, long position, int bytes) { + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("read"); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_READ); Scope scope = tracer.withSpan(span); - try { + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { checkArgument(position >= 0, "Position should be non-negative, is " + position); Get req = createReadRequest(from, options); Boolean shouldReturnRawInputStream = Option.RETURN_RAW_INPUT_STREAM.getBoolean(options); @@ -902,6 +907,8 @@ public Tuple<String, byte[]> read( String etag = req.getLastResponseHeaders().getETag(); return Tuple.of(etag, output.toByteArray()); } catch (IOException ex) { + otelSpan.recordException(ex); + otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = StorageException.translate(ex); if (serviceException.getCode() == SC_REQUESTED_RANGE_NOT_SATISFIABLE) { @@ -909,6 +916,7 @@ public Tuple<String, byte[]> read( } throw serviceException; } finally { + otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -1158,11 +1166,13 @@ public String open(String signedURL) { @Override public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("openRewrite"); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_OPEN_REWRITE); Scope scope = tracer.withSpan(span); - try { - return rewrite(rewriteRequest, null); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + return rewrite(rewriteRequest, null, openTelemetryTraceUtil.currentContext()); } finally { + otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -1170,18 +1180,25 @@ public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { @Override public RewriteResponse continueRewrite(RewriteResponse previousResponse) { + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("continueRewrite"); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CONTINUE_REWRITE); Scope scope = tracer.withSpan(span); - try { - return rewrite(previousResponse.rewriteRequest, previousResponse.rewriteToken); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + return rewrite( + previousResponse.rewriteRequest, + previousResponse.rewriteToken, + openTelemetryTraceUtil.currentContext()); } finally { + otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } - private RewriteResponse rewrite(RewriteRequest req, String token) { - try { + private RewriteResponse rewrite( + RewriteRequest req, String token, OpenTelemetryTraceUtil.Context ctx) { + OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("rewrite", ctx); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { String userProject = Option.USER_PROJECT.getString(req.sourceOptions); if (userProject == null) { userProject = Option.USER_PROJECT.getString(req.targetOptions); @@ -1232,8 +1249,12 @@ private RewriteResponse rewrite(RewriteRequest req, String token) { rewriteResponse.getRewriteToken(), rewriteResponse.getTotalBytesRewritten().longValue()); } catch (IOException ex) { + otelSpan.recordException(ex); + otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); tracer.getCurrentSpan().setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); + } finally { + otelSpan.end(); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java index c4b43fd7ad..afe7884445 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java @@ -19,6 +19,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.cloud.NoCredentials; +import com.google.cloud.storage.Storage.BlobSourceOption; +import com.google.cloud.storage.Storage.BlobTargetOption; +import com.google.cloud.storage.Storage.CopyRequest; 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; @@ -31,6 +34,10 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.List; import org.junit.Assert; import org.junit.Before; @@ -43,7 +50,10 @@ public class ITHttpOpenTelemetryTest { @Inject public TestBench testBench; private StorageOptions options; private SpanExporter exporter; + private BlobId blobId; private Storage storage; + private static final byte[] helloWorldTextBytes = "hello world".getBytes(); + private static final byte[] helloWorldGzipBytes = TestUtils.gzipBytes(helloWorldTextBytes); @Inject public Generator generator; @Inject public BucketInfo testBucket; @@ -65,6 +75,8 @@ public void setUp() { .setOpenTelemetrySdk(openTelemetrySdk) .build(); storage = options.getService(); + String objectString = generator.randomObjectName(); + blobId = BlobId.of(testBucket.getName(), objectString); } @Test @@ -72,13 +84,8 @@ public void runCreateBucket() { String bucket = "random-bucket"; storage.create(BucketInfo.of(bucket)); TestExporter testExported = (TestExporter) exporter; - SpanData spanData = testExported.getExportedSpans().get(0); - Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); - Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); - Assert.assertEquals( - "com.google.cloud.google-cloud-storage", - getAttributeValue(spanData, "gcp.client.artifact")); - Assert.assertEquals("http", getAttributeValue(spanData, "rpc.system")); + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); } @Test @@ -88,6 +95,53 @@ public void runCreateBlob() { storage.create(BlobInfo.newBuilder(toCreate).build(), content); TestExporter testExported = (TestExporter) exporter; List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + } + + @Test + public void runRead() throws IOException { + BlobInfo blobInfo = + BlobInfo.newBuilder(blobId).setContentEncoding("gzip").setContentType("text/plain").build(); + storage.create(blobInfo, helloWorldGzipBytes); + Path helloWorldTxtGz = File.createTempFile(blobId.getName(), ".txt.gz").toPath(); + storage.downloadTo( + blobId, helloWorldTxtGz, Storage.BlobSourceOption.shouldReturnRawInputStream(true)); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("read"))); + } + + @Test + public void runCopy() { + + byte[] expected = "Hello, World!".getBytes(StandardCharsets.UTF_8); + + BlobInfo info = + BlobInfo.newBuilder(testBucket.getName(), generator.randomObjectName() + "copy/src") + .build(); + Blob cpySrc = storage.create(info, expected, BlobTargetOption.doesNotExist()); + + BlobInfo dst = + BlobInfo.newBuilder(testBucket.getName(), generator.randomObjectName() + "copy/dst") + .build(); + + CopyRequest copyRequest = + CopyRequest.newBuilder() + .setSource(cpySrc.getBlobId()) + .setSourceOptions(BlobSourceOption.generationMatch(cpySrc.getGeneration())) + .setTarget(dst, BlobTargetOption.doesNotExist()) + .build(); + CopyWriter copyWriter = storage.copy(copyRequest); + copyWriter.getResult(); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("openRewrite"))); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("rewrite"))); + } + + private void checkCommonAttributes(List<SpanData> spanData) { for (SpanData span : spanData) { Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); Assert.assertEquals("googleapis/java-storage", getAttributeValue(span, "gcp.client.repo")); From dc5a7340f4acfc89e6599d144869cbe1530e565d Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:47:41 -0800 Subject: [PATCH 06/28] feat: Take in a module name when creating a span (#2811) --- .../google/cloud/storage/GrpcStorageImpl.java | 11 ++++++---- .../otel/NoOpOpenTelemetryInstance.java | 4 ++-- .../storage/otel/OpenTelemetryInstance.java | 8 +++---- .../storage/otel/OpenTelemetryTraceUtil.java | 4 ++-- .../cloud/storage/spi/v1/HttpStorageRpc.java | 21 ++++++++++++------- 5 files changed, 29 insertions(+), 19 deletions(-) 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 5a16b6c299..cf58235678 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 @@ -201,7 +201,8 @@ public void close() throws Exception { @Override public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); Opts<BucketTargetOpt> opts = Opts.unwrap(options).resolveFrom(bucketInfo).prepend(defaultOpts); GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); @@ -248,7 +249,8 @@ public Blob create( BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options) { Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); // Start the otel span to retain information of the origin of the request - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { return internalDirectUpload( blobInfo, @@ -267,7 +269,8 @@ public Blob create( @Override public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); try (OpenTelemetryTraceUtil.Scope ununsed = otelSpan.makeCurrent()) { requireNonNull(blobInfo, "blobInfo must be non null"); InputStream inputStreamParam = firstNonNull(content, new ByteArrayInputStream(ZERO_BYTES)); @@ -830,7 +833,7 @@ public BlobInfo internalDirectUpload( requireNonNull(blobInfo, "blobInfo must be non null"); requireNonNull(buf, "content must be non null"); OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("internalDirectUpload(BlobInfo)", ctx); + openTelemetryTraceUtil.startSpan("internalDirectUpload", this.getClass().getName(), ctx); Opts<ObjectTargetOpt> optsWithDefaults = opts.prepend(defaultOpts); GrpcCallContext grpcCallContext = optsWithDefaults.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java index 814dbafdc4..0106f18f86 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java @@ -24,13 +24,13 @@ class NoOpOpenTelemetryInstance implements OpenTelemetryTraceUtil { @Override - public OpenTelemetryTraceUtil.Span startSpan(String spanName) { + public OpenTelemetryTraceUtil.Span startSpan(String spanName, String module) { return new Span(); } @Override public OpenTelemetryTraceUtil.Span startSpan( - String spanName, OpenTelemetryTraceUtil.Context parent) { + String spanName, String module, OpenTelemetryTraceUtil.Context parent) { return new Span(); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java index d7ce734c07..1c0718fce4 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java @@ -153,8 +153,8 @@ public Scope makeCurrent() { } @Override - public OpenTelemetryTraceUtil.Span startSpan(String methodName) { - String formatSpanName = String.format("%s.%s/%s", "storage", "client", methodName); + public OpenTelemetryTraceUtil.Span startSpan(String methodName, String module) { + String formatSpanName = String.format("%s/%s", module, methodName); SpanBuilder spanBuilder = tracer.spanBuilder(formatSpanName).setSpanKind(SpanKind.CLIENT); io.opentelemetry.api.trace.Span span = addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); @@ -163,9 +163,9 @@ public OpenTelemetryTraceUtil.Span startSpan(String methodName) { @Override public OpenTelemetryTraceUtil.Span startSpan( - String methodName, OpenTelemetryTraceUtil.Context parent) { + String methodName, String module, OpenTelemetryTraceUtil.Context parent) { assert (parent instanceof OpenTelemetryInstance.Context); - String formatSpanName = String.format("%s.%s/%s", "storage", "client", methodName); + String formatSpanName = String.format("%s/%s", module, methodName); SpanBuilder spanBuilder = tracer .spanBuilder(formatSpanName) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java index 932457d05e..a99b1da954 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java @@ -76,13 +76,13 @@ interface Scope extends AutoCloseable { } /** Starts a new span with the given name, sets it as the current span, and returns it. */ - Span startSpan(String spanName); + Span startSpan(String spanName, String module); /** * Starts a new span with the given name and the given context as its parent, sets it as the * current span, and returns it. */ - Span startSpan(String spanName, Context parent); + Span startSpan(String spanName, String module, Context parent); /** Returns the current span. */ @Nonnull 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 bb69d83000..45366e5e1c 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 @@ -360,7 +360,8 @@ private Span startSpan(String spanName) { @Override public Bucket create(Bucket bucket, Map<Option, ?> options) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_BUCKET); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { @@ -388,7 +389,8 @@ public Bucket create(Bucket bucket, Map<Option, ?> options) { @Override public StorageObject create( StorageObject storageObject, final InputStream content, Map<Option, ?> options) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_OBJECT); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { @@ -848,7 +850,8 @@ private Get createReadRequest(StorageObject from, Map<Option, ?> options) throws @Override public long read( StorageObject from, Map<Option, ?> options, long position, OutputStream outputStream) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("read"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("read", this.getClass().getName()); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_READ); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { @@ -886,7 +889,8 @@ public long read( @Override public Tuple<String, byte[]> read( StorageObject from, Map<Option, ?> options, long position, int bytes) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("read"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("read", this.getClass().getName()); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_READ); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { @@ -1166,7 +1170,8 @@ public String open(String signedURL) { @Override public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("openRewrite"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("openRewrite", this.getClass().getName()); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_OPEN_REWRITE); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { @@ -1180,7 +1185,8 @@ public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { @Override public RewriteResponse continueRewrite(RewriteResponse previousResponse) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("continueRewrite"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("continueRewrite", this.getClass().getName()); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CONTINUE_REWRITE); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { @@ -1197,7 +1203,8 @@ public RewriteResponse continueRewrite(RewriteResponse previousResponse) { private RewriteResponse rewrite( RewriteRequest req, String token, OpenTelemetryTraceUtil.Context ctx) { - OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("rewrite", ctx); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("rewrite", this.getClass().getName(), ctx); try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { String userProject = Option.USER_PROJECT.getString(req.sourceOptions); if (userProject == null) { From d7243f4f855c86a1d79908d936bcd92eb2fba759 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:07:27 -0800 Subject: [PATCH 07/28] feat: Instrument HTTP readAllBytes (#2812) * feat: instrument HTTP readAllBytes --- .../google/cloud/storage/spi/v1/HttpStorageRpc.java | 7 ++++++- .../google/cloud/storage/ITHttpOpenTelemetryTest.java | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) 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 45366e5e1c..05fce30f9a 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 @@ -791,9 +791,11 @@ public StorageObject compose( @Override public byte[] load(StorageObject from, Map<Option, ?> options) { + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("load", this.getClass().getName()); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_LOAD); Scope scope = tracer.withSpan(span); - try { + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { Storage.Objects.Get getRequest = storage .objects() @@ -812,9 +814,12 @@ public byte[] load(StorageObject from, Map<Option, ?> options) { getRequest.executeMedia().download(out); return out.toByteArray(); } catch (IOException ex) { + otelSpan.recordException(ex); + otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); } finally { + otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java index afe7884445..be5efedec3 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java @@ -141,6 +141,17 @@ public void runCopy() { Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("rewrite"))); } + @Test + public void runReadAllBytes() { + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); + storage.create(blobInfo, helloWorldTextBytes); + byte[] read = storage.readAllBytes(blobId); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("load"))); + } + private void checkCommonAttributes(List<SpanData> spanData) { for (SpanData span : spanData) { Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); From 2a85338d8b0e7cd275aabd7feed3eaf94ce012d4 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:04:28 -0800 Subject: [PATCH 08/28] feat: Instrument ReadAllBytes and CreateFrom in gRPC (#2815) --- .../google/cloud/storage/GrpcStorageImpl.java | 138 +++++++++++------- .../google/cloud/storage/StorageInternal.java | 6 + .../storage/ITGrpcOpenTelemetryTest.java | 50 +++++-- 3 files changed, 130 insertions(+), 64 deletions(-) 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 cf58235678..2c00fdf300 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 @@ -315,39 +315,51 @@ public Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options) @Override public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOption... options) throws IOException { - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); - return internalCreateFrom(path, blobInfo, opts); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("createFrom", this.getClass().getName()); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); + return internalCreateFrom(path, blobInfo, opts, openTelemetryTraceUtil.currentContext()); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } finally { + otelSpan.end(); + } } @Override - public Blob internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOpt> opts) + public Blob internalCreateFrom( + Path path, BlobInfo info, Opts<ObjectTargetOpt> opts, OpenTelemetryTraceUtil.Context ctx) throws IOException { + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("internalCreateFrom", this.getClass().getName(), ctx); requireNonNull(path, "path must be non null"); if (Files.isDirectory(path)) { throw new StorageException(0, path + " is a directory"); } - - GrpcCallContext grpcCallContext = - opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); - WriteObjectRequest req = getWriteObjectRequest(info, opts); - - ClientStreamingCallable<WriteObjectRequest, WriteObjectResponse> write = - storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext); - - ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts); - ApiFuture<GrpcResumableSession> session2 = - ApiFutures.transform( - start, - rw -> - ResumableSession.grpc( - getOptions(), - retryAlgorithmManager.idempotent(), - write, - storageClient.queryWriteStatusCallable(), - rw, - Hasher.noop()), - MoreExecutors.directExecutor()); - try { + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + GrpcCallContext grpcCallContext = + opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + WriteObjectRequest req = getWriteObjectRequest(info, opts); + + ClientStreamingCallable<WriteObjectRequest, WriteObjectResponse> write = + storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext); + + ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts); + ApiFuture<GrpcResumableSession> session2 = + ApiFutures.transform( + start, + rw -> + ResumableSession.grpc( + getOptions(), + retryAlgorithmManager.idempotent(), + write, + storageClient.queryWriteStatusCallable(), + rw, + Hasher.noop()), + MoreExecutors.directExecutor()); GrpcResumableSession got = session2.get(); ResumableOperationResult<@Nullable Object> put = got.put(RewindableContent.of(path)); Object object = put.getObject(); @@ -358,7 +370,11 @@ public Blob internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOpt> o } return codecs.blobInfo().decode(object).asBlob(this); } catch (InterruptedException | ExecutionException e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); + } finally { + otelSpan.end(); } } @@ -372,37 +388,46 @@ public Blob createFrom(BlobInfo blobInfo, InputStream content, BlobWriteOption.. public Blob createFrom( BlobInfo blobInfo, InputStream in, int bufferSize, BlobWriteOption... options) throws IOException { - requireNonNull(blobInfo, "blobInfo must be non null"); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("createFrom", this.getClass().getName()); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + requireNonNull(blobInfo, "blobInfo must be non null"); - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); - GrpcCallContext grpcCallContext = - opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); - WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); + GrpcCallContext grpcCallContext = + opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); - ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts); - - BufferedWritableByteChannelSession<WriteObjectResponse> 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(); + ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts); - // Specifically not in the try-with, so we don't close the provided stream - ReadableByteChannel src = - Channels.newChannel(firstNonNull(in, new ByteArrayInputStream(ZERO_BYTES))); - try (BufferedWritableByteChannel dst = session.open()) { - ByteStreams.copy(src, dst); - } catch (Exception e) { - throw StorageException.coalesce(e); + BufferedWritableByteChannelSession<WriteObjectResponse> 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(); + + // Specifically not in the try-with, so we don't close the provided stream + ReadableByteChannel src = + Channels.newChannel(firstNonNull(in, new ByteArrayInputStream(ZERO_BYTES))); + try (BufferedWritableByteChannel dst = session.open()) { + ByteStreams.copy(src, dst); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus( + io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } + return getBlob(session.getResult()); + } finally { + otelSpan.end(); } - return getBlob(session.getResult()); } @Override @@ -732,14 +757,21 @@ public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... optio @Override public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) { + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("readAllBytes", this.getClass().getName()); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (UnbufferedReadableByteChannel r = session.open(); + try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent(); + UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Channels.newChannel(baos)) { ByteStreams.copy(r, w); } catch (ApiException | IOException e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); + } finally { + otelSpan.end(); } return baos.toByteArray(); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java index 403f03e9ee..8fd230a19c 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java @@ -32,6 +32,12 @@ default BlobInfo internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetO throw new UnsupportedOperationException("not implemented"); } + default BlobInfo internalCreateFrom( + Path path, BlobInfo info, Opts<ObjectTargetOpt> opts, OpenTelemetryTraceUtil.Context ctx) + throws IOException { + throw new UnsupportedOperationException("not implemented"); + } + default BlobInfo internalDirectUpload( BlobInfo blobInfo, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { throw new UnsupportedOperationException("not implemented"); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java index 98d24ba159..5d426902a5 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java @@ -31,6 +31,9 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; import java.util.List; import org.junit.Assert; import org.junit.Before; @@ -44,6 +47,8 @@ public class ITGrpcOpenTelemetryTest { private StorageOptions options; private SpanExporter exporter; private Storage storage; + private static final byte[] helloWorldTextBytes = "hello world".getBytes(); + private BlobId blobId; @Inject public Generator generator; @Inject public BucketInfo testBucket; @@ -65,6 +70,8 @@ public void setUp() { .setOpenTelemetrySdk(openTelemetrySdk) .build(); storage = options.getService(); + String objectString = generator.randomObjectName(); + blobId = BlobId.of(testBucket.getName(), objectString); } @Test @@ -72,13 +79,8 @@ public void runCreateBucket() { String bucket = "random-bucket"; storage.create(BucketInfo.of(bucket)); TestExporter testExported = (TestExporter) exporter; - SpanData spanData = testExported.getExportedSpans().get(0); - Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); - Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); - Assert.assertEquals( - "com.google.cloud.google-cloud-storage", - getAttributeValue(spanData, "gcp.client.artifact")); - Assert.assertEquals("grpc", getAttributeValue(spanData, "rpc.system")); + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); } @Test @@ -88,6 +90,36 @@ public void runCreateBlob() { storage.create(BlobInfo.newBuilder(toCreate).build(), content); TestExporter testExported = (TestExporter) exporter; List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("create"))); + Assert.assertTrue( + spanData.stream().anyMatch(x -> x.getName().contains("internalDirectUpload"))); + Assert.assertEquals(spanData.get(1).getSpanContext(), spanData.get(0).getParentSpanContext()); + } + + @Test + public void runReadAllBytes() { + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); + storage.create(blobInfo, helloWorldTextBytes); + byte[] read = storage.readAllBytes(blobId); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("readAllBytes"))); + } + + @Test + public void createFrom() throws IOException { + Path helloWorldTxtGz = File.createTempFile(blobId.getName(), ".txt.gz").toPath(); + storage.createFrom(BlobInfo.newBuilder(blobId).build(), helloWorldTxtGz); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("createFrom"))); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("internalCreateFrom"))); + } + + private void checkCommonAttributes(List<SpanData> spanData) { for (SpanData span : spanData) { Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); Assert.assertEquals("googleapis/java-storage", getAttributeValue(span, "gcp.client.repo")); @@ -95,10 +127,6 @@ public void runCreateBlob() { "com.google.cloud.google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); Assert.assertEquals("grpc", getAttributeValue(span, "rpc.system")); } - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("create"))); - Assert.assertTrue( - spanData.stream().anyMatch(x -> x.getName().contains("internalDirectUpload"))); - Assert.assertEquals(spanData.get(1).getSpanContext(), spanData.get(0).getParentSpanContext()); } private String getAttributeValue(SpanData spanData, String key) { From 57aeade55c1acb09d4afc1d558b23c3022e49ed6 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:56:13 -0800 Subject: [PATCH 09/28] feat: Instrument gRPC downloadTo and Copy (#2818) --- .../google/cloud/storage/GrpcStorageImpl.java | 124 +++++++++++------- .../storage/ITGrpcOpenTelemetryTest.java | 53 +++++++- 2 files changed, 126 insertions(+), 51 deletions(-) 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 2c00fdf300..dd4eb17e90 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 @@ -69,6 +69,8 @@ import com.google.cloud.storage.UnifiedOpts.ProjectId; import com.google.cloud.storage.UnifiedOpts.UserProject; import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Scope; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Span; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -698,56 +700,66 @@ public Blob compose(ComposeRequest composeRequest) { @Override public CopyWriter copy(CopyRequest copyRequest) { - BlobId src = copyRequest.getSource(); - BlobInfo dst = copyRequest.getTarget(); - Opts<ObjectSourceOpt> srcOpts = - Opts.unwrap(copyRequest.getSourceOptions()) - .projectAsSource() - .resolveFrom(src) - .prepend(defaultOpts); - Opts<ObjectTargetOpt> dstOpts = - Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(dst).prepend(defaultOpts); - - Mapper<RewriteObjectRequest.Builder> mapper = - srcOpts.rewriteObjectsRequest().andThen(dstOpts.rewriteObjectsRequest()); - - Object srcProto = codecs.blobId().encode(src); - Object dstProto = codecs.blobInfo().encode(dst); - - RewriteObjectRequest.Builder b = - RewriteObjectRequest.newBuilder() - .setDestinationName(dstProto.getName()) - .setDestinationBucket(dstProto.getBucket()) - // destination_kms_key comes from dstOpts - // according to the docs in the protos, it is illegal to populate the following fields, - // clear them out if they are set - // destination_predefined_acl comes from dstOpts - // if_*_match come from srcOpts and dstOpts - // copy_source_encryption_* come from srcOpts - // common_object_request_params come from dstOpts - .setDestination(dstProto.toBuilder().clearName().clearBucket().clearKmsKey().build()) - .setSourceBucket(srcProto.getBucket()) - .setSourceObject(srcProto.getName()); - - if (src.getGeneration() != null) { - b.setSourceGeneration(src.getGeneration()); - } + Span otelSpan = openTelemetryTraceUtil.startSpan("copy", this.getClass().getName()); + try (Scope unused = otelSpan.makeCurrent()) { + BlobId src = copyRequest.getSource(); + BlobInfo dst = copyRequest.getTarget(); + Opts<ObjectSourceOpt> srcOpts = + Opts.unwrap(copyRequest.getSourceOptions()) + .projectAsSource() + .resolveFrom(src) + .prepend(defaultOpts); + Opts<ObjectTargetOpt> dstOpts = + Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(dst).prepend(defaultOpts); + + Mapper<RewriteObjectRequest.Builder> mapper = + srcOpts.rewriteObjectsRequest().andThen(dstOpts.rewriteObjectsRequest()); + + Object srcProto = codecs.blobId().encode(src); + Object dstProto = codecs.blobInfo().encode(dst); + + RewriteObjectRequest.Builder b = + RewriteObjectRequest.newBuilder() + .setDestinationName(dstProto.getName()) + .setDestinationBucket(dstProto.getBucket()) + // destination_kms_key comes from dstOpts + // according to the docs in the protos, it is illegal to populate the following + // fields, + // clear them out if they are set + // destination_predefined_acl comes from dstOpts + // if_*_match come from srcOpts and dstOpts + // copy_source_encryption_* come from srcOpts + // common_object_request_params come from dstOpts + .setDestination(dstProto.toBuilder().clearName().clearBucket().clearKmsKey().build()) + .setSourceBucket(srcProto.getBucket()) + .setSourceObject(srcProto.getName()); + + if (src.getGeneration() != null) { + b.setSourceGeneration(src.getGeneration()); + } - if (copyRequest.getMegabytesCopiedPerChunk() != null) { - b.setMaxBytesRewrittenPerCall(copyRequest.getMegabytesCopiedPerChunk() * _1MiB); - } + if (copyRequest.getMegabytesCopiedPerChunk() != null) { + b.setMaxBytesRewrittenPerCall(copyRequest.getMegabytesCopiedPerChunk() * _1MiB); + } - RewriteObjectRequest req = mapper.apply(b).build(); - GrpcCallContext grpcCallContext = - srcOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); - UnaryCallable<RewriteObjectRequest, RewriteResponse> callable = - storageClient.rewriteObjectCallable().withDefaultCallContext(grpcCallContext); - GrpcCallContext retryContext = Retrying.newCallContext(); - return Retrying.run( - getOptions(), - retryAlgorithmManager.getFor(req), - () -> callable.call(req, retryContext), - (resp) -> new GapicCopyWriter(this, callable, retryAlgorithmManager.idempotent(), resp)); + RewriteObjectRequest req = mapper.apply(b).build(); + GrpcCallContext grpcCallContext = + srcOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + UnaryCallable<RewriteObjectRequest, RewriteResponse> callable = + storageClient.rewriteObjectCallable().withDefaultCallContext(grpcCallContext); + GrpcCallContext retryContext = Retrying.newCallContext(); + return Retrying.run( + getOptions(), + retryAlgorithmManager.getFor(req), + () -> callable.call(req, retryContext), + (resp) -> new GapicCopyWriter(this, callable, retryAlgorithmManager.idempotent(), resp)); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } finally { + otelSpan.end(); + } } @Override @@ -803,27 +815,39 @@ public GrpcBlobReadChannel reader(BlobId blob, BlobSourceOption... options) { @Override public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) { + Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", this.getClass().getName()); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); - try (UnbufferedReadableByteChannel r = session.open(); + try (Scope unused = otelSpan.makeCurrent(); + UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Files.newByteChannel(path, WRITE_OPS)) { ByteStreams.copy(r, w); } catch (ApiException | IOException e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); + } finally { + otelSpan.end(); } } @Override public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) { + Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", this.getClass().getName()); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); - try (UnbufferedReadableByteChannel r = session.open(); + try (Scope unused = otelSpan.makeCurrent(); + UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Channels.newChannel(outputStream)) { ByteStreams.copy(r, w); } catch (ApiException | IOException e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); + } finally { + otelSpan.end(); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java index 5d426902a5..c0f599e3ab 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java @@ -19,6 +19,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.cloud.NoCredentials; +import com.google.cloud.storage.Storage.BlobSourceOption; +import com.google.cloud.storage.Storage.BlobTargetOption; +import com.google.cloud.storage.Storage.CopyRequest; 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; @@ -31,9 +34,11 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import org.junit.Assert; import org.junit.Before; @@ -51,6 +56,7 @@ public class ITGrpcOpenTelemetryTest { private BlobId blobId; @Inject public Generator generator; @Inject public BucketInfo testBucket; + private static final Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir")); @Before public void setUp() { @@ -109,7 +115,7 @@ public void runReadAllBytes() { } @Test - public void createFrom() throws IOException { + public void runCreateFrom() throws IOException { Path helloWorldTxtGz = File.createTempFile(blobId.getName(), ".txt.gz").toPath(); storage.createFrom(BlobInfo.newBuilder(blobId).build(), helloWorldTxtGz); TestExporter testExported = (TestExporter) exporter; @@ -119,6 +125,51 @@ public void createFrom() throws IOException { Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("internalCreateFrom"))); } + @Test + public void runDownloadToPath() throws IOException { + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); + storage.create(blobInfo, helloWorldTextBytes); + try (TmpFile file = TmpFile.of(tmpDir, "download-to", ".txt")) { + storage.downloadTo(blobId, file.getPath()); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("downloadTo"))); + } + } + + @Test + public void runDownloadToOutputStream() { + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); + storage.create(blobInfo, helloWorldTextBytes); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + storage.downloadTo(blobId, baos); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("downloadTo"))); + } + + @Test + public void runCopy() { + BlobInfo info = + BlobInfo.newBuilder(testBucket, generator.randomObjectName() + "copy/src").build(); + Blob cpySrc = storage.create(info, helloWorldTextBytes, BlobTargetOption.doesNotExist()); + BlobInfo dst = + BlobInfo.newBuilder(testBucket, generator.randomObjectName() + "copy/dst").build(); + CopyRequest copyRequest = + CopyRequest.newBuilder() + .setSource(cpySrc.getBlobId()) + .setSourceOptions(BlobSourceOption.generationMatch(cpySrc.getGeneration())) + .setTarget(dst, BlobTargetOption.doesNotExist()) + .build(); + storage.copy(copyRequest); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("copy"))); + } + private void checkCommonAttributes(List<SpanData> spanData) { for (SpanData span : spanData) { Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); From bd2fd5e37d36c72eca8e7f78dffcfd0ef8dfe7d4 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Mon, 25 Nov 2024 10:39:13 -0800 Subject: [PATCH 10/28] feat: Instrument HTTP createFrom (#2824) * feat: Instrument HTTP createFrom * make exception handling more generic --- .../com/google/cloud/storage/StorageImpl.java | 132 ++++++++----- .../storage/ITHttpOpenTelemetryTest.java | 22 +++ .../storage/it/runner/registry/TestBench.java | 179 +----------------- 3 files changed, 105 insertions(+), 228 deletions(-) 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 1726660ca9..fbb93d1b22 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 @@ -66,6 +66,7 @@ import com.google.common.io.BaseEncoding; import com.google.common.io.CountingOutputStream; import com.google.common.primitives.Ints; +import io.opentelemetry.api.trace.StatusCode; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -123,12 +124,14 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage, final HttpRetryAlgorithmManager retryAlgorithmManager; final StorageRpc storageRpc; final WriterFactory writerFactory; + private final OpenTelemetryTraceUtil openTelemetryTraceUtil; StorageImpl(HttpStorageOptions options, WriterFactory writerFactory) { super(options); this.retryAlgorithmManager = options.getRetryAlgorithmManager(); this.storageRpc = options.getStorageRpcV1(); this.writerFactory = writerFactory; + this.openTelemetryTraceUtil = OpenTelemetryTraceUtil.getInstance(options); } @Override @@ -243,45 +246,55 @@ public Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options) @Override public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOption... options) throws IOException { - if (Files.isDirectory(path)) { - throw new StorageException(0, path + " is a directory"); - } - long size = Files.size(path); - if (size == 0L) { - return create(blobInfo, null, options); - } - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); - final Map<StorageRpc.Option, ?> 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<String> uploadIdSupplier = - ResumableMedia.startUploadForBlobInfo( - getOptions(), - updated, - optionsMap, - retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); - JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); - - JsonResumableSession session = - ResumableSession.json( - HttpClientContext.from(storageRpc), - getOptions().asRetryDependencies(), - retryAlgorithmManager.idempotent(), - jsonResumableWrite); - HttpContentRange contentRange = HttpContentRange.of(ByteRangeSpec.explicit(0L, size), size); - ResumableOperationResult<StorageObject> put = - session.put(RewindableContent.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(); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("createFrom", this.getClass().getName()); + try (OpenTelemetryTraceUtil.Scope scope = otelSpan.makeCurrent()) { + if (Files.isDirectory(path)) { + throw new StorageException(0, path + " is a directory"); + } + long size = Files.size(path); + if (size == 0L) { + return create(blobInfo, null, options); + } + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); + final Map<StorageRpc.Option, ?> 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<String> uploadIdSupplier = + ResumableMedia.startUploadForBlobInfo( + getOptions(), + updated, + optionsMap, + retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); + + JsonResumableSession session = + ResumableSession.json( + HttpClientContext.from(storageRpc), + getOptions().asRetryDependencies(), + retryAlgorithmManager.idempotent(), + jsonResumableWrite); + HttpContentRange contentRange = HttpContentRange.of(ByteRangeSpec.explicit(0L, size), size); + ResumableOperationResult<StorageObject> put = + session.put(RewindableContent.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); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } finally { + otelSpan.end(); } - return codecs.blobInfo().decode(object).asBlob(this); } @Override @@ -294,20 +307,35 @@ public Blob createFrom(BlobInfo blobInfo, InputStream content, BlobWriteOption.. public Blob createFrom( BlobInfo blobInfo, InputStream content, int bufferSize, BlobWriteOption... options) throws IOException { - - ApiFuture<BlobInfo> objectFuture; - try (StorageWriteChannel writer = writer(blobInfo, options)) { - objectFuture = writer.getObject(); - uploadHelper(Channels.newChannel(content), writer, bufferSize); - } - // 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); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("createFrom", this.getClass().getName()); + try (OpenTelemetryTraceUtil.Scope scope = otelSpan.makeCurrent()) { + + ApiFuture<BlobInfo> objectFuture; + try (StorageWriteChannel writer = writer(blobInfo, options)) { + objectFuture = writer.getObject(); + uploadHelper(Channels.newChannel(content), writer, bufferSize); + } + // 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) { + otelSpan.recordException(e); + otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); + // We don't want to wrap the storage exception, but we want to record any other exception + // we simply throw the exception after recording in the span. + throw e; + + } finally { + otelSpan.end(); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java index be5efedec3..a042d490fe 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java @@ -34,8 +34,10 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; @@ -152,6 +154,26 @@ public void runReadAllBytes() { Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("load"))); } + @Test + public void runCreateFromPath() throws IOException { + Path helloWorldTxtGz = File.createTempFile(blobId.getName(), ".txt.gz").toPath(); + storage.createFrom(BlobInfo.newBuilder(blobId).build(), helloWorldTxtGz); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("createFrom"))); + } + + @Test + public void runCreateFromInputStream() throws IOException { + InputStream inputStream = new ByteArrayInputStream(helloWorldTextBytes); + storage.createFrom(BlobInfo.newBuilder(blobId).build(), inputStream); + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("createFrom"))); + } + private void checkCommonAttributes(List<SpanData> spanData) { for (SpanData span : spanData) { Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java index 8a58a07212..186e1225b5 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java @@ -16,7 +16,6 @@ package com.google.cloud.storage.it.runner.registry; -import static com.google.cloud.RetryHelper.runWithRetries; import static java.util.Objects.requireNonNull; import com.google.api.client.http.ByteArrayContent; @@ -26,10 +25,6 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.core.NanoClock; -import com.google.api.gax.retrying.BasicResultRetryAlgorithm; -import com.google.api.gax.retrying.RetrySettings; -import com.google.cloud.RetryHelper.RetryHelperException; import com.google.cloud.Tuple; import com.google.cloud.conformance.storage.v1.InstructionList; import com.google.cloud.conformance.storage.v1.Method; @@ -48,19 +43,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.net.SocketException; -import java.net.URI; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.threeten.bp.Duration; /** * A {@link ManagedLifecycle} which integrates with the <a target="_blank" @@ -183,11 +172,7 @@ public List<RetryTestResource> listRetryTests() throws IOException { } private boolean startGRPCServer(int gRPCPort) throws IOException { - GenericUrl url = new GenericUrl(baseUri + "/start_grpc?port=9090"); - HttpRequest req = requestFactory.buildGetRequest(url); - HttpResponse resp = req.execute(); - resp.disconnect(); - return resp.getStatusCode() == 200; + return true; } @Override @@ -196,168 +181,10 @@ public Object get() { } @Override - public void start() { - try { - tempDirectory = Files.createTempDirectory(containerName); - outPath = tempDirectory.resolve("stdout"); - errPath = tempDirectory.resolve("stderr"); - - File outFile = outPath.toFile(); - File errFile = errPath.toFile(); - LOGGER.info("Redirecting server stdout to: " + outFile.getAbsolutePath()); - LOGGER.info("Redirecting server stderr to: " + errFile.getAbsolutePath()); - String dockerImage = String.format("%s:%s", dockerImageName, dockerImageTag); - // First try and pull the docker image, this validates docker is available and running - // on the host, as well as gives time for the image to be downloaded independently of - // trying to start the container. (Below, when we first start the container we then attempt - // to issue a call against the api before we yield to run our tests.) - try { - Process p = - new ProcessBuilder() - .command("docker", "pull", dockerImage) - .redirectOutput(outFile) - .redirectError(errFile) - .start(); - p.waitFor(5, TimeUnit.MINUTES); - if (!ignorePullError && p.exitValue() != 0) { - dumpServerLogs(outPath, errPath); - throw new IllegalStateException( - String.format( - "Non-zero status while attempting to pull docker image '%s'", dockerImage)); - } - } catch (InterruptedException | IllegalThreadStateException e) { - dumpServerLogs(outPath, errPath); - throw new IllegalStateException( - String.format("Timeout while attempting to pull docker image '%s'", dockerImage)); - } - - int port = URI.create(baseUri).getPort(); - int gRPCPort = URI.create(gRPCBaseUri).getPort(); - final List<String> command = - ImmutableList.of( - "docker", - "run", - "-i", - "--rm", - "--publish", - port + ":9000", - "--publish", - gRPCPort + ":9090", - String.format("--name=%s", containerName), - dockerImage); - process = - new ProcessBuilder() - .command(command) - .redirectOutput(outFile) - .redirectError(errFile) - .start(); - LOGGER.log(Level.INFO, command.toString()); - try { - // wait a small amount of time for the server to come up before probing - Thread.sleep(500); - // wait for the server to come up - List<RetryTestResource> existingResources = - runWithRetries( - TestBench.this::listRetryTests, - RetrySettings.newBuilder() - .setTotalTimeout(Duration.ofSeconds(30)) - .setInitialRetryDelay(Duration.ofMillis(500)) - .setRetryDelayMultiplier(1.5) - .setMaxRetryDelay(Duration.ofSeconds(5)) - .build(), - new BasicResultRetryAlgorithm<List<RetryTestResource>>() { - @Override - public boolean shouldRetry( - Throwable previousThrowable, List<RetryTestResource> previousResponse) { - return previousThrowable instanceof SocketException; - } - }, - NanoClock.getDefaultClock()); - if (!existingResources.isEmpty()) { - LOGGER.info( - "Test Server already has retry tests in it, is it running outside the tests?"); - } - // Start gRPC Service - if (!startGRPCServer(gRPCPort)) { - throw new IllegalStateException( - "Failed to start server within a reasonable amount of time. Host url(gRPC): " - + gRPCBaseUri); - } - } catch (RetryHelperException e) { - dumpServerLogs(outPath, errPath); - throw new IllegalStateException( - "Failed to connect to server within a reasonable amount of time. Host url: " + baseUri, - e.getCause()); - } - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - } + public void start() {} @Override - public void stop() { - try { - process.destroy(); - process.waitFor(2, TimeUnit.SECONDS); - boolean attemptForceStopContainer = false; - try { - int processExitValue = process.exitValue(); - if (processExitValue != 0) { - attemptForceStopContainer = true; - } - System.out.println("processExitValue = " + processExitValue); - LOGGER.warning("Container exit value = " + processExitValue); - } catch (IllegalThreadStateException e) { - attemptForceStopContainer = true; - } - - if (attemptForceStopContainer) { - LOGGER.warning("Container did not gracefully exit, attempting to explicitly stop it."); - System.out.println("Container did not gracefully exit, attempting to explicitly stop it."); - ImmutableList<String> command = ImmutableList.of("docker", "kill", containerName); - System.out.println("command = " + command); - LOGGER.log(Level.WARNING, command.toString()); - Process shutdownProcess = new ProcessBuilder(command).start(); - shutdownProcess.waitFor(5, TimeUnit.SECONDS); - int shutdownProcessExitValue = shutdownProcess.exitValue(); - LOGGER.warning("Container exit value = " + shutdownProcessExitValue); - } - - // wait for the server to shutdown - runWithRetries( - () -> { - try { - listRetryTests(); - } catch (SocketException e) { - // desired result - return null; - } - throw new NotShutdownException(); - }, - RetrySettings.newBuilder() - .setTotalTimeout(Duration.ofSeconds(30)) - .setInitialRetryDelay(Duration.ofMillis(500)) - .setRetryDelayMultiplier(1.5) - .setMaxRetryDelay(Duration.ofSeconds(5)) - .build(), - new BasicResultRetryAlgorithm<List<?>>() { - @Override - public boolean shouldRetry(Throwable previousThrowable, List<?> previousResponse) { - return previousThrowable instanceof NotShutdownException; - } - }, - NanoClock.getDefaultClock()); - try { - Files.delete(errPath); - Files.delete(outPath); - Files.delete(tempDirectory); - } catch (IOException e) { - throw new RuntimeException(e); - } - } catch (InterruptedException | IOException e) { - throw new RuntimeException(e); - } - } + public void stop() {} private void dumpServerLogs(Path outFile, Path errFile) throws IOException { try { From 6a2b3cac7ae0303b8073a32eca9528f825849e9e Mon Sep 17 00:00:00 2001 From: BenWhitehead <BenWhitehead@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:46:26 -0500 Subject: [PATCH 11/28] chore: misc otel internalization/cleanup (#2835) --- .../google/cloud/storage/GrpcStorageImpl.java | 45 ++++++++++--------- .../com/google/cloud/storage/StorageImpl.java | 9 ++-- .../storage/otel/OpenTelemetryInstance.java | 30 ++++++------- .../storage/otel/OpenTelemetryTraceUtil.java | 24 ++++++++++ .../cloud/storage/otel/package-info.java | 26 +++++++++++ .../cloud/storage/spi/v1/HttpStorageRpc.java | 33 +++++++------- 6 files changed, 109 insertions(+), 58 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/otel/package-info.java 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 dd4eb17e90..28bd9a3d3c 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 @@ -26,6 +26,7 @@ import static com.google.cloud.storage.StorageV2ProtoUtils.objectAclEntityOrAltEq; import static com.google.cloud.storage.Utils.bucketNameCodec; import static com.google.cloud.storage.Utils.ifNonNull; +import static com.google.cloud.storage.otel.OpenTelemetryTraceUtil.MODULE_STORAGE; import static com.google.common.base.MoreObjects.firstNonNull; import static java.util.Objects.requireNonNull; @@ -204,7 +205,7 @@ public void close() throws Exception { @Override public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); Opts<BucketTargetOpt> opts = Opts.unwrap(options).resolveFrom(bucketInfo).prepend(defaultOpts); GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); @@ -219,7 +220,7 @@ public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { .setParent("projects/_"); CreateBucketRequest req = opts.createBucketsRequest().apply(builder).build(); GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { return Retrying.run( getOptions(), retryAlgorithmManager.getFor(req), @@ -252,8 +253,8 @@ public Blob create( Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); // Start the otel span to retain information of the origin of the request OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { return internalDirectUpload( blobInfo, opts, @@ -272,8 +273,8 @@ public Blob create( @Override public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); - try (OpenTelemetryTraceUtil.Scope ununsed = otelSpan.makeCurrent()) { + openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { requireNonNull(blobInfo, "blobInfo must be non null"); InputStream inputStreamParam = firstNonNull(content, new ByteArrayInputStream(ZERO_BYTES)); @@ -318,8 +319,8 @@ public Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options) public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOption... options) throws IOException { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("createFrom", this.getClass().getName()); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); return internalCreateFrom(path, blobInfo, opts, openTelemetryTraceUtil.currentContext()); } catch (Exception e) { @@ -336,12 +337,12 @@ public Blob internalCreateFrom( Path path, BlobInfo info, Opts<ObjectTargetOpt> opts, OpenTelemetryTraceUtil.Context ctx) throws IOException { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("internalCreateFrom", this.getClass().getName(), ctx); + openTelemetryTraceUtil.startSpan("internalCreateFrom", MODULE_STORAGE, ctx); requireNonNull(path, "path must be non null"); if (Files.isDirectory(path)) { throw new StorageException(0, path + " is a directory"); } - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); WriteObjectRequest req = getWriteObjectRequest(info, opts); @@ -391,8 +392,8 @@ public Blob createFrom( BlobInfo blobInfo, InputStream in, int bufferSize, BlobWriteOption... options) throws IOException { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("createFrom", this.getClass().getName()); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { requireNonNull(blobInfo, "blobInfo must be non null"); Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); @@ -700,8 +701,8 @@ public Blob compose(ComposeRequest composeRequest) { @Override public CopyWriter copy(CopyRequest copyRequest) { - Span otelSpan = openTelemetryTraceUtil.startSpan("copy", this.getClass().getName()); - try (Scope unused = otelSpan.makeCurrent()) { + Span otelSpan = openTelemetryTraceUtil.startSpan("copy", MODULE_STORAGE); + try (Scope ignored = otelSpan.makeCurrent()) { BlobId src = copyRequest.getSource(); BlobInfo dst = copyRequest.getTarget(); Opts<ObjectSourceOpt> srcOpts = @@ -770,11 +771,11 @@ public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... optio @Override public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("readAllBytes", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("readAllBytes", MODULE_STORAGE); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent(); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent(); UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Channels.newChannel(baos)) { ByteStreams.copy(r, w); @@ -815,11 +816,11 @@ public GrpcBlobReadChannel reader(BlobId blob, BlobSourceOption... options) { @Override public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", this.getClass().getName()); + Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", MODULE_STORAGE); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); - try (Scope unused = otelSpan.makeCurrent(); + try (Scope ignored = otelSpan.makeCurrent(); UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Files.newByteChannel(path, WRITE_OPS)) { ByteStreams.copy(r, w); @@ -834,11 +835,11 @@ public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) { @Override public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", this.getClass().getName()); + Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", MODULE_STORAGE); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); - try (Scope unused = otelSpan.makeCurrent(); + try (Scope ignored = otelSpan.makeCurrent(); UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Channels.newChannel(outputStream)) { ByteStreams.copy(r, w); @@ -889,7 +890,7 @@ public BlobInfo internalDirectUpload( requireNonNull(blobInfo, "blobInfo must be non null"); requireNonNull(buf, "content must be non null"); OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("internalDirectUpload", this.getClass().getName(), ctx); + openTelemetryTraceUtil.startSpan("internalDirectUpload", MODULE_STORAGE, ctx); Opts<ObjectTargetOpt> optsWithDefaults = opts.prepend(defaultOpts); GrpcCallContext grpcCallContext = optsWithDefaults.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); @@ -897,7 +898,7 @@ public BlobInfo internalDirectUpload( Hasher hasher = Hasher.enabled(); GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); RewindableContent content = RewindableContent.of(buf); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { return Retrying.run( getOptions(), retryAlgorithmManager.getFor(req), 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 fbb93d1b22..cbb16941ce 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 @@ -17,6 +17,7 @@ package com.google.cloud.storage; import static com.google.cloud.storage.SignedUrlEncodingHelper.Rfc3986UriEncode; +import static com.google.cloud.storage.otel.OpenTelemetryTraceUtil.MODULE_STORAGE; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -247,8 +248,8 @@ public Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options) public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOption... options) throws IOException { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("createFrom", this.getClass().getName()); - try (OpenTelemetryTraceUtil.Scope scope = otelSpan.makeCurrent()) { + openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { if (Files.isDirectory(path)) { throw new StorageException(0, path + " is a directory"); } @@ -308,8 +309,8 @@ public Blob createFrom( BlobInfo blobInfo, InputStream content, int bufferSize, BlobWriteOption... options) throws IOException { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("createFrom", this.getClass().getName()); - try (OpenTelemetryTraceUtil.Scope scope = otelSpan.makeCurrent()) { + openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { ApiFuture<BlobInfo> objectFuture; try (StorageWriteChannel writer = writer(blobInfo, options)) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java index 1c0718fce4..87be2d56d5 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java @@ -16,12 +16,11 @@ package com.google.cloud.storage.otel; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.api.core.ApiFuture; -import com.google.api.gax.core.GaxProperties; import com.google.cloud.storage.GrpcStorageOptions; import com.google.cloud.storage.StorageOptions; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Context; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Span; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -42,11 +41,10 @@ class OpenTelemetryInstance implements OpenTelemetryTraceUtil { private final String transport; - public OpenTelemetryInstance(StorageOptions storageOptions) { + OpenTelemetryInstance(StorageOptions storageOptions) { this.storageOptions = storageOptions; this.openTelemetry = storageOptions.getOpenTelemetrySdk(); - this.tracer = - openTelemetry.getTracer(LIBRARY_NAME, GaxProperties.getLibraryVersion(this.getClass())); + this.tracer = openTelemetry.getTracer(LIBRARY_NAME, storageOptions.getLibraryVersion()); this.transport = storageOptions instanceof GrpcStorageOptions ? "grpc" : "http"; } @@ -54,7 +52,7 @@ static class Span implements OpenTelemetryTraceUtil.Span { private final io.opentelemetry.api.trace.Span span; private final String spanName; - Span(io.opentelemetry.api.trace.Span span, String spanName) { + private Span(io.opentelemetry.api.trace.Span span, String spanName) { this.span = span; this.spanName = spanName; } @@ -129,7 +127,7 @@ public <T> void endAtFuture(ApiFuture<T> futureValue) {} static class Scope implements OpenTelemetryTraceUtil.Scope { private final io.opentelemetry.context.Scope scope; - Scope(io.opentelemetry.context.Scope scope) { + private Scope(io.opentelemetry.context.Scope scope) { this.scope = scope; } @@ -142,7 +140,7 @@ public void close() { static class Context implements OpenTelemetryTraceUtil.Context { private final io.opentelemetry.context.Context context; - Context(io.opentelemetry.context.Context context) { + private Context(io.opentelemetry.context.Context context) { this.context = context; } @@ -164,13 +162,13 @@ public OpenTelemetryTraceUtil.Span startSpan(String methodName, String module) { @Override public OpenTelemetryTraceUtil.Span startSpan( String methodName, String module, OpenTelemetryTraceUtil.Context parent) { - assert (parent instanceof OpenTelemetryInstance.Context); + checkArgument( + parent instanceof OpenTelemetryInstance.Context, + "parent must be an instance of " + OpenTelemetryInstance.Context.class.getName()); String formatSpanName = String.format("%s/%s", module, methodName); + Context p2 = (Context) parent; SpanBuilder spanBuilder = - tracer - .spanBuilder(formatSpanName) - .setSpanKind(SpanKind.CLIENT) - .setParent(((OpenTelemetryInstance.Context) parent).context); + tracer.spanBuilder(formatSpanName).setSpanKind(SpanKind.CLIENT).setParent(p2.context); io.opentelemetry.api.trace.Span span = addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); return new Span(span, formatSpanName); @@ -193,9 +191,9 @@ private SpanBuilder addSettingsAttributesToCurrentSpan(SpanBuilder spanBuilder) spanBuilder = spanBuilder.setAllAttributes( Attributes.builder() - .put("gcp.client.version", GaxProperties.getLibraryVersion(this.getClass())) + .put("gcp.client.version", storageOptions.getLibraryVersion()) .put("gcp.client.repo", "googleapis/java-storage") - .put("gcp.client.artifact", "com.google.cloud.google-cloud-storage") + .put("gcp.client.artifact", "com.google.cloud:google-cloud-storage") .put("rpc.system", transport) .build()); return spanBuilder; diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java index a99b1da954..ed76c31e39 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java @@ -17,13 +17,20 @@ package com.google.cloud.storage.otel; import com.google.api.core.ApiFuture; +import com.google.api.core.InternalApi; +import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; +import com.google.cloud.storage.spi.v1.StorageRpc; import io.opentelemetry.api.trace.StatusCode; import java.util.Map; import javax.annotation.Nonnull; +@InternalApi public interface OpenTelemetryTraceUtil { + String MODULE_STORAGE = Storage.class.getName(); + String MODULE_STORAGE_RPC = StorageRpc.class.getName(); + @InternalApi static OpenTelemetryTraceUtil getInstance(@Nonnull StorageOptions storageOptions) { boolean createNoOp = storageOptions.getOpenTelemetrySdk() == null; @@ -35,23 +42,31 @@ static OpenTelemetryTraceUtil getInstance(@Nonnull StorageOptions storageOptions } /** Represents a trace span. */ + @InternalApi interface Span { + @InternalApi Span recordException(Throwable error); + @InternalApi Span setStatus(StatusCode status, String name); /** Adds the given event to this span. */ + @InternalApi Span addEvent(String name); /** Adds the given event with the given attributes to this span. */ + @InternalApi Span addEvent(String name, Map<String, Object> attributes); /** Marks this span as the current span. */ + @InternalApi Scope makeCurrent(); /** Ends this span. */ + @InternalApi void end(); /** Ends this span in an error. */ + @InternalApi void end(Throwable error); /** @@ -60,35 +75,44 @@ interface Span { * future. In order for telemetry info to be recorded, the future returned by this method should * be completed. */ + @InternalApi <T> void endAtFuture(ApiFuture<T> futureValue); } /** Represents a trace context. */ + @InternalApi interface Context { /** Makes this context the current context. */ + @InternalApi Scope makeCurrent(); } /** Represents a trace scope. */ + @InternalApi interface Scope extends AutoCloseable { /** Closes the current scope. */ + @InternalApi void close(); } /** Starts a new span with the given name, sets it as the current span, and returns it. */ + @InternalApi Span startSpan(String spanName, String module); /** * Starts a new span with the given name and the given context as its parent, sets it as the * current span, and returns it. */ + @InternalApi Span startSpan(String spanName, String module, Context parent); /** Returns the current span. */ @Nonnull + @InternalApi Span currentSpan(); /** Returns the current Context. */ @Nonnull + @InternalApi Context currentContext(); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/package-info.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/package-info.java new file mode 100644 index 0000000000..d8fac3ca8e --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Set of internal utilities to make our OTel use a bit more terse. + * + * <p>All classes, interfaces, etc are considered to be for internal library use only and can break + * at any time. + */ +@InternalApi +package com.google.cloud.storage.otel; + +import com.google.api.core.InternalApi; 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 05fce30f9a..9f449c36f6 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 @@ -16,6 +16,7 @@ package com.google.cloud.storage.spi.v1; +import static com.google.cloud.storage.otel.OpenTelemetryTraceUtil.MODULE_STORAGE_RPC; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -361,10 +362,10 @@ private Span startSpan(String spanName) { @Override public Bucket create(Bucket bucket, Map<Option, ?> options) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_BUCKET); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { return storage .buckets() .insert(this.options.getProjectId(), bucket) @@ -390,10 +391,10 @@ public Bucket create(Bucket bucket, Map<Option, ?> options) { public StorageObject create( StorageObject storageObject, final InputStream content, Map<Option, ?> options) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_OBJECT); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { Storage.Objects.Insert insert = storage .objects() @@ -792,10 +793,10 @@ public StorageObject compose( @Override public byte[] load(StorageObject from, Map<Option, ?> options) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("load", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("load", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_LOAD); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { Storage.Objects.Get getRequest = storage .objects() @@ -856,10 +857,10 @@ private Get createReadRequest(StorageObject from, Map<Option, ?> options) throws public long read( StorageObject from, Map<Option, ?> options, long position, OutputStream outputStream) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("read", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("read", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_READ); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { Get req = createReadRequest(from, options); Boolean shouldReturnRawInputStream = Option.RETURN_RAW_INPUT_STREAM.getBoolean(options); if (shouldReturnRawInputStream != null) { @@ -895,10 +896,10 @@ public long read( public Tuple<String, byte[]> read( StorageObject from, Map<Option, ?> options, long position, int bytes) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("read", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("read", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_READ); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { checkArgument(position >= 0, "Position should be non-negative, is " + position); Get req = createReadRequest(from, options); Boolean shouldReturnRawInputStream = Option.RETURN_RAW_INPUT_STREAM.getBoolean(options); @@ -1176,10 +1177,10 @@ public String open(String signedURL) { @Override public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("openRewrite", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("openRewrite", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_OPEN_REWRITE); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { return rewrite(rewriteRequest, null, openTelemetryTraceUtil.currentContext()); } finally { otelSpan.end(); @@ -1191,10 +1192,10 @@ public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { @Override public RewriteResponse continueRewrite(RewriteResponse previousResponse) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("continueRewrite", this.getClass().getName()); + openTelemetryTraceUtil.startSpan("continueRewrite", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CONTINUE_REWRITE); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { return rewrite( previousResponse.rewriteRequest, previousResponse.rewriteToken, @@ -1209,8 +1210,8 @@ public RewriteResponse continueRewrite(RewriteResponse previousResponse) { private RewriteResponse rewrite( RewriteRequest req, String token, OpenTelemetryTraceUtil.Context ctx) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("rewrite", this.getClass().getName(), ctx); - try (OpenTelemetryTraceUtil.Scope unused = otelSpan.makeCurrent()) { + openTelemetryTraceUtil.startSpan("rewrite", MODULE_STORAGE_RPC, ctx); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { String userProject = Option.USER_PROJECT.getString(req.sourceOptions); if (userProject == null) { userProject = Option.USER_PROJECT.getString(req.targetOptions); From 125fc4eef6f36080c9f89bd85c25021c16aedeb7 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:09:32 -0800 Subject: [PATCH 12/28] feat: Instrument Reader and Writer methods (#2829) * feat: Instrument Reader and Writer methods --- .../google/cloud/storage/GrpcStorageImpl.java | 79 +++++--- .../com/google/cloud/storage/StorageImpl.java | 95 ++++++---- .../storage/ITGrpcOpenTelemetryTest.java | 29 ++- .../storage/ITHttpOpenTelemetryTest.java | 29 ++- .../cloud/storage/ITOpenTelemetryTest.java | 4 +- .../storage/it/runner/registry/TestBench.java | 175 +++++++++++++++++- 6 files changed, 341 insertions(+), 70 deletions(-) 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 28bd9a3d3c..d15b53dadf 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 @@ -801,17 +801,26 @@ public GrpcBlobReadChannel reader(String bucket, String blob, BlobSourceOption.. @Override public GrpcBlobReadChannel reader(BlobId blob, BlobSourceOption... options) { - Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts); - ReadObjectRequest request = getReadObjectRequest(blob, opts); - GrpcCallContext grpcCallContext = Retrying.newCallContext(); - - return new GrpcBlobReadChannel( - storageClient.readObjectCallable().withDefaultCallContext(grpcCallContext), - getOptions(), - retryAlgorithmManager.getFor(request), - responseContentLifecycleManager, - request, - !opts.autoGzipDecompression()); + Span otelSpan = openTelemetryTraceUtil.startSpan("reader", this.getClass().getName()); + try (Scope unused = otelSpan.makeCurrent()) { + Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts); + ReadObjectRequest request = getReadObjectRequest(blob, opts); + GrpcCallContext grpcCallContext = Retrying.newCallContext(); + + return new GrpcBlobReadChannel( + storageClient.readObjectCallable().withDefaultCallContext(grpcCallContext), + getOptions(), + retryAlgorithmManager.getFor(request), + responseContentLifecycleManager, + request, + !opts.autoGzipDecompression()); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } finally { + otelSpan.end(); + } } @Override @@ -854,25 +863,35 @@ public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption. @Override public GrpcBlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); - GrpcCallContext grpcCallContext = - opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); - WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); - Hasher hasher = Hasher.noop(); - // in JSON, the starting of the resumable session happens before the invocation of write can - // happen. Emulate the same thing here. - // 1. create the future - ApiFuture<ResumableWrite> startResumableWrite = startResumableWrite(grpcCallContext, req, opts); - // 2. await the result of the future - ResumableWrite resumableWrite = ApiFutureUtils.await(startResumableWrite); - // 3. wrap the result in another future container before constructing the BlobWriteChannel - ApiFuture<ResumableWrite> wrapped = ApiFutures.immediateFuture(resumableWrite); - return new GrpcBlobWriteChannel( - storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext), - getOptions(), - retryAlgorithmManager.idempotent(), - () -> wrapped, - hasher); + Span otelSpan = openTelemetryTraceUtil.startSpan("writer", this.getClass().getName()); + try (Scope unused = otelSpan.makeCurrent()) { + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); + GrpcCallContext grpcCallContext = + opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); + Hasher hasher = Hasher.noop(); + // in JSON, the starting of the resumable session happens before the invocation of write can + // happen. Emulate the same thing here. + // 1. create the future + ApiFuture<ResumableWrite> startResumableWrite = + startResumableWrite(grpcCallContext, req, opts); + // 2. await the result of the future + ResumableWrite resumableWrite = ApiFutureUtils.await(startResumableWrite); + // 3. wrap the result in another future container before constructing the BlobWriteChannel + ApiFuture<ResumableWrite> wrapped = ApiFutures.immediateFuture(resumableWrite); + return new GrpcBlobWriteChannel( + storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext), + getOptions(), + retryAlgorithmManager.idempotent(), + () -> wrapped, + hasher); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw StorageException.coalesce(e); + } finally { + otelSpan.end(); + } } @Override 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 cbb16941ce..053bc45ede 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 @@ -52,6 +52,8 @@ import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt; import com.google.cloud.storage.UnifiedOpts.Opts; import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Scope; +import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Span; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.cloud.storage.spi.v1.StorageRpc.RewriteRequest; import com.google.common.base.CharMatcher; @@ -744,10 +746,19 @@ public StorageReadChannel reader(String bucket, String blob, BlobSourceOption... @Override public StorageReadChannel reader(BlobId blob, BlobSourceOption... options) { - Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob); - StorageObject storageObject = Conversions.json().blobId().encode(blob); - ImmutableMap<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); - return new BlobReadChannelV2(storageObject, optionsMap, BlobReadChannelContext.from(this)); + Span otelSpan = openTelemetryTraceUtil.startSpan("reader", this.getClass().getName()); + try (Scope unused = otelSpan.makeCurrent()) { + Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob); + StorageObject storageObject = Conversions.json().blobId().encode(blob); + ImmutableMap<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); + return new BlobReadChannelV2(storageObject, optionsMap, BlobReadChannelContext.from(this)); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } @Override @@ -778,40 +789,58 @@ public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption. @Override public StorageWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); - final Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); - BlobInfo.Builder builder = blobInfo.toBuilder().setMd5(null).setCrc32c(null); - BlobInfo updated = opts.blobInfoMapper().apply(builder).build(); + Span otelSpan = openTelemetryTraceUtil.startSpan("writer", this.getClass().getName()); + try (Scope unused = otelSpan.makeCurrent()) { + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); + final Map<StorageRpc.Option, ?> 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); - // open the resumable session outside the write channel - // the exception behavior of open is different from #write(ByteBuffer) - Supplier<String> uploadIdSupplier = - ResumableMedia.startUploadForBlobInfo( - getOptions(), - updated, - optionsMap, - retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); - JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); - return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); + 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<String> uploadIdSupplier = + ResumableMedia.startUploadForBlobInfo( + getOptions(), + updated, + optionsMap, + retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); + return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } @Override 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()); - // open the resumable session outside the write channel - // the exception behavior of open is different from #write(ByteBuffer) - String signedUrlString = signedURL.toString(); - Supplier<String> uploadIdSupplier = - ResumableMedia.startUploadForSignedUrl( - getOptions(), signedURL, forResumableUploadSessionCreate); - JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(signedUrlString, uploadIdSupplier.get(), 0); - return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); + Span otelSpan = openTelemetryTraceUtil.startSpan("writer", this.getClass().getName()); + try (Scope unused = otelSpan.makeCurrent()) { + // 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()); + // open the resumable session outside the write channel + // the exception behavior of open is different from #write(ByteBuffer) + String signedUrlString = signedURL.toString(); + Supplier<String> uploadIdSupplier = + ResumableMedia.startUploadForSignedUrl( + getOptions(), signedURL, forResumableUploadSessionCreate); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(signedUrlString, uploadIdSupplier.get(), 0); + return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } @Override diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java index c0f599e3ab..96ac92b7d9 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java @@ -19,6 +19,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.cloud.NoCredentials; +import com.google.cloud.ReadChannel; +import com.google.cloud.WriteChannel; import com.google.cloud.storage.Storage.BlobSourceOption; import com.google.cloud.storage.Storage.BlobTargetOption; import com.google.cloud.storage.Storage.CopyRequest; @@ -170,12 +172,37 @@ public void runCopy() { Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("copy"))); } + @Test + public void runWriter() throws IOException { + BlobInfo info = BlobInfo.newBuilder(testBucket, generator.randomObjectName()).build(); + try (WriteChannel writer = storage.writer(info)) { + // Do nothing + } + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("writer"))); + } + + @Test + public void runReader() throws IOException { + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); + storage.create(blobInfo, helloWorldTextBytes); + try (ReadChannel reader = storage.reader(blobId)) { + // Do nothing + } + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("reader"))); + } + private void checkCommonAttributes(List<SpanData> spanData) { for (SpanData span : spanData) { Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); Assert.assertEquals("googleapis/java-storage", getAttributeValue(span, "gcp.client.repo")); Assert.assertEquals( - "com.google.cloud.google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); + "com.google.cloud:google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); Assert.assertEquals("grpc", getAttributeValue(span, "rpc.system")); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java index a042d490fe..16384a2371 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java @@ -19,6 +19,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.cloud.NoCredentials; +import com.google.cloud.ReadChannel; +import com.google.cloud.WriteChannel; import com.google.cloud.storage.Storage.BlobSourceOption; import com.google.cloud.storage.Storage.BlobTargetOption; import com.google.cloud.storage.Storage.CopyRequest; @@ -174,12 +176,37 @@ public void runCreateFromInputStream() throws IOException { Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("createFrom"))); } + @Test + public void runWriter() throws IOException { + BlobInfo info = BlobInfo.newBuilder(testBucket, generator.randomObjectName()).build(); + try (WriteChannel writer = storage.writer(info)) { + // Do nothing + } + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("writer"))); + } + + @Test + public void runReader() throws IOException { + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); + storage.create(blobInfo, helloWorldTextBytes); + try (ReadChannel reader = storage.reader(blobId)) { + // Do nothing + } + TestExporter testExported = (TestExporter) exporter; + List<SpanData> spanData = testExported.getExportedSpans(); + checkCommonAttributes(spanData); + Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("reader"))); + } + private void checkCommonAttributes(List<SpanData> spanData) { for (SpanData span : spanData) { Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); Assert.assertEquals("googleapis/java-storage", getAttributeValue(span, "gcp.client.repo")); Assert.assertEquals( - "com.google.cloud.google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); + "com.google.cloud:google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); Assert.assertEquals("http", getAttributeValue(span, "rpc.system")); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java index c3261de542..363f1fa08b 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java @@ -55,7 +55,7 @@ public void checkInstrumentation() { Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); Assert.assertEquals( - "com.google.cloud.google-cloud-storage", + "com.google.cloud:google-cloud-storage", getAttributeValue(spanData, "gcp.client.artifact")); Assert.assertEquals("http", getAttributeValue(spanData, "rpc.system")); @@ -84,7 +84,7 @@ public void checkInstrumentationGrpc() { Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); Assert.assertEquals( - "com.google.cloud.google-cloud-storage", + "com.google.cloud:google-cloud-storage", getAttributeValue(spanData, "gcp.client.artifact")); Assert.assertEquals("grpc", getAttributeValue(spanData, "rpc.system")); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java index 186e1225b5..5cfe6bff0d 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java @@ -16,6 +16,7 @@ package com.google.cloud.storage.it.runner.registry; +import static com.google.cloud.RetryHelper.runWithRetries; import static java.util.Objects.requireNonNull; import com.google.api.client.http.ByteArrayContent; @@ -25,6 +26,10 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.core.NanoClock; +import com.google.api.gax.retrying.BasicResultRetryAlgorithm; +import com.google.api.gax.retrying.RetrySettings; +import com.google.cloud.RetryHelper.RetryHelperException; import com.google.cloud.Tuple; import com.google.cloud.conformance.storage.v1.InstructionList; import com.google.cloud.conformance.storage.v1.Method; @@ -43,13 +48,19 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.SocketException; +import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.threeten.bp.Duration; /** * A {@link ManagedLifecycle} which integrates with the <a target="_blank" @@ -172,7 +183,11 @@ public List<RetryTestResource> listRetryTests() throws IOException { } private boolean startGRPCServer(int gRPCPort) throws IOException { - return true; + GenericUrl url = new GenericUrl(baseUri + "/start_grpc?port=9090"); + HttpRequest req = requestFactory.buildGetRequest(url); + HttpResponse resp = req.execute(); + resp.disconnect(); + return resp.getStatusCode() == 200; } @Override @@ -181,10 +196,164 @@ public Object get() { } @Override - public void start() {} + public void start() { + try { + tempDirectory = Files.createTempDirectory(containerName); + outPath = tempDirectory.resolve("stdout"); + errPath = tempDirectory.resolve("stderr"); + File outFile = outPath.toFile(); + File errFile = errPath.toFile(); + LOGGER.info("Redirecting server stdout to: " + outFile.getAbsolutePath()); + LOGGER.info("Redirecting server stderr to: " + errFile.getAbsolutePath()); + String dockerImage = String.format("%s:%s", dockerImageName, dockerImageTag); + // First try and pull the docker image, this validates docker is available and running + // on the host, as well as gives time for the image to be downloaded independently of + // trying to start the container. (Below, when we first start the container we then attempt + // to issue a call against the api before we yield to run our tests.) + try { + Process p = + new ProcessBuilder() + .command("docker", "pull", dockerImage) + .redirectOutput(outFile) + .redirectError(errFile) + .start(); + p.waitFor(5, TimeUnit.MINUTES); + if (!ignorePullError && p.exitValue() != 0) { + dumpServerLogs(outPath, errPath); + throw new IllegalStateException( + String.format( + "Non-zero status while attempting to pull docker image '%s'", dockerImage)); + } + } catch (InterruptedException | IllegalThreadStateException e) { + dumpServerLogs(outPath, errPath); + throw new IllegalStateException( + String.format("Timeout while attempting to pull docker image '%s'", dockerImage)); + } + int port = URI.create(baseUri).getPort(); + int gRPCPort = URI.create(gRPCBaseUri).getPort(); + final List<String> command = + ImmutableList.of( + "docker", + "run", + "-i", + "--rm", + "--publish", + port + ":9000", + "--publish", + gRPCPort + ":9090", + String.format("--name=%s", containerName), + dockerImage); + process = + new ProcessBuilder() + .command(command) + .redirectOutput(outFile) + .redirectError(errFile) + .start(); + LOGGER.log(Level.INFO, command.toString()); + try { + // wait a small amount of time for the server to come up before probing + Thread.sleep(500); + // wait for the server to come up + List<RetryTestResource> existingResources = + runWithRetries( + TestBench.this::listRetryTests, + RetrySettings.newBuilder() + .setTotalTimeout(Duration.ofSeconds(30)) + .setInitialRetryDelay(Duration.ofMillis(500)) + .setRetryDelayMultiplier(1.5) + .setMaxRetryDelay(Duration.ofSeconds(5)) + .build(), + new BasicResultRetryAlgorithm<List<RetryTestResource>>() { + @Override + public boolean shouldRetry( + Throwable previousThrowable, List<RetryTestResource> previousResponse) { + return previousThrowable instanceof SocketException; + } + }, + NanoClock.getDefaultClock()); + if (!existingResources.isEmpty()) { + LOGGER.info( + "Test Server already has retry tests in it, is it running outside the tests?"); + } + // Start gRPC Service + if (!startGRPCServer(gRPCPort)) { + throw new IllegalStateException( + "Failed to start server within a reasonable amount of time. Host url(gRPC): " + + gRPCBaseUri); + } + } catch (RetryHelperException e) { + dumpServerLogs(outPath, errPath); + throw new IllegalStateException( + "Failed to connect to server within a reasonable amount of time. Host url: " + baseUri, + e.getCause()); + } + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } @Override - public void stop() {} + public void stop() { + try { + process.destroy(); + process.waitFor(2, TimeUnit.SECONDS); + boolean attemptForceStopContainer = false; + try { + int processExitValue = process.exitValue(); + if (processExitValue != 0) { + attemptForceStopContainer = true; + } + System.out.println("processExitValue = " + processExitValue); + LOGGER.warning("Container exit value = " + processExitValue); + } catch (IllegalThreadStateException e) { + attemptForceStopContainer = true; + } + if (attemptForceStopContainer) { + LOGGER.warning("Container did not gracefully exit, attempting to explicitly stop it."); + System.out.println("Container did not gracefully exit, attempting to explicitly stop it."); + ImmutableList<String> command = ImmutableList.of("docker", "kill", containerName); + System.out.println("command = " + command); + LOGGER.log(Level.WARNING, command.toString()); + Process shutdownProcess = new ProcessBuilder(command).start(); + shutdownProcess.waitFor(5, TimeUnit.SECONDS); + int shutdownProcessExitValue = shutdownProcess.exitValue(); + LOGGER.warning("Container exit value = " + shutdownProcessExitValue); + } + // wait for the server to shutdown + runWithRetries( + () -> { + try { + listRetryTests(); + } catch (SocketException e) { + // desired result + return null; + } + throw new NotShutdownException(); + }, + RetrySettings.newBuilder() + .setTotalTimeout(Duration.ofSeconds(30)) + .setInitialRetryDelay(Duration.ofMillis(500)) + .setRetryDelayMultiplier(1.5) + .setMaxRetryDelay(Duration.ofSeconds(5)) + .build(), + new BasicResultRetryAlgorithm<List<?>>() { + @Override + public boolean shouldRetry(Throwable previousThrowable, List<?> previousResponse) { + return previousThrowable instanceof NotShutdownException; + } + }, + NanoClock.getDefaultClock()); + try { + Files.delete(errPath); + Files.delete(outPath); + Files.delete(tempDirectory); + } catch (IOException e) { + throw new RuntimeException(e); + } + } catch (InterruptedException | IOException e) { + throw new RuntimeException(e); + } + } private void dumpServerLogs(Path outFile, Path errFile) throws IOException { try { From 56af5550aec37709e5c9782f2b30e732d956736f Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:06:28 -0800 Subject: [PATCH 13/28] chore: remove unnecessary context propagation (#2853) --- .../google/cloud/storage/GrpcStorageImpl.java | 24 ++++--------------- ...lelCompositeUploadWritableByteChannel.java | 2 -- .../com/google/cloud/storage/StorageImpl.java | 6 +---- .../google/cloud/storage/StorageInternal.java | 15 ------------ .../cloud/storage/spi/v1/HttpStorageRpc.java | 12 ++++------ 5 files changed, 10 insertions(+), 49 deletions(-) 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 d15b53dadf..1747772d42 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 @@ -255,11 +255,7 @@ public Blob create( OpenTelemetryTraceUtil.Span otelSpan = openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - return internalDirectUpload( - blobInfo, - opts, - ByteBuffer.wrap(content, offset, length), - openTelemetryTraceUtil.currentContext()) + return internalDirectUpload(blobInfo, opts, ByteBuffer.wrap(content, offset, length)) .asBlob(this); } catch (Exception e) { otelSpan.recordException(e); @@ -322,7 +318,7 @@ public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOp openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); - return internalCreateFrom(path, blobInfo, opts, openTelemetryTraceUtil.currentContext()); + return internalCreateFrom(path, blobInfo, opts); } catch (Exception e) { otelSpan.recordException(e); otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); @@ -333,11 +329,10 @@ public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOp } @Override - public Blob internalCreateFrom( - Path path, BlobInfo info, Opts<ObjectTargetOpt> opts, OpenTelemetryTraceUtil.Context ctx) + public Blob internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOpt> opts) throws IOException { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("internalCreateFrom", MODULE_STORAGE, ctx); + openTelemetryTraceUtil.startSpan("internalCreateFrom", MODULE_STORAGE); requireNonNull(path, "path must be non null"); if (Files.isDirectory(path)) { throw new StorageException(0, path + " is a directory"); @@ -897,19 +892,10 @@ public GrpcBlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options @Override public BlobInfo internalDirectUpload( BlobInfo blobInfo, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { - return internalDirectUpload(blobInfo, opts, buf, null); - } - - @Override - public BlobInfo internalDirectUpload( - BlobInfo blobInfo, - Opts<ObjectTargetOpt> opts, - ByteBuffer buf, - OpenTelemetryTraceUtil.Context ctx) { requireNonNull(blobInfo, "blobInfo must be non null"); requireNonNull(buf, "content must be non null"); OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("internalDirectUpload", MODULE_STORAGE, ctx); + openTelemetryTraceUtil.startSpan("internalDirectUpload", MODULE_STORAGE); Opts<ObjectTargetOpt> optsWithDefaults = opts.prepend(defaultOpts); GrpcCallContext grpcCallContext = optsWithDefaults.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java index 0d08759d1f..db807ce6ce 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java @@ -219,7 +219,6 @@ public void close() throws IOException { // We never created any parts // create an empty object try { - // TODO: Add in Otel context when available BlobInfo blobInfo = storage.internalDirectUpload(ultimateObject, opts, Buffers.allocate(0)); finalObject.set(blobInfo); return; @@ -286,7 +285,6 @@ private void internalFlush(ByteBuffer buf) { ApiFutures.immediateFuture(partInfo), info -> { try { - // TODO: Add in Otel context when available return storage.internalDirectUpload(info, partOpts, buf); } catch (StorageException e) { // a precondition failure usually means the part was created, but we didn't get the 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 053bc45ede..4f7ae06cb1 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 @@ -1796,11 +1796,7 @@ public BlobInfo internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOp } @Override - public BlobInfo internalDirectUpload( - BlobInfo info, - Opts<ObjectTargetOpt> opts, - ByteBuffer buf, - OpenTelemetryTraceUtil.Context ctx) { + public BlobInfo internalDirectUpload(BlobInfo info, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { BlobInfo.Builder builder = info.toBuilder() diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java index 8fd230a19c..fa150d64d8 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java @@ -20,7 +20,6 @@ import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt; import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt; import com.google.cloud.storage.UnifiedOpts.Opts; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -32,25 +31,11 @@ default BlobInfo internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetO throw new UnsupportedOperationException("not implemented"); } - default BlobInfo internalCreateFrom( - Path path, BlobInfo info, Opts<ObjectTargetOpt> opts, OpenTelemetryTraceUtil.Context ctx) - throws IOException { - throw new UnsupportedOperationException("not implemented"); - } - default BlobInfo internalDirectUpload( BlobInfo blobInfo, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { throw new UnsupportedOperationException("not implemented"); } - default BlobInfo internalDirectUpload( - BlobInfo info, - Opts<ObjectTargetOpt> opts, - ByteBuffer buf, - OpenTelemetryTraceUtil.Context ctx) { - throw new UnsupportedOperationException("not implemented"); - } - // Void to allow easier mapping/use within streams and other mapping contexts @SuppressWarnings("UnusedReturnValue") default Void internalObjectDelete(BlobId id, Opts<ObjectSourceOpt> opts) { 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 9f449c36f6..84621063b9 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 @@ -1181,7 +1181,7 @@ public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_OPEN_REWRITE); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - return rewrite(rewriteRequest, null, openTelemetryTraceUtil.currentContext()); + return rewrite(rewriteRequest, null); } finally { otelSpan.end(); scope.close(); @@ -1196,10 +1196,7 @@ public RewriteResponse continueRewrite(RewriteResponse previousResponse) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CONTINUE_REWRITE); Scope scope = tracer.withSpan(span); try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - return rewrite( - previousResponse.rewriteRequest, - previousResponse.rewriteToken, - openTelemetryTraceUtil.currentContext()); + return rewrite(previousResponse.rewriteRequest, previousResponse.rewriteToken); } finally { otelSpan.end(); scope.close(); @@ -1207,10 +1204,9 @@ public RewriteResponse continueRewrite(RewriteResponse previousResponse) { } } - private RewriteResponse rewrite( - RewriteRequest req, String token, OpenTelemetryTraceUtil.Context ctx) { + private RewriteResponse rewrite(RewriteRequest req, String token) { OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("rewrite", MODULE_STORAGE_RPC, ctx); + openTelemetryTraceUtil.startSpan("rewrite", MODULE_STORAGE_RPC); try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { String userProject = Option.USER_PROJECT.getString(req.sourceOptions); if (userProject == null) { From 156b64398418e3846555f242fbc6a1837f76eaff Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:15:02 -0800 Subject: [PATCH 14/28] lint --- .../java/com/google/cloud/storage/GrpcStorageOptions.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index 06b9e3a265..bda0ac9ccb 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -356,7 +356,7 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw public OpenTelemetrySdk getOpenTelemetrySdk() { return openTelemetrySdk; } - + /** @since 2.14.0 */ @Override public GrpcStorageOptions.Builder toBuilder() { @@ -630,7 +630,6 @@ public GrpcStorageOptions.Builder setBlobWriteSessionConfig( return this; } - /** * Enable OpenTelemetry Tracing and provide an instance for the client to use. * @@ -641,7 +640,7 @@ public GrpcStorageOptions.Builder setOpenTelemetrySdk(OpenTelemetrySdk openTelem this.openTelemetrySdk = openTelemetrySdk; return this; } - + /** @since 2.14.0 */ @Override public GrpcStorageOptions build() { From cf94763cf68780c1d16de744492547f99e15aecf Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Tue, 17 Dec 2024 10:49:44 -0800 Subject: [PATCH 15/28] fix test failures --- .../main/java/com/google/cloud/storage/GrpcStorageImpl.java | 2 +- .../java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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 8dc9c27f68..4a9cbb5234 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 @@ -320,7 +320,7 @@ public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOp } catch (Exception e) { otelSpan.recordException(e); otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); + throw e; } finally { otelSpan.end(); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java index 16384a2371..def465d215 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java @@ -43,6 +43,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; +import java.util.UUID; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -85,7 +86,7 @@ public void setUp() { @Test public void runCreateBucket() { - String bucket = "random-bucket"; + String bucket = "random-bucket" + UUID.randomUUID(); storage.create(BucketInfo.of(bucket)); TestExporter testExported = (TestExporter) exporter; List<SpanData> spanData = testExported.getExportedSpans(); From 55e5770b883a3baf492a4fbcd269e07659c0269b Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Tue, 17 Dec 2024 11:58:30 -0800 Subject: [PATCH 16/28] remove otel-v1-branch sync repo settings and fix integ test --- .github/sync-repo-settings.yaml | 16 ---------------- .../com/google/cloud/storage/StorageImpl.java | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index f2d105459d..3336e9eeeb 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -163,22 +163,6 @@ branchProtectionRules: - 'Kokoro - Test: Java GraalVM Native Image' - 'Kokoro - Test: Java 17 GraalVM Native Image' - javadoc - - pattern: otel-v1-branch - isAdminEnforced: true - requiredApprovingReviewCount: 1 - requiresCodeOwnerReviews: true - requiresStrictStatusChecks: false - requiredStatusCheckContexts: - - dependencies (17) - - lint - - clirr - - units (8) - - units (11) - - 'Kokoro - Test: Integration' - - cla/google - - 'Kokoro - Test: Java GraalVM Native Image' - - 'Kokoro - Test: Java 17 GraalVM Native Image' - - javadoc permissionRules: - team: yoshi-admins permission: admin 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 4f7ae06cb1..7580d444a2 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 @@ -294,7 +294,7 @@ public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOp } catch (Exception e) { otelSpan.recordException(e); otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); + throw e; } finally { otelSpan.end(); } From 7c19e784d38fea1f8689720e53ac6d55a7840d5a Mon Sep 17 00:00:00 2001 From: BenWhitehead <BenWhitehead@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:51:07 -0500 Subject: [PATCH 17/28] fix: cleanup span names for reader and writer (#2855) --- .../com/google/cloud/storage/GrpcStorageImpl.java | 8 ++++---- .../java/com/google/cloud/storage/StorageImpl.java | 12 ++++++------ .../cloud/storage/otel/OpenTelemetryInstance.java | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) 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 4a9cbb5234..41f08713af 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 @@ -794,8 +794,8 @@ public GrpcBlobReadChannel reader(String bucket, String blob, BlobSourceOption.. @Override public GrpcBlobReadChannel reader(BlobId blob, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("reader", this.getClass().getName()); - try (Scope unused = otelSpan.makeCurrent()) { + Span otelSpan = openTelemetryTraceUtil.startSpan("reader", MODULE_STORAGE); + try (Scope ignore = otelSpan.makeCurrent()) { Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts); ReadObjectRequest request = getReadObjectRequest(blob, opts); GrpcCallContext grpcCallContext = Retrying.newCallContext(); @@ -856,8 +856,8 @@ public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption. @Override public GrpcBlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("writer", this.getClass().getName()); - try (Scope unused = otelSpan.makeCurrent()) { + Span otelSpan = openTelemetryTraceUtil.startSpan("writer", MODULE_STORAGE); + try (Scope ignore = otelSpan.makeCurrent()) { Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); 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 7580d444a2..70ba16d155 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 @@ -746,8 +746,8 @@ public StorageReadChannel reader(String bucket, String blob, BlobSourceOption... @Override public StorageReadChannel reader(BlobId blob, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("reader", this.getClass().getName()); - try (Scope unused = otelSpan.makeCurrent()) { + Span otelSpan = openTelemetryTraceUtil.startSpan("reader", MODULE_STORAGE); + try (Scope ignored = otelSpan.makeCurrent()) { Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob); StorageObject storageObject = Conversions.json().blobId().encode(blob); ImmutableMap<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); @@ -789,8 +789,8 @@ public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption. @Override public StorageWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("writer", this.getClass().getName()); - try (Scope unused = otelSpan.makeCurrent()) { + Span otelSpan = openTelemetryTraceUtil.startSpan("writer", MODULE_STORAGE); + try (Scope ignored = otelSpan.makeCurrent()) { Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); final Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); BlobInfo.Builder builder = blobInfo.toBuilder().setMd5(null).setCrc32c(null); @@ -819,8 +819,8 @@ public StorageWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) @Override public StorageWriteChannel writer(URL signedURL) { - Span otelSpan = openTelemetryTraceUtil.startSpan("writer", this.getClass().getName()); - try (Scope unused = otelSpan.makeCurrent()) { + Span otelSpan = openTelemetryTraceUtil.startSpan("writer", MODULE_STORAGE); + try (Scope ignored = otelSpan.makeCurrent()) { // TODO: is it possible to know if a signed url is configured to have a constraint which makes // it idempotent? ResultRetryAlgorithm<?> forResumableUploadSessionCreate = diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java index 87be2d56d5..dd9f5d4233 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java @@ -153,7 +153,7 @@ public Scope makeCurrent() { @Override public OpenTelemetryTraceUtil.Span startSpan(String methodName, String module) { String formatSpanName = String.format("%s/%s", module, methodName); - SpanBuilder spanBuilder = tracer.spanBuilder(formatSpanName).setSpanKind(SpanKind.CLIENT); + SpanBuilder spanBuilder = tracer.spanBuilder(formatSpanName); io.opentelemetry.api.trace.Span span = addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); return new Span(span, formatSpanName); From 92989f520dec8f48c0e8f1a966c0ef94fa28c10e Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:10:13 -0800 Subject: [PATCH 18/28] pr comments --- .../cloud/storage/GrpcStorageOptions.java | 4 +- .../cloud/storage/HttpStorageOptions.java | 8 +- .../storage/ITGrpcOpenTelemetryTest.java | 1 + .../storage/ITHttpOpenTelemetryTest.java | 1 + .../cloud/storage/ITOpenTelemetryTest.java | 107 ++++++++---------- .../storage/it/runner/registry/TestBench.java | 1 + .../cloud/storage/otel/TestExporter.java | 2 +- 7 files changed, 55 insertions(+), 69 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index bda0ac9ccb..0e2a75f922 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -120,7 +120,7 @@ public final class GrpcStorageOptions extends StorageOptions private final boolean grpcClientMetricsManuallyEnabled; private final GrpcInterceptorProvider grpcInterceptorProvider; private final BlobWriteSessionConfig blobWriteSessionConfig; - private OpenTelemetrySdk openTelemetrySdk; + private final OpenTelemetrySdk openTelemetrySdk; private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) { super(builder, serviceDefaults); @@ -634,7 +634,9 @@ public GrpcStorageOptions.Builder setBlobWriteSessionConfig( * Enable OpenTelemetry Tracing and provide an instance for the client to use. * * @param openTelemetrySdk User defined instance of OpenTelemetry SDK to be used by the library + * @since 2.46.1 This new api is in preview and is subject to breaking changes. */ + @BetaApi public GrpcStorageOptions.Builder setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { requireNonNull(openTelemetrySdk, "openTelemetry must be non null"); this.openTelemetrySdk = openTelemetrySdk; 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 bb391a0614..77d9aee314 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 @@ -60,7 +60,7 @@ public class HttpStorageOptions extends StorageOptions { private transient RetryDependenciesAdapter retryDepsAdapter; private final BlobWriteSessionConfig blobWriteSessionConfig; - private OpenTelemetrySdk openTelemetrySdk; + private final OpenTelemetrySdk openTelemetrySdk; private HttpStorageOptions(Builder builder, StorageDefaults serviceDefaults) { super(builder, serviceDefaults); @@ -139,10 +139,6 @@ RetryingDependencies asRetryDependencies() { return retryDepsAdapter; } - public void setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { - this.openTelemetrySdk = openTelemetrySdk; - } - public static class Builder extends StorageOptions.Builder { private StorageRetryStrategy storageRetryStrategy; @@ -288,7 +284,9 @@ public HttpStorageOptions build() { * Enable OpenTelemetry Tracing and provide an instance for the client to use. * * @param openTelemetrySdk + * @since 2.34.0 This new api is in preview and is subject to breaking changes. */ + @BetaApi public HttpStorageOptions.Builder setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { requireNonNull(openTelemetrySdk, "openTelemetry must be non null"); this.openTelemetrySdk = openTelemetrySdk; diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java index 96ac92b7d9..aedbd6549b 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java @@ -30,6 +30,7 @@ import com.google.cloud.storage.it.runner.annotations.SingleBackend; import com.google.cloud.storage.it.runner.registry.Generator; import com.google.cloud.storage.it.runner.registry.TestBench; +import com.google.cloud.storage.otel.TestExporter; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java index def465d215..36f6e0c76c 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java @@ -30,6 +30,7 @@ import com.google.cloud.storage.it.runner.annotations.SingleBackend; import com.google.cloud.storage.it.runner.registry.Generator; import com.google.cloud.storage.it.runner.registry.TestBench; +import com.google.cloud.storage.otel.TestExporter; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java index 363f1fa08b..99bc1386ce 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java @@ -16,18 +16,14 @@ package com.google.cloud.storage; +import com.google.cloud.storage.otel.TestExporter; import com.google.cloud.storage.testing.RemoteStorageHelper; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; import java.util.UUID; import org.junit.Assert; import org.junit.Test; @@ -49,18 +45,21 @@ public void checkInstrumentation() { StorageOptions.http().setOpenTelemetrySdk(openTelemetrySdk).build(); Storage storage = storageOptions.getService(); String bucket = randomBucketName(); - storage.create(BucketInfo.of(bucket)); - TestExporter testExported = (TestExporter) exporter; - SpanData spanData = testExported.getExportedSpans().get(0); - Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); - Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); - Assert.assertEquals( - "com.google.cloud:google-cloud-storage", - getAttributeValue(spanData, "gcp.client.artifact")); - Assert.assertEquals("http", getAttributeValue(spanData, "rpc.system")); - - // Cleanup - RemoteStorageHelper.forceDelete(storage, bucket); + try { + storage.create(BucketInfo.of(bucket)); + TestExporter testExported = (TestExporter) exporter; + SpanData spanData = testExported.getExportedSpans().get(0); + Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); + Assert.assertEquals( + "googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); + Assert.assertEquals( + "com.google.cloud:google-cloud-storage", + getAttributeValue(spanData, "gcp.client.artifact")); + Assert.assertEquals("http", getAttributeValue(spanData, "rpc.system")); + } finally { + // Cleanup + RemoteStorageHelper.forceDelete(storage, bucket); + } } @Test @@ -79,36 +78,45 @@ public void checkInstrumentationGrpc() { Storage storage = storageOptions.getService(); String bucket = randomBucketName(); storage.create(BucketInfo.of(bucket)); - TestExporter testExported = (TestExporter) exporter; - SpanData spanData = testExported.getExportedSpans().get(0); - Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); - Assert.assertEquals("googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); - Assert.assertEquals( - "com.google.cloud:google-cloud-storage", - getAttributeValue(spanData, "gcp.client.artifact")); - Assert.assertEquals("grpc", getAttributeValue(spanData, "rpc.system")); - - // Cleanup - RemoteStorageHelper.forceDelete(storage, bucket); + try { + storage.create(BucketInfo.of(bucket)); + TestExporter testExported = (TestExporter) exporter; + SpanData spanData = testExported.getExportedSpans().get(0); + Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); + Assert.assertEquals( + "googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); + Assert.assertEquals( + "com.google.cloud:google-cloud-storage", + getAttributeValue(spanData, "gcp.client.artifact")); + Assert.assertEquals("grpc", getAttributeValue(spanData, "rpc.system")); + } finally { + // Cleanup + RemoteStorageHelper.forceDelete(storage, bucket); + } } @Test public void noOpDoesNothing() { String httpBucket = randomBucketName(); String grpcBucket = randomBucketName(); - // NoOp for HTTP StorageOptions storageOptionsHttp = StorageOptions.http().build(); Storage storageHttp = storageOptionsHttp.getService(); - storageHttp.create(BucketInfo.of(httpBucket)); - - // NoOp for Grpc StorageOptions storageOptionsGrpc = StorageOptions.grpc().build(); Storage storageGrpc = storageOptionsGrpc.getService(); - storageGrpc.create(BucketInfo.of(grpcBucket)); - - // cleanup - RemoteStorageHelper.forceDelete(storageHttp, httpBucket); - RemoteStorageHelper.forceDelete(storageGrpc, grpcBucket); + try { + // NoOp for HTTP + storageHttp.create(BucketInfo.of(httpBucket)); + + // NoOp for Grpc + storageGrpc.create(BucketInfo.of(grpcBucket)); + + Assert.assertNull(storageOptionsGrpc.getOpenTelemetrySdk()); + Assert.assertNull(storageOptionsHttp.getOpenTelemetrySdk()); + } finally { + // cleanup + RemoteStorageHelper.forceDelete(storageHttp, httpBucket); + RemoteStorageHelper.forceDelete(storageGrpc, grpcBucket); + } } private String getAttributeValue(SpanData spanData, String key) { @@ -119,28 +127,3 @@ public String randomBucketName() { return "java-storage-grpc-rand-" + UUID.randomUUID(); } } - -class TestExporter implements SpanExporter { - - public final List<SpanData> exportedSpans = Collections.synchronizedList(new ArrayList<>()); - - @Override - public CompletableResultCode export(Collection<SpanData> spans) { - exportedSpans.addAll(spans); - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode flush() { - return null; - } - - @Override - public CompletableResultCode shutdown() { - return null; - } - - public List<SpanData> getExportedSpans() { - return exportedSpans; - } -} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java index 0a0dfac5aa..3c595337a8 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java @@ -201,6 +201,7 @@ public void start() { tempDirectory = Files.createTempDirectory(containerName); outPath = tempDirectory.resolve("stdout"); errPath = tempDirectory.resolve("stderr"); + File outFile = outPath.toFile(); File errFile = errPath.toFile(); LOGGER.info("Redirecting server stdout to: " + outFile.getAbsolutePath()); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/otel/TestExporter.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/otel/TestExporter.java index 93ac4b3483..1532fa836b 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/otel/TestExporter.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/otel/TestExporter.java @@ -24,7 +24,7 @@ import java.util.Collections; import java.util.List; -class TestExporter implements SpanExporter { +public class TestExporter implements SpanExporter { public final List<SpanData> exportedSpans = Collections.synchronizedList(new ArrayList<>()); From 588b1c6f3eeea18c872d5af1dcda118239ba4e07 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:12:11 -0800 Subject: [PATCH 19/28] pr comments --- .../main/java/com/google/cloud/storage/StorageInternal.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java index fa150d64d8..0d700c46df 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageInternal.java @@ -31,8 +31,7 @@ default BlobInfo internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetO throw new UnsupportedOperationException("not implemented"); } - default BlobInfo internalDirectUpload( - BlobInfo blobInfo, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { + default BlobInfo internalDirectUpload(BlobInfo info, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { throw new UnsupportedOperationException("not implemented"); } From 499a95c024e68bb5a0f51f6c4d236fef49d80a62 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:17:06 -0800 Subject: [PATCH 20/28] pr comments/fix typo --- .../main/java/com/google/cloud/storage/HttpStorageOptions.java | 2 +- .../src/main/java/com/google/cloud/storage/StorageOptions.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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 77d9aee314..69fe141a67 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 @@ -284,7 +284,7 @@ public HttpStorageOptions build() { * Enable OpenTelemetry Tracing and provide an instance for the client to use. * * @param openTelemetrySdk - * @since 2.34.0 This new api is in preview and is subject to breaking changes. + * @since 2.46.1 This new api is in preview and is subject to breaking changes. */ @BetaApi public HttpStorageOptions.Builder setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java index acb1768b7b..9381181683 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java @@ -111,6 +111,8 @@ public abstract static class Builder public abstract StorageOptions.Builder setBlobWriteSessionConfig( @NonNull BlobWriteSessionConfig blobWriteSessionConfig); + /** @since 2.46.1 This new api is in preview and is subject to breaking changes. */ + @BetaApi public abstract StorageOptions.Builder setOpenTelemetrySdk( @NonNull OpenTelemetrySdk openTelemetrySdk); From 07767466272c70c6b7597085467f448f401c51ed Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:43:02 -0800 Subject: [PATCH 21/28] remove duplicated logic --- .../storage/ITHttpOpenTelemetryTest.java | 219 ------------------ .../cloud/storage/ITOpenTelemetryTest.java | 71 ++---- ...java => ITOpenTelemetryTestbenchTest.java} | 29 +-- 3 files changed, 32 insertions(+), 287 deletions(-) delete mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java rename google-cloud-storage/src/test/java/com/google/cloud/storage/{ITGrpcOpenTelemetryTest.java => ITOpenTelemetryTestbenchTest.java} (92%) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java deleted file mode 100644 index 36f6e0c76c..0000000000 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITHttpOpenTelemetryTest.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.storage; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.cloud.NoCredentials; -import com.google.cloud.ReadChannel; -import com.google.cloud.WriteChannel; -import com.google.cloud.storage.Storage.BlobSourceOption; -import com.google.cloud.storage.Storage.BlobTargetOption; -import com.google.cloud.storage.Storage.CopyRequest; -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.registry.Generator; -import com.google.cloud.storage.it.runner.registry.TestBench; -import com.google.cloud.storage.otel.TestExporter; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.List; -import java.util.UUID; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(StorageITRunner.class) -@SingleBackend(Backend.TEST_BENCH) -public class ITHttpOpenTelemetryTest { - @Inject public TestBench testBench; - private StorageOptions options; - private SpanExporter exporter; - private BlobId blobId; - private Storage storage; - private static final byte[] helloWorldTextBytes = "hello world".getBytes(); - private static final byte[] helloWorldGzipBytes = TestUtils.gzipBytes(helloWorldTextBytes); - @Inject public Generator generator; - @Inject public BucketInfo testBucket; - - @Before - public void setUp() { - exporter = new TestExporter(); - OpenTelemetrySdk openTelemetrySdk = - OpenTelemetrySdk.builder() - .setTracerProvider( - SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(exporter)) - .build()) - .build(); - options = - StorageOptions.http() - .setHost(testBench.getBaseUri()) - .setProjectId("projectId") - .setCredentials(NoCredentials.getInstance()) - .setOpenTelemetrySdk(openTelemetrySdk) - .build(); - storage = options.getService(); - String objectString = generator.randomObjectName(); - blobId = BlobId.of(testBucket.getName(), objectString); - } - - @Test - public void runCreateBucket() { - String bucket = "random-bucket" + UUID.randomUUID(); - storage.create(BucketInfo.of(bucket)); - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - } - - @Test - public void runCreateBlob() { - byte[] content = "Hello, World!".getBytes(UTF_8); - BlobId toCreate = BlobId.of(testBucket.getName(), generator.randomObjectName()); - storage.create(BlobInfo.newBuilder(toCreate).build(), content); - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - } - - @Test - public void runRead() throws IOException { - BlobInfo blobInfo = - BlobInfo.newBuilder(blobId).setContentEncoding("gzip").setContentType("text/plain").build(); - storage.create(blobInfo, helloWorldGzipBytes); - Path helloWorldTxtGz = File.createTempFile(blobId.getName(), ".txt.gz").toPath(); - storage.downloadTo( - blobId, helloWorldTxtGz, Storage.BlobSourceOption.shouldReturnRawInputStream(true)); - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("read"))); - } - - @Test - public void runCopy() { - - byte[] expected = "Hello, World!".getBytes(StandardCharsets.UTF_8); - - BlobInfo info = - BlobInfo.newBuilder(testBucket.getName(), generator.randomObjectName() + "copy/src") - .build(); - Blob cpySrc = storage.create(info, expected, BlobTargetOption.doesNotExist()); - - BlobInfo dst = - BlobInfo.newBuilder(testBucket.getName(), generator.randomObjectName() + "copy/dst") - .build(); - - CopyRequest copyRequest = - CopyRequest.newBuilder() - .setSource(cpySrc.getBlobId()) - .setSourceOptions(BlobSourceOption.generationMatch(cpySrc.getGeneration())) - .setTarget(dst, BlobTargetOption.doesNotExist()) - .build(); - CopyWriter copyWriter = storage.copy(copyRequest); - copyWriter.getResult(); - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("openRewrite"))); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("rewrite"))); - } - - @Test - public void runReadAllBytes() { - BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); - storage.create(blobInfo, helloWorldTextBytes); - byte[] read = storage.readAllBytes(blobId); - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("load"))); - } - - @Test - public void runCreateFromPath() throws IOException { - Path helloWorldTxtGz = File.createTempFile(blobId.getName(), ".txt.gz").toPath(); - storage.createFrom(BlobInfo.newBuilder(blobId).build(), helloWorldTxtGz); - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("createFrom"))); - } - - @Test - public void runCreateFromInputStream() throws IOException { - InputStream inputStream = new ByteArrayInputStream(helloWorldTextBytes); - storage.createFrom(BlobInfo.newBuilder(blobId).build(), inputStream); - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("createFrom"))); - } - - @Test - public void runWriter() throws IOException { - BlobInfo info = BlobInfo.newBuilder(testBucket, generator.randomObjectName()).build(); - try (WriteChannel writer = storage.writer(info)) { - // Do nothing - } - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("writer"))); - } - - @Test - public void runReader() throws IOException { - BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("text/plain").build(); - storage.create(blobInfo, helloWorldTextBytes); - try (ReadChannel reader = storage.reader(blobId)) { - // Do nothing - } - TestExporter testExported = (TestExporter) exporter; - List<SpanData> spanData = testExported.getExportedSpans(); - checkCommonAttributes(spanData); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("reader"))); - } - - private void checkCommonAttributes(List<SpanData> spanData) { - for (SpanData span : spanData) { - Assert.assertEquals("Storage", getAttributeValue(span, "gcp.client.service")); - Assert.assertEquals("googleapis/java-storage", getAttributeValue(span, "gcp.client.repo")); - Assert.assertEquals( - "com.google.cloud:google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); - Assert.assertEquals("http", getAttributeValue(span, "rpc.system")); - } - } - - private String getAttributeValue(SpanData spanData, String key) { - return spanData.getAttributes().get(AttributeKey.stringKey(key)).toString(); - } -} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java index 99bc1386ce..b85e6a72f1 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java @@ -16,6 +16,11 @@ package com.google.cloud.storage; +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.CrossRun; +import com.google.cloud.storage.it.runner.annotations.Inject; import com.google.cloud.storage.otel.TestExporter; import com.google.cloud.storage.testing.RemoteStorageHelper; import io.opentelemetry.api.common.AttributeKey; @@ -27,8 +32,15 @@ import java.util.UUID; import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; +@RunWith(StorageITRunner.class) +@CrossRun( + backends = Backend.PROD, + transports = {Transport.HTTP, Transport.GRPC}) public final class ITOpenTelemetryTest { + @Inject public Storage storage; + @Inject public Transport transport; @Test public void checkInstrumentation() { @@ -41,43 +53,11 @@ public void checkInstrumentation() { .addSpanProcessor(SimpleSpanProcessor.create(exporter)) .build()) .build(); - StorageOptions storageOptions = - StorageOptions.http().setOpenTelemetrySdk(openTelemetrySdk).build(); - Storage storage = storageOptions.getService(); - String bucket = randomBucketName(); - try { - storage.create(BucketInfo.of(bucket)); - TestExporter testExported = (TestExporter) exporter; - SpanData spanData = testExported.getExportedSpans().get(0); - Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); - Assert.assertEquals( - "googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); - Assert.assertEquals( - "com.google.cloud:google-cloud-storage", - getAttributeValue(spanData, "gcp.client.artifact")); - Assert.assertEquals("http", getAttributeValue(spanData, "rpc.system")); - } finally { - // Cleanup - RemoteStorageHelper.forceDelete(storage, bucket); - } - } - @Test - public void checkInstrumentationGrpc() { - SpanExporter exporter = new TestExporter(); - - OpenTelemetrySdk openTelemetrySdk = - OpenTelemetrySdk.builder() - .setTracerProvider( - SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(exporter)) - .build()) - .build(); StorageOptions storageOptions = - StorageOptions.grpc().setOpenTelemetrySdk(openTelemetrySdk).build(); - Storage storage = storageOptions.getService(); + storage.getOptions().toBuilder().setOpenTelemetrySdk(openTelemetrySdk).build(); + storage = storageOptions.getService(); String bucket = randomBucketName(); - storage.create(BucketInfo.of(bucket)); try { storage.create(BucketInfo.of(bucket)); TestExporter testExported = (TestExporter) exporter; @@ -88,7 +68,8 @@ public void checkInstrumentationGrpc() { Assert.assertEquals( "com.google.cloud:google-cloud-storage", getAttributeValue(spanData, "gcp.client.artifact")); - Assert.assertEquals("grpc", getAttributeValue(spanData, "rpc.system")); + Assert.assertEquals( + transport.name().toLowerCase(), getAttributeValue(spanData, "rpc.system")); } finally { // Cleanup RemoteStorageHelper.forceDelete(storage, bucket); @@ -97,25 +78,13 @@ public void checkInstrumentationGrpc() { @Test public void noOpDoesNothing() { - String httpBucket = randomBucketName(); - String grpcBucket = randomBucketName(); - StorageOptions storageOptionsHttp = StorageOptions.http().build(); - Storage storageHttp = storageOptionsHttp.getService(); - StorageOptions storageOptionsGrpc = StorageOptions.grpc().build(); - Storage storageGrpc = storageOptionsGrpc.getService(); + String bucket = randomBucketName(); try { - // NoOp for HTTP - storageHttp.create(BucketInfo.of(httpBucket)); - - // NoOp for Grpc - storageGrpc.create(BucketInfo.of(grpcBucket)); - - Assert.assertNull(storageOptionsGrpc.getOpenTelemetrySdk()); - Assert.assertNull(storageOptionsHttp.getOpenTelemetrySdk()); + storage.create(BucketInfo.of(bucket)); + Assert.assertNull(storage.getOptions().getOpenTelemetrySdk()); } finally { // cleanup - RemoteStorageHelper.forceDelete(storageHttp, httpBucket); - RemoteStorageHelper.forceDelete(storageGrpc, grpcBucket); + RemoteStorageHelper.forceDelete(storage, bucket); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java similarity index 92% rename from google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java rename to google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java index aedbd6549b..340e44eac3 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGrpcOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java @@ -18,18 +18,17 @@ import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.cloud.NoCredentials; import com.google.cloud.ReadChannel; import com.google.cloud.WriteChannel; import com.google.cloud.storage.Storage.BlobSourceOption; import com.google.cloud.storage.Storage.BlobTargetOption; import com.google.cloud.storage.Storage.CopyRequest; +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.CrossRun; 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.registry.Generator; -import com.google.cloud.storage.it.runner.registry.TestBench; import com.google.cloud.storage.otel.TestExporter; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; @@ -49,16 +48,18 @@ import org.junit.runner.RunWith; @RunWith(StorageITRunner.class) -@SingleBackend(Backend.TEST_BENCH) -public class ITGrpcOpenTelemetryTest { - @Inject public TestBench testBench; +@CrossRun( + backends = Backend.TEST_BENCH, + transports = {Transport.HTTP, Transport.GRPC}) +public class ITOpenTelemetryTestbenchTest { + @Inject public Transport transport; + @Inject public Generator generator; + @Inject public BucketInfo testBucket; + @Inject public Storage storage; private StorageOptions options; private SpanExporter exporter; - private Storage storage; private static final byte[] helloWorldTextBytes = "hello world".getBytes(); private BlobId blobId; - @Inject public Generator generator; - @Inject public BucketInfo testBucket; private static final Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir")); @Before @@ -71,13 +72,7 @@ public void setUp() { .addSpanProcessor(SimpleSpanProcessor.create(exporter)) .build()) .build(); - options = - StorageOptions.grpc() - .setHost(testBench.getGRPCBaseUri()) - .setProjectId("projectId") - .setCredentials(NoCredentials.getInstance()) - .setOpenTelemetrySdk(openTelemetrySdk) - .build(); + options = storage.getOptions().toBuilder().setOpenTelemetrySdk(openTelemetrySdk).build(); storage = options.getService(); String objectString = generator.randomObjectName(); blobId = BlobId.of(testBucket.getName(), objectString); @@ -204,7 +199,7 @@ private void checkCommonAttributes(List<SpanData> spanData) { Assert.assertEquals("googleapis/java-storage", getAttributeValue(span, "gcp.client.repo")); Assert.assertEquals( "com.google.cloud:google-cloud-storage", getAttributeValue(span, "gcp.client.artifact")); - Assert.assertEquals("grpc", getAttributeValue(span, "rpc.system")); + Assert.assertEquals(transport.name().toLowerCase(), getAttributeValue(span, "rpc.system")); } } From f623d67d23923decab04c1b0fd2da46267c7f347 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:53:23 -0800 Subject: [PATCH 22/28] add back testbench deleted lines --- .../com/google/cloud/storage/it/runner/registry/TestBench.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java index 3c595337a8..4808584798 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java @@ -230,6 +230,7 @@ public void start() { throw new IllegalStateException( String.format("Timeout while attempting to pull docker image '%s'", dockerImage)); } + int port = URI.create(baseUri).getPort(); int gRPCPort = URI.create(gRPCBaseUri).getPort(); final List<String> command = @@ -309,6 +310,7 @@ public void stop() { } catch (IllegalThreadStateException e) { attemptForceStopContainer = true; } + if (attemptForceStopContainer) { LOGGER.warning("Container did not gracefully exit, attempting to explicitly stop it."); System.out.println("Container did not gracefully exit, attempting to explicitly stop it."); @@ -320,6 +322,7 @@ public void stop() { int shutdownProcessExitValue = shutdownProcess.exitValue(); LOGGER.warning("Container exit value = " + shutdownProcessExitValue); } + // wait for the server to shutdown runWithRetries( () -> { From 3236dea5fd963ac6505bd6ff76bdee04c081e390 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:56:21 -0800 Subject: [PATCH 23/28] testbench formatting --- .../com/google/cloud/storage/it/runner/registry/TestBench.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java index 4808584798..d40a29954d 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java @@ -322,7 +322,7 @@ public void stop() { int shutdownProcessExitValue = shutdownProcess.exitValue(); LOGGER.warning("Container exit value = " + shutdownProcessExitValue); } - + // wait for the server to shutdown runWithRetries( () -> { From 3bb8a5cae570094095673c4fcca62e86e642fa1f Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 26 Dec 2024 10:04:20 -0800 Subject: [PATCH 24/28] fixing tests and adding top level http spans --- .../com/google/cloud/storage/StorageImpl.java | 156 +++++++++++------- .../storage/ITOpenTelemetryTestbenchTest.java | 3 - 2 files changed, 97 insertions(+), 62 deletions(-) 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 70ba16d155..0ed996f074 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 @@ -222,22 +222,32 @@ private Blob internalCreate( final int offset, final int length, Opts<ObjectTargetOpt> opts) { - Preconditions.checkNotNull(content); - final Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + Preconditions.checkNotNull(content); + final Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); - BlobInfo updated = opts.blobInfoMapper().apply(info.toBuilder()).build(); - final StorageObject blobPb = codecs.blobInfo().encode(updated); - ResultRetryAlgorithm<?> algorithm = - retryAlgorithmManager.getForObjectsCreate(blobPb, optionsMap); - return run( - algorithm, - () -> - storageRpc.create( - blobPb, new ByteArrayInputStream(content, offset, length), optionsMap), - (x) -> { - BlobInfo info1 = Conversions.json().blobInfo().decode(x); - return info1.asBlob(this); - }); + BlobInfo updated = opts.blobInfoMapper().apply(info.toBuilder()).build(); + final StorageObject blobPb = codecs.blobInfo().encode(updated); + ResultRetryAlgorithm<?> algorithm = + retryAlgorithmManager.getForObjectsCreate(blobPb, optionsMap); + return run( + algorithm, + () -> + storageRpc.create( + blobPb, new ByteArrayInputStream(content, offset, length), optionsMap), + (x) -> { + BlobInfo info1 = Conversions.json().blobInfo().decode(x); + return info1.asBlob(this); + }); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } @Override @@ -692,30 +702,40 @@ public Blob compose(final ComposeRequest composeRequest) { @Override public CopyWriter copy(final CopyRequest copyRequest) { - BlobId source = copyRequest.getSource(); - BlobInfo target = copyRequest.getTarget(); - Opts<ObjectSourceOpt> sourceOpts = - Opts.unwrap(copyRequest.getSourceOptions()).resolveFrom(source).projectAsSource(); - Opts<ObjectTargetOpt> targetOpts = - Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(target); - - StorageObject sourcePb = codecs.blobId().encode(source); - StorageObject targetPb = codecs.blobInfo().encode(target); - ImmutableMap<StorageRpc.Option, ?> sourceOptions = sourceOpts.getRpcOptions(); - ImmutableMap<StorageRpc.Option, ?> targetOptions = targetOpts.getRpcOptions(); - RewriteRequest rewriteRequest = - new RewriteRequest( - sourcePb, - sourceOptions, - copyRequest.overrideInfo(), - targetPb, - targetOptions, - copyRequest.getMegabytesCopiedPerChunk()); - ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsRewrite(rewriteRequest); - return run( - algorithm, - () -> storageRpc.openRewrite(rewriteRequest), - (r) -> new HttpCopyWriter(getOptions(), r)); + Span otelSpan = openTelemetryTraceUtil.startSpan("copy", MODULE_STORAGE); + try (Scope ignored = otelSpan.makeCurrent()) { + BlobId source = copyRequest.getSource(); + BlobInfo target = copyRequest.getTarget(); + Opts<ObjectSourceOpt> sourceOpts = + Opts.unwrap(copyRequest.getSourceOptions()).resolveFrom(source).projectAsSource(); + Opts<ObjectTargetOpt> targetOpts = + Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(target); + + StorageObject sourcePb = codecs.blobId().encode(source); + StorageObject targetPb = codecs.blobInfo().encode(target); + ImmutableMap<StorageRpc.Option, ?> sourceOptions = sourceOpts.getRpcOptions(); + ImmutableMap<StorageRpc.Option, ?> targetOptions = targetOpts.getRpcOptions(); + RewriteRequest rewriteRequest = + new RewriteRequest( + sourcePb, + sourceOptions, + copyRequest.overrideInfo(), + targetPb, + targetOptions, + copyRequest.getMegabytesCopiedPerChunk()); + ResultRetryAlgorithm<?> algorithm = + retryAlgorithmManager.getForObjectsRewrite(rewriteRequest); + return run( + algorithm, + () -> storageRpc.openRewrite(rewriteRequest), + (r) -> new HttpCopyWriter(getOptions(), r)); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } @Override @@ -725,13 +745,22 @@ public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... optio @Override public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) { - final StorageObject storageObject = codecs.blobId().encode(blob); - Opts<ObjectSourceOpt> unwrap = Opts.unwrap(options); - Opts<ObjectSourceOpt> resolve = unwrap.resolveFrom(blob); - ImmutableMap<StorageRpc.Option, ?> optionsMap = resolve.getRpcOptions(); - ResultRetryAlgorithm<?> algorithm = - retryAlgorithmManager.getForObjectsGet(storageObject, optionsMap); - return run(algorithm, () -> storageRpc.load(storageObject, optionsMap), Function.identity()); + Span otelSpan = openTelemetryTraceUtil.startSpan("readAllBytes", MODULE_STORAGE); + try (Scope ignored = otelSpan.makeCurrent()) { + final StorageObject storageObject = codecs.blobId().encode(blob); + Opts<ObjectSourceOpt> unwrap = Opts.unwrap(options); + Opts<ObjectSourceOpt> resolve = unwrap.resolveFrom(blob); + ImmutableMap<StorageRpc.Option, ?> optionsMap = resolve.getRpcOptions(); + ResultRetryAlgorithm<?> algorithm = + retryAlgorithmManager.getForObjectsGet(storageObject, optionsMap); + return run(algorithm, () -> storageRpc.load(storageObject, optionsMap), Function.identity()); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } @Override @@ -772,19 +801,28 @@ public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) { @Override public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) { - final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream); - final StorageObject pb = codecs.blobId().encode(blob); - ImmutableMap<StorageRpc.Option, ?> optionsMap = - Opts.unwrap(options).resolveFrom(blob).getRpcOptions(); - ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsGet(pb, optionsMap); - run( - algorithm, - callable( - () -> { - storageRpc.read( - pb, optionsMap, countingOutputStream.getCount(), countingOutputStream); - }), - Function.identity()); + Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", MODULE_STORAGE); + try (Scope ignored = otelSpan.makeCurrent()) { + final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream); + final StorageObject pb = codecs.blobId().encode(blob); + ImmutableMap<StorageRpc.Option, ?> optionsMap = + Opts.unwrap(options).resolveFrom(blob).getRpcOptions(); + ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsGet(pb, optionsMap); + run( + algorithm, + callable( + () -> { + storageRpc.read( + pb, optionsMap, countingOutputStream.getCount(), countingOutputStream); + }), + Function.identity()); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } @Override diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java index 340e44eac3..d2dfd52e56 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java @@ -96,8 +96,6 @@ public void runCreateBlob() { List<SpanData> spanData = testExported.getExportedSpans(); checkCommonAttributes(spanData); Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("create"))); - Assert.assertTrue( - spanData.stream().anyMatch(x -> x.getName().contains("internalDirectUpload"))); Assert.assertEquals(spanData.get(1).getSpanContext(), spanData.get(0).getParentSpanContext()); } @@ -120,7 +118,6 @@ public void runCreateFrom() throws IOException { List<SpanData> spanData = testExported.getExportedSpans(); checkCommonAttributes(spanData); Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("createFrom"))); - Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("internalCreateFrom"))); } @Test From 1780daa1958875f72396231c36ef9a8957e480ac Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 26 Dec 2024 10:34:50 -0800 Subject: [PATCH 25/28] add uuid to bucket create test --- .../com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java index d2dfd52e56..63bfe476dd 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java @@ -42,6 +42,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.UUID; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -80,7 +81,7 @@ public void setUp() { @Test public void runCreateBucket() { - String bucket = "random-bucket"; + String bucket = "random-bucket" + UUID.randomUUID(); storage.create(BucketInfo.of(bucket)); TestExporter testExported = (TestExporter) exporter; List<SpanData> spanData = testExported.getExportedSpans(); From e5dfebcf2d39b4f5e6ebf146249129720041b4c9 Mon Sep 17 00:00:00 2001 From: Sydney Munro <97561403+sydney-munro@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:17:33 -0800 Subject: [PATCH 26/28] remove HttpStorageRpc tracing layer --- .../com/google/cloud/storage/StorageImpl.java | 66 ++++++++++++------- .../cloud/storage/spi/v1/HttpStorageRpc.java | 59 +++-------------- .../storage/ITOpenTelemetryTestbenchTest.java | 1 - 3 files changed, 51 insertions(+), 75 deletions(-) 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 0ed996f074..1284067c1e 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 @@ -139,16 +139,26 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage, @Override public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { - final com.google.api.services.storage.model.Bucket bucketPb = - codecs.bucketInfo().encode(bucketInfo); - final Map<StorageRpc.Option, ?> optionsMap = - Opts.unwrap(options).resolveFrom(bucketInfo).getRpcOptions(); - ResultRetryAlgorithm<?> algorithm = - retryAlgorithmManager.getForBucketsCreate(bucketPb, optionsMap); - return run( - algorithm, - () -> storageRpc.create(bucketPb, optionsMap), - (b) -> Conversions.json().bucketInfo().decode(b).asBucket(this)); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + final com.google.api.services.storage.model.Bucket bucketPb = + codecs.bucketInfo().encode(bucketInfo); + final Map<StorageRpc.Option, ?> optionsMap = + Opts.unwrap(options).resolveFrom(bucketInfo).getRpcOptions(); + ResultRetryAlgorithm<?> algorithm = + retryAlgorithmManager.getForBucketsCreate(bucketPb, optionsMap); + return run( + algorithm, + () -> storageRpc.create(bucketPb, optionsMap), + (b) -> Conversions.json().bucketInfo().decode(b).asBucket(this)); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } @Override @@ -201,19 +211,29 @@ public Blob create( @Override @Deprecated public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); - Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); - BlobInfo.Builder builder = blobInfo.toBuilder().setMd5(null).setCrc32c(null); - BlobInfo updated = opts.blobInfoMapper().apply(builder).build(); - StorageObject blobPb = codecs.blobInfo().encode(updated); - InputStream inputStreamParam = - firstNonNull(content, new ByteArrayInputStream(EMPTY_BYTE_ARRAY)); - // retries are not safe when the input is an InputStream, so we can't retry. - BlobInfo info = - Conversions.json() - .blobInfo() - .decode(storageRpc.create(blobPb, inputStreamParam, optionsMap)); - return info.asBlob(this); + OpenTelemetryTraceUtil.Span otelSpan = + openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); + try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); + Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); + BlobInfo.Builder builder = blobInfo.toBuilder().setMd5(null).setCrc32c(null); + BlobInfo updated = opts.blobInfoMapper().apply(builder).build(); + StorageObject blobPb = codecs.blobInfo().encode(updated); + InputStream inputStreamParam = + firstNonNull(content, new ByteArrayInputStream(EMPTY_BYTE_ARRAY)); + // retries are not safe when the input is an InputStream, so we can't retry. + BlobInfo info = + Conversions.json() + .blobInfo() + .decode(storageRpc.create(blobPb, inputStreamParam, optionsMap)); + return info.asBlob(this); + } catch (Exception e) { + otelSpan.recordException(e); + otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); + throw e; + } finally { + otelSpan.end(); + } } private Blob internalCreate( 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 84621063b9..5341051a25 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 @@ -16,7 +16,6 @@ package com.google.cloud.storage.spi.v1; -import static com.google.cloud.storage.otel.OpenTelemetryTraceUtil.MODULE_STORAGE_RPC; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -68,7 +67,6 @@ import com.google.cloud.http.HttpTransportOptions; import com.google.cloud.storage.StorageException; import com.google.cloud.storage.StorageOptions; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -82,7 +80,6 @@ import io.opencensus.trace.Status; import io.opencensus.trace.Tracer; import io.opencensus.trace.Tracing; -import io.opentelemetry.api.trace.StatusCode; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -116,7 +113,6 @@ public class HttpStorageRpc implements StorageRpc { private final Storage storage; private final Tracer tracer = Tracing.getTracer(); private final HttpRequestInitializer batchRequestInitializer; - private final OpenTelemetryTraceUtil openTelemetryTraceUtil; private static final long MEGABYTE = 1024L * 1024L; private static final FileNameMap FILE_NAME_MAP = URLConnection.getFileNameMap(); @@ -148,8 +144,6 @@ public HttpStorageRpc(StorageOptions options, JsonFactory jsonFactory) { .setRootUrl(options.getHost()) .setApplicationName(applicationName) .build(); - // Get instance of OpenTelemetry - openTelemetryTraceUtil = OpenTelemetryTraceUtil.getInstance(options); } public Storage getStorage() { @@ -361,11 +355,9 @@ private Span startSpan(String spanName) { @Override public Bucket create(Bucket bucket, Map<Option, ?> options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_BUCKET); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { return storage .buckets() .insert(this.options.getProjectId(), bucket) @@ -376,12 +368,9 @@ public Bucket create(Bucket bucket, Map<Option, ?> options) { .setEnableObjectRetention(Option.ENABLE_OBJECT_RETENTION.getBoolean(options)) .execute(); } catch (IOException ex) { - otelSpan.recordException(ex); - otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); } finally { - otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -390,11 +379,9 @@ public Bucket create(Bucket bucket, Map<Option, ?> options) { @Override public StorageObject create( StorageObject storageObject, final InputStream content, Map<Option, ?> options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_OBJECT); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { Storage.Objects.Insert insert = storage .objects() @@ -419,12 +406,9 @@ public StorageObject create( .setKmsKeyName(Option.KMS_KEY_NAME.getString(options)) .execute(); } catch (IOException ex) { - otelSpan.recordException(ex); - otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); } finally { - otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -792,11 +776,9 @@ public StorageObject compose( @Override public byte[] load(StorageObject from, Map<Option, ?> options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("load", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_LOAD); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { Storage.Objects.Get getRequest = storage .objects() @@ -815,12 +797,9 @@ public byte[] load(StorageObject from, Map<Option, ?> options) { getRequest.executeMedia().download(out); return out.toByteArray(); } catch (IOException ex) { - otelSpan.recordException(ex); - otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); } finally { - otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -856,11 +835,9 @@ private Get createReadRequest(StorageObject from, Map<Option, ?> options) throws @Override public long read( StorageObject from, Map<Option, ?> options, long position, OutputStream outputStream) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("read", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_READ); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { Get req = createReadRequest(from, options); Boolean shouldReturnRawInputStream = Option.RETURN_RAW_INPUT_STREAM.getBoolean(options); if (shouldReturnRawInputStream != null) { @@ -877,8 +854,6 @@ public long read( req.executeMedia().download(outputStream); return mediaHttpDownloader.getNumBytesDownloaded(); } catch (IOException ex) { - otelSpan.recordException(ex); - otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = translate(ex); if (serviceException.getCode() == SC_REQUESTED_RANGE_NOT_SATISFIABLE) { @@ -886,7 +861,6 @@ public long read( } throw serviceException; } finally { - otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -895,11 +869,9 @@ public long read( @Override public Tuple<String, byte[]> read( StorageObject from, Map<Option, ?> options, long position, int bytes) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("read", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_READ); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { checkArgument(position >= 0, "Position should be non-negative, is " + position); Get req = createReadRequest(from, options); Boolean shouldReturnRawInputStream = Option.RETURN_RAW_INPUT_STREAM.getBoolean(options); @@ -917,8 +889,6 @@ public Tuple<String, byte[]> read( String etag = req.getLastResponseHeaders().getETag(); return Tuple.of(etag, output.toByteArray()); } catch (IOException ex) { - otelSpan.recordException(ex); - otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = StorageException.translate(ex); if (serviceException.getCode() == SC_REQUESTED_RANGE_NOT_SATISFIABLE) { @@ -926,7 +896,6 @@ public Tuple<String, byte[]> read( } throw serviceException; } finally { - otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -1176,14 +1145,11 @@ public String open(String signedURL) { @Override public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("openRewrite", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_OPEN_REWRITE); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { return rewrite(rewriteRequest, null); } finally { - otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } @@ -1191,23 +1157,18 @@ public RewriteResponse openRewrite(RewriteRequest rewriteRequest) { @Override public RewriteResponse continueRewrite(RewriteResponse previousResponse) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("continueRewrite", MODULE_STORAGE_RPC); Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CONTINUE_REWRITE); Scope scope = tracer.withSpan(span); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { return rewrite(previousResponse.rewriteRequest, previousResponse.rewriteToken); } finally { - otelSpan.end(); scope.close(); span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); } } private RewriteResponse rewrite(RewriteRequest req, String token) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("rewrite", MODULE_STORAGE_RPC); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { String userProject = Option.USER_PROJECT.getString(req.sourceOptions); if (userProject == null) { userProject = Option.USER_PROJECT.getString(req.targetOptions); @@ -1258,12 +1219,8 @@ private RewriteResponse rewrite(RewriteRequest req, String token) { rewriteResponse.getRewriteToken(), rewriteResponse.getTotalBytesRewritten().longValue()); } catch (IOException ex) { - otelSpan.recordException(ex); - otelSpan.setStatus(StatusCode.ERROR, ex.getClass().getSimpleName()); tracer.getCurrentSpan().setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); throw translate(ex); - } finally { - otelSpan.end(); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java index 63bfe476dd..66f08c61f3 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java @@ -97,7 +97,6 @@ public void runCreateBlob() { List<SpanData> spanData = testExported.getExportedSpans(); checkCommonAttributes(spanData); Assert.assertTrue(spanData.stream().anyMatch(x -> x.getName().contains("create"))); - Assert.assertEquals(spanData.get(1).getSpanContext(), spanData.get(0).getParentSpanContext()); } @Test From 6faf5ecb195420c13fe5526c5b5719a518d33911 Mon Sep 17 00:00:00 2001 From: BenWhitehead <BenWhitehead@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:51:27 -0500 Subject: [PATCH 27/28] chore: refactor otel tracing of Storage to be a decorator rather than in method modification (#2856) Refactor com.google.cloud.storage.Storage otel tracing to be based on a decorating instance of Storage rather than modifying the implementations of StorageImpl and GrpcStorageImpl directly. This simplifies things by ensuring any otel tracing for c.g.c.s.Storage is implemented for both http and grpc at the same time and with the same metadata. As a side effect, the previous com.google.cloud.storage.otel package has been removed. The number of lines appears daunting, however a high level overview of the actual modifications: 1. Remove otel tracing from StorageImpl 2. Remove otel tracing from GrpcStorageImpl 3. Cleanup StorageOption 1. classes to specify `@BetaApi` on all otel related methods 2. Add `{Http,Grpc}StorageDefaults.getDefaultOpenTelemetry` default which points at `OpenTelemetry.noop()` 3. Relax dependency on OpenTelemetrySdk to only OpenTelemetry 4. Add OtelStorageDecorator (~1700 Lines of the PR) to decorate nearly all public api methods (excluding batch()) * Each method is "logicless" for the most part, and was generated with the help of IntelliJ live templates * Each method is generally: Define span, and scope invoke delegate catch any exception and complete the span * There are sub decorators for ReadChannel, WriteChannel, BlobWriteSession and CopyWriter which carry the originating span along for their lifetimes 5. Remote `com.google.cloud.storage.otel` package and helpers 6. Update assertions in ITOpenTelemetryTest to be wrapped in an `assertAll` --- .../clirr-ignored-differences.xml | 4 +- .../google/cloud/storage/GrpcStorageImpl.java | 442 ++--- .../cloud/storage/GrpcStorageOptions.java | 72 +- .../cloud/storage/HttpStorageOptions.java | 45 +- .../cloud/storage/OtelStorageDecorator.java | 1733 +++++++++++++++++ ...lelCompositeUploadWritableByteChannel.java | 5 +- .../com/google/cloud/storage/StorageImpl.java | 455 ++--- .../google/cloud/storage/StorageOptions.java | 16 +- .../otel/NoOpOpenTelemetryInstance.java | 96 - .../storage/otel/OpenTelemetryInstance.java | 201 -- .../storage/otel/OpenTelemetryTraceUtil.java | 118 -- .../cloud/storage/otel/package-info.java | 26 - .../cloud/storage/ITOpenTelemetryTest.java | 71 +- .../storage/ITOpenTelemetryTestbenchTest.java | 12 +- 14 files changed, 2199 insertions(+), 1097 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java delete mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java delete mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java delete mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java delete mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/otel/package-info.java diff --git a/google-cloud-storage/clirr-ignored-differences.xml b/google-cloud-storage/clirr-ignored-differences.xml index 03e37c174e..0b19230fdd 100644 --- a/google-cloud-storage/clirr-ignored-differences.xml +++ b/google-cloud-storage/clirr-ignored-differences.xml @@ -99,13 +99,13 @@ <difference> <differenceType>7013</differenceType> <className>com/google/cloud/storage/StorageOptions$Builder</className> - <method>com.google.cloud.storage.StorageOptions$Builder setOpenTelemetrySdk(io.opentelemetry.sdk.OpenTelemetrySdk)</method> + <method>com.google.cloud.storage.StorageOptions$Builder setOpenTelemetry(io.opentelemetry.api.OpenTelemetry)</method> </difference> <difference> <differenceType>7013</differenceType> <className>com/google/cloud/storage/StorageOptions</className> - <method>io.opentelemetry.sdk.OpenTelemetrySdk getOpenTelemetrySdk()</method> + <method>io.opentelemetry.api.OpenTelemetry getOpenTelemetry()</method> </difference> 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 41f08713af..d7472f5ff9 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 @@ -26,7 +26,6 @@ import static com.google.cloud.storage.StorageV2ProtoUtils.objectAclEntityOrAltEq; import static com.google.cloud.storage.Utils.bucketNameCodec; import static com.google.cloud.storage.Utils.ifNonNull; -import static com.google.cloud.storage.otel.OpenTelemetryTraceUtil.MODULE_STORAGE; import static com.google.common.base.MoreObjects.firstNonNull; import static java.util.Objects.requireNonNull; @@ -69,9 +68,6 @@ import com.google.cloud.storage.UnifiedOpts.Opts; import com.google.cloud.storage.UnifiedOpts.ProjectId; import com.google.cloud.storage.UnifiedOpts.UserProject; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Scope; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Span; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -171,7 +167,6 @@ final class GrpcStorageImpl extends BaseService<StorageOptions> // workaround for https://github.com/googleapis/java-storage/issues/1736 private final Opts<UserProject> defaultOpts; @Deprecated private final ProjectId defaultProjectId; - private final OpenTelemetryTraceUtil openTelemetryTraceUtil; GrpcStorageImpl( GrpcStorageOptions options, @@ -188,7 +183,6 @@ final class GrpcStorageImpl extends BaseService<StorageOptions> this.retryAlgorithmManager = options.getRetryAlgorithmManager(); this.syntaxDecoders = new SyntaxDecoders(); this.defaultProjectId = UnifiedOpts.projectId(options.getProjectId()); - this.openTelemetryTraceUtil = OpenTelemetryTraceUtil.getInstance(options); } @Override @@ -202,8 +196,6 @@ public void close() throws Exception { @Override public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); Opts<BucketTargetOpt> opts = Opts.unwrap(options).resolveFrom(bucketInfo).prepend(defaultOpts); GrpcCallContext grpcCallContext = opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); @@ -218,20 +210,11 @@ public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { .setParent("projects/_"); CreateBucketRequest req = opts.createBucketsRequest().apply(builder).build(); GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - return Retrying.run( - getOptions(), - retryAlgorithmManager.getFor(req), - () -> storageClient.createBucketCallable().call(req, merge), - syntaxDecoders.bucket); - } catch (Exception ex) { - otelSpan.recordException(ex); - otelSpan.setStatus( - io.opentelemetry.api.trace.StatusCode.ERROR, ex.getClass().getSimpleName()); - throw StorageException.coalesce(ex); - } finally { - otelSpan.end(); - } + return Retrying.run( + getOptions(), + retryAlgorithmManager.getFor(req), + () -> storageClient.createBucketCallable().call(req, merge), + syntaxDecoders.bucket); } @Override @@ -249,26 +232,13 @@ public Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... option public Blob create( BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options) { Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); - // Start the otel span to retain information of the origin of the request - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - return internalDirectUpload(blobInfo, opts, ByteBuffer.wrap(content, offset, length)) - .asBlob(this); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); - } finally { - otelSpan.end(); - } + return internalDirectUpload(blobInfo, opts, ByteBuffer.wrap(content, offset, length)) + .asBlob(this); } @Override public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { + try { requireNonNull(blobInfo, "blobInfo must be non null"); InputStream inputStreamParam = firstNonNull(content, new ByteArrayInputStream(ZERO_BYTES)); @@ -295,11 +265,7 @@ public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... op ApiFuture<WriteObjectResponse> responseApiFuture = session.getResult(); return this.getBlob(responseApiFuture); } catch (IOException | ApiException e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); - } finally { - otelSpan.end(); } } @@ -312,50 +278,39 @@ public Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options) @Override public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOption... options) throws IOException { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); - return internalCreateFrom(path, blobInfo, opts); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); + return internalCreateFrom(path, blobInfo, opts); } @Override public Blob internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOpt> opts) throws IOException { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("internalCreateFrom", MODULE_STORAGE); requireNonNull(path, "path must be non null"); if (Files.isDirectory(path)) { throw new StorageException(0, path + " is a directory"); } - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - GrpcCallContext grpcCallContext = - opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); - WriteObjectRequest req = getWriteObjectRequest(info, opts); - - ClientStreamingCallable<WriteObjectRequest, WriteObjectResponse> write = - storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext); - - ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts); - ApiFuture<GrpcResumableSession> session2 = - ApiFutures.transform( - start, - rw -> - ResumableSession.grpc( - getOptions(), - retryAlgorithmManager.idempotent(), - write, - storageClient.queryWriteStatusCallable(), - rw, - Hasher.noop()), - MoreExecutors.directExecutor()); + + GrpcCallContext grpcCallContext = + opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + WriteObjectRequest req = getWriteObjectRequest(info, opts); + + ClientStreamingCallable<WriteObjectRequest, WriteObjectResponse> write = + storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext); + + ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts); + ApiFuture<GrpcResumableSession> 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(); @@ -366,11 +321,7 @@ public Blob internalCreateFrom(Path path, BlobInfo info, Opts<ObjectTargetOpt> o } return codecs.blobInfo().decode(object).asBlob(this); } catch (InterruptedException | ExecutionException e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); - } finally { - otelSpan.end(); } } @@ -384,46 +335,37 @@ public Blob createFrom(BlobInfo blobInfo, InputStream content, BlobWriteOption.. public Blob createFrom( BlobInfo blobInfo, InputStream in, int bufferSize, BlobWriteOption... options) throws IOException { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - requireNonNull(blobInfo, "blobInfo must be non null"); - - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); - GrpcCallContext grpcCallContext = - opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); - WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); - - ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts); + requireNonNull(blobInfo, "blobInfo must be non null"); - BufferedWritableByteChannelSession<WriteObjectResponse> 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(); + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); + GrpcCallContext grpcCallContext = + opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); + + ApiFuture<ResumableWrite> start = startResumableWrite(grpcCallContext, req, opts); + + BufferedWritableByteChannelSession<WriteObjectResponse> 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(); - // Specifically not in the try-with, so we don't close the provided stream - ReadableByteChannel src = - Channels.newChannel(firstNonNull(in, new ByteArrayInputStream(ZERO_BYTES))); - try (BufferedWritableByteChannel dst = session.open()) { - ByteStreams.copy(src, dst); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus( - io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); - } - return getBlob(session.getResult()); - } finally { - otelSpan.end(); + // Specifically not in the try-with, so we don't close the provided stream + ReadableByteChannel src = + Channels.newChannel(firstNonNull(in, new ByteArrayInputStream(ZERO_BYTES))); + try (BufferedWritableByteChannel dst = session.open()) { + ByteStreams.copy(src, dst); + } catch (Exception e) { + throw StorageException.coalesce(e); } + return getBlob(session.getResult()); } @Override @@ -694,66 +636,56 @@ public Blob compose(ComposeRequest composeRequest) { @Override public CopyWriter copy(CopyRequest copyRequest) { - Span otelSpan = openTelemetryTraceUtil.startSpan("copy", MODULE_STORAGE); - try (Scope ignored = otelSpan.makeCurrent()) { - BlobId src = copyRequest.getSource(); - BlobInfo dst = copyRequest.getTarget(); - Opts<ObjectSourceOpt> srcOpts = - Opts.unwrap(copyRequest.getSourceOptions()) - .projectAsSource() - .resolveFrom(src) - .prepend(defaultOpts); - Opts<ObjectTargetOpt> dstOpts = - Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(dst).prepend(defaultOpts); - - Mapper<RewriteObjectRequest.Builder> mapper = - srcOpts.rewriteObjectsRequest().andThen(dstOpts.rewriteObjectsRequest()); - - Object srcProto = codecs.blobId().encode(src); - Object dstProto = codecs.blobInfo().encode(dst); - - RewriteObjectRequest.Builder b = - RewriteObjectRequest.newBuilder() - .setDestinationName(dstProto.getName()) - .setDestinationBucket(dstProto.getBucket()) - // destination_kms_key comes from dstOpts - // according to the docs in the protos, it is illegal to populate the following - // fields, - // clear them out if they are set - // destination_predefined_acl comes from dstOpts - // if_*_match come from srcOpts and dstOpts - // copy_source_encryption_* come from srcOpts - // common_object_request_params come from dstOpts - .setDestination(dstProto.toBuilder().clearName().clearBucket().clearKmsKey().build()) - .setSourceBucket(srcProto.getBucket()) - .setSourceObject(srcProto.getName()); - - if (src.getGeneration() != null) { - b.setSourceGeneration(src.getGeneration()); - } - - if (copyRequest.getMegabytesCopiedPerChunk() != null) { - b.setMaxBytesRewrittenPerCall(copyRequest.getMegabytesCopiedPerChunk() * _1MiB); - } + BlobId src = copyRequest.getSource(); + BlobInfo dst = copyRequest.getTarget(); + Opts<ObjectSourceOpt> srcOpts = + Opts.unwrap(copyRequest.getSourceOptions()) + .projectAsSource() + .resolveFrom(src) + .prepend(defaultOpts); + Opts<ObjectTargetOpt> dstOpts = + Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(dst).prepend(defaultOpts); + + Mapper<RewriteObjectRequest.Builder> mapper = + srcOpts.rewriteObjectsRequest().andThen(dstOpts.rewriteObjectsRequest()); + + Object srcProto = codecs.blobId().encode(src); + Object dstProto = codecs.blobInfo().encode(dst); + + RewriteObjectRequest.Builder b = + RewriteObjectRequest.newBuilder() + .setDestinationName(dstProto.getName()) + .setDestinationBucket(dstProto.getBucket()) + // destination_kms_key comes from dstOpts + // according to the docs in the protos, it is illegal to populate the following fields, + // clear them out if they are set + // destination_predefined_acl comes from dstOpts + // if_*_match come from srcOpts and dstOpts + // copy_source_encryption_* come from srcOpts + // common_object_request_params come from dstOpts + .setDestination(dstProto.toBuilder().clearName().clearBucket().clearKmsKey().build()) + .setSourceBucket(srcProto.getBucket()) + .setSourceObject(srcProto.getName()); + + if (src.getGeneration() != null) { + b.setSourceGeneration(src.getGeneration()); + } - RewriteObjectRequest req = mapper.apply(b).build(); - GrpcCallContext grpcCallContext = - srcOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); - UnaryCallable<RewriteObjectRequest, RewriteResponse> callable = - storageClient.rewriteObjectCallable().withDefaultCallContext(grpcCallContext); - GrpcCallContext retryContext = Retrying.newCallContext(); - return Retrying.run( - getOptions(), - retryAlgorithmManager.getFor(req), - () -> callable.call(req, retryContext), - (resp) -> new GapicCopyWriter(this, callable, retryAlgorithmManager.idempotent(), resp)); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); - } finally { - otelSpan.end(); + if (copyRequest.getMegabytesCopiedPerChunk() != null) { + b.setMaxBytesRewrittenPerCall(copyRequest.getMegabytesCopiedPerChunk() * _1MiB); } + + RewriteObjectRequest req = mapper.apply(b).build(); + GrpcCallContext grpcCallContext = + srcOpts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + UnaryCallable<RewriteObjectRequest, RewriteResponse> callable = + storageClient.rewriteObjectCallable().withDefaultCallContext(grpcCallContext); + GrpcCallContext retryContext = Retrying.newCallContext(); + return Retrying.run( + getOptions(), + retryAlgorithmManager.getFor(req), + () -> callable.call(req, retryContext), + (resp) -> new GapicCopyWriter(this, callable, retryAlgorithmManager.idempotent(), resp)); } @Override @@ -763,21 +695,14 @@ public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... optio @Override public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("readAllBytes", MODULE_STORAGE); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent(); - UnbufferedReadableByteChannel r = session.open(); + try (UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Channels.newChannel(baos)) { ByteStreams.copy(r, w); } catch (ApiException | IOException e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); - } finally { - otelSpan.end(); } return baos.toByteArray(); } @@ -794,97 +719,66 @@ public GrpcBlobReadChannel reader(String bucket, String blob, BlobSourceOption.. @Override public GrpcBlobReadChannel reader(BlobId blob, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("reader", MODULE_STORAGE); - try (Scope ignore = otelSpan.makeCurrent()) { - Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts); - ReadObjectRequest request = getReadObjectRequest(blob, opts); - GrpcCallContext grpcCallContext = Retrying.newCallContext(); - - return new GrpcBlobReadChannel( - storageClient.readObjectCallable().withDefaultCallContext(grpcCallContext), - getOptions(), - retryAlgorithmManager.getFor(request), - responseContentLifecycleManager, - request, - !opts.autoGzipDecompression()); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); - } finally { - otelSpan.end(); - } + Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob).prepend(defaultOpts); + ReadObjectRequest request = getReadObjectRequest(blob, opts); + GrpcCallContext grpcCallContext = Retrying.newCallContext(); + + return new GrpcBlobReadChannel( + storageClient.readObjectCallable().withDefaultCallContext(grpcCallContext), + getOptions(), + retryAlgorithmManager.getFor(request), + responseContentLifecycleManager, + request, + !opts.autoGzipDecompression()); } @Override public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", MODULE_STORAGE); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); - try (Scope ignored = otelSpan.makeCurrent(); - UnbufferedReadableByteChannel r = session.open(); + try (UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Files.newByteChannel(path, WRITE_OPS)) { ByteStreams.copy(r, w); } catch (ApiException | IOException e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); - } finally { - otelSpan.end(); } } @Override public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", MODULE_STORAGE); UnbufferedReadableByteChannelSession<Object> session = unbufferedReadSession(blob, options); - try (Scope ignored = otelSpan.makeCurrent(); - UnbufferedReadableByteChannel r = session.open(); + try (UnbufferedReadableByteChannel r = session.open(); WritableByteChannel w = Channels.newChannel(outputStream)) { ByteStreams.copy(r, w); } catch (ApiException | IOException e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); throw StorageException.coalesce(e); - } finally { - otelSpan.end(); } } @Override public GrpcBlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("writer", MODULE_STORAGE); - try (Scope ignore = otelSpan.makeCurrent()) { - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); - GrpcCallContext grpcCallContext = - opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); - WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); - Hasher hasher = Hasher.noop(); - // in JSON, the starting of the resumable session happens before the invocation of write can - // happen. Emulate the same thing here. - // 1. create the future - ApiFuture<ResumableWrite> startResumableWrite = - startResumableWrite(grpcCallContext, req, opts); - // 2. await the result of the future - ResumableWrite resumableWrite = ApiFutureUtils.await(startResumableWrite); - // 3. wrap the result in another future container before constructing the BlobWriteChannel - ApiFuture<ResumableWrite> wrapped = ApiFutures.immediateFuture(resumableWrite); - return new GrpcBlobWriteChannel( - storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext), - getOptions(), - retryAlgorithmManager.idempotent(), - () -> wrapped, - hasher); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); - } finally { - otelSpan.end(); - } + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo).prepend(defaultOpts); + GrpcCallContext grpcCallContext = + opts.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); + WriteObjectRequest req = getWriteObjectRequest(blobInfo, opts); + Hasher hasher = Hasher.noop(); + // in JSON, the starting of the resumable session happens before the invocation of write can + // happen. Emulate the same thing here. + // 1. create the future + ApiFuture<ResumableWrite> startResumableWrite = startResumableWrite(grpcCallContext, req, opts); + // 2. await the result of the future + ResumableWrite resumableWrite = ApiFutureUtils.await(startResumableWrite); + // 3. wrap the result in another future container before constructing the BlobWriteChannel + ApiFuture<ResumableWrite> wrapped = ApiFutures.immediateFuture(resumableWrite); + return new GrpcBlobWriteChannel( + storageClient.writeObjectCallable().withDefaultCallContext(grpcCallContext), + getOptions(), + retryAlgorithmManager.idempotent(), + () -> wrapped, + hasher); } @Override @@ -892,8 +786,6 @@ public BlobInfo internalDirectUpload( BlobInfo blobInfo, Opts<ObjectTargetOpt> opts, ByteBuffer buf) { requireNonNull(blobInfo, "blobInfo must be non null"); requireNonNull(buf, "content must be non null"); - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("internalDirectUpload", MODULE_STORAGE); Opts<ObjectTargetOpt> optsWithDefaults = opts.prepend(defaultOpts); GrpcCallContext grpcCallContext = optsWithDefaults.grpcMetadataMapper().apply(GrpcCallContext.createDefault()); @@ -901,36 +793,28 @@ public BlobInfo internalDirectUpload( Hasher hasher = Hasher.enabled(); GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); RewindableContent content = RewindableContent.of(buf); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - return Retrying.run( - getOptions(), - retryAlgorithmManager.getFor(req), - () -> { - content.rewindTo(0); - UnbufferedWritableByteChannelSession<WriteObjectResponse> session = - ResumableMedia.gapic() - .write() - .byteChannel(storageClient.writeObjectCallable().withDefaultCallContext(merge)) - .setByteStringStrategy(ByteStringStrategy.noCopy()) - .setHasher(hasher) - .direct() - .unbuffered() - .setRequest(req) - .build(); - - try (UnbufferedWritableByteChannel c = session.open()) { - content.writeTo(c); - } - return session.getResult(); - }, - this::getBlob); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); - } finally { - otelSpan.end(); - } + return Retrying.run( + getOptions(), + retryAlgorithmManager.getFor(req), + () -> { + content.rewindTo(0); + UnbufferedWritableByteChannelSession<WriteObjectResponse> session = + ResumableMedia.gapic() + .write() + .byteChannel(storageClient.writeObjectCallable().withDefaultCallContext(merge)) + .setByteStringStrategy(ByteStringStrategy.noCopy()) + .setHasher(hasher) + .direct() + .unbuffered() + .setRequest(req) + .build(); + + try (UnbufferedWritableByteChannel c = session.open()) { + content.writeTo(c); + } + return session.getResult(); + }, + this::getBlob); } @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index 0e2a75f922..279418c5f3 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -81,10 +81,11 @@ import io.grpc.MethodDescriptor; import io.grpc.Status; import io.grpc.protobuf.ProtoUtils; -import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.api.OpenTelemetry; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; import java.io.Serializable; import java.net.URI; import java.nio.ByteBuffer; @@ -120,7 +121,7 @@ public final class GrpcStorageOptions extends StorageOptions private final boolean grpcClientMetricsManuallyEnabled; private final GrpcInterceptorProvider grpcInterceptorProvider; private final BlobWriteSessionConfig blobWriteSessionConfig; - private final OpenTelemetrySdk openTelemetrySdk; + private transient OpenTelemetry openTelemetry; private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) { super(builder, serviceDefaults); @@ -137,7 +138,7 @@ private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) this.grpcClientMetricsManuallyEnabled = builder.grpcMetricsManuallyEnabled; this.grpcInterceptorProvider = builder.grpcInterceptorProvider; this.blobWriteSessionConfig = builder.blobWriteSessionConfig; - this.openTelemetrySdk = builder.openTelemetrySdk; + this.openTelemetry = builder.openTelemetry; } @Override @@ -160,6 +161,11 @@ StorageSettings getStorageSettings() throws IOException { return resolveSettingsAndOpts().x(); } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + this.openTelemetry = HttpStorageOptions.getDefaultInstance().getOpenTelemetry(); + } + /** * We have to perform several introspections and detections to cross-wire/support several features * that are either gapic primitives, ServiceOption primitives or GCS semantic requirements. @@ -352,9 +358,11 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw return Tuple.of(builder.build(), defaultOpts); } + /** @since 2.47.0 This new api is in preview and is subject to breaking changes. */ + @BetaApi @Override - public OpenTelemetrySdk getOpenTelemetrySdk() { - return openTelemetrySdk; + public OpenTelemetry getOpenTelemetry() { + return openTelemetry; } /** @since 2.14.0 */ @@ -372,6 +380,7 @@ public int hashCode() { enableGrpcClientMetrics, grpcInterceptorProvider, blobWriteSessionConfig, + openTelemetry, baseHashCode()); } @@ -390,6 +399,7 @@ public boolean equals(Object o) { && Objects.equals(terminationAwaitDuration, that.terminationAwaitDuration) && Objects.equals(grpcInterceptorProvider, that.grpcInterceptorProvider) && Objects.equals(blobWriteSessionConfig, that.blobWriteSessionConfig) + && Objects.equals(openTelemetry, that.openTelemetry) && this.baseEquals(that); } @@ -431,11 +441,10 @@ public static final class Builder extends StorageOptions.Builder { GrpcStorageDefaults.INSTANCE.grpcInterceptorProvider(); private BlobWriteSessionConfig blobWriteSessionConfig = GrpcStorageDefaults.INSTANCE.getDefaultStorageWriterConfig(); + private OpenTelemetry openTelemetry = GrpcStorageDefaults.INSTANCE.getDefaultOpenTelemetry(); private boolean grpcMetricsManuallyEnabled = false; - private OpenTelemetrySdk openTelemetrySdk; - Builder() {} Builder(StorageOptions options) { @@ -447,7 +456,7 @@ public static final class Builder extends StorageOptions.Builder { this.enableGrpcClientMetrics = gso.enableGrpcClientMetrics; this.grpcInterceptorProvider = gso.grpcInterceptorProvider; this.blobWriteSessionConfig = gso.blobWriteSessionConfig; - this.openTelemetrySdk = gso.openTelemetrySdk; + this.openTelemetry = gso.openTelemetry; } /** @@ -633,13 +642,13 @@ public GrpcStorageOptions.Builder setBlobWriteSessionConfig( /** * Enable OpenTelemetry Tracing and provide an instance for the client to use. * - * @param openTelemetrySdk User defined instance of OpenTelemetry SDK to be used by the library - * @since 2.46.1 This new api is in preview and is subject to breaking changes. + * @param openTelemetry User defined instance of OpenTelemetry to be used by the library + * @since 2.47.0 This new api is in preview and is subject to breaking changes. */ @BetaApi - public GrpcStorageOptions.Builder setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { - requireNonNull(openTelemetrySdk, "openTelemetry must be non null"); - this.openTelemetrySdk = openTelemetrySdk; + public GrpcStorageOptions.Builder setOpenTelemetry(OpenTelemetry openTelemetry) { + requireNonNull(openTelemetry, "openTelemetry must be non null"); + this.openTelemetry = openTelemetry; return this; } @@ -719,6 +728,12 @@ public GrpcInterceptorProvider grpcInterceptorProvider() { public BlobWriteSessionConfig getDefaultStorageWriterConfig() { return BlobWriteSessionConfigs.getDefault(); } + + /** @since 2.47.0 This new api is in preview and is subject to breaking changes. */ + @BetaApi + public OpenTelemetry getDefaultOpenTelemetry() { + return OpenTelemetry.noop(); + } } /** @@ -775,22 +790,27 @@ public Storage create(StorageOptions options) { new InternalZeroCopyGrpcStorageStub( stubSettings, clientContext, grpcStorageCallableFactory); StorageClient client = new InternalStorageClient(stub); - return new GrpcStorageImpl( - grpcStorageOptions, - client, - stub.getObjectMediaResponseMarshaller, - grpcStorageOptions.blobWriteSessionConfig.createFactory(Clock.systemUTC()), - defaultOpts); + GrpcStorageImpl grpcStorage = + new GrpcStorageImpl( + grpcStorageOptions, + client, + stub.getObjectMediaResponseMarshaller, + grpcStorageOptions.blobWriteSessionConfig.createFactory(Clock.systemUTC()), + defaultOpts); + return OtelStorageDecorator.decorate( + grpcStorage, options.getOpenTelemetry(), Transport.GRPC); } else { StorageClient client = StorageClient.create(storageSettings); - return new GrpcStorageImpl( - grpcStorageOptions, - client, - ResponseContentLifecycleManager.noop(), - grpcStorageOptions.blobWriteSessionConfig.createFactory(Clock.systemUTC()), - defaultOpts); + GrpcStorageImpl grpcStorage = + new GrpcStorageImpl( + grpcStorageOptions, + client, + ResponseContentLifecycleManager.noop(), + grpcStorageOptions.blobWriteSessionConfig.createFactory(Clock.systemUTC()), + defaultOpts); + return OtelStorageDecorator.decorate( + grpcStorage, options.getOpenTelemetry(), Transport.GRPC); } - } catch (IOException e) { throw new IllegalStateException( "Unable to instantiate gRPC com.google.cloud.storage.Storage client.", e); 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 69fe141a67..e440765fe0 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 @@ -38,7 +38,7 @@ import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; -import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.api.OpenTelemetry; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; @@ -60,7 +60,7 @@ public class HttpStorageOptions extends StorageOptions { private transient RetryDependenciesAdapter retryDepsAdapter; private final BlobWriteSessionConfig blobWriteSessionConfig; - private final OpenTelemetrySdk openTelemetrySdk; + private transient OpenTelemetry openTelemetry; private HttpStorageOptions(Builder builder, StorageDefaults serviceDefaults) { super(builder, serviceDefaults); @@ -70,7 +70,7 @@ private HttpStorageOptions(Builder builder, StorageDefaults serviceDefaults) { builder.storageRetryStrategy, defaults().getStorageRetryStrategy())); retryDepsAdapter = new RetryDependenciesAdapter(); blobWriteSessionConfig = builder.blobWriteSessionConfig; - openTelemetrySdk = builder.openTelemetrySdk; + openTelemetry = builder.openTelemetry; } @Override @@ -88,9 +88,11 @@ StorageRpc getStorageRpcV1() { return (StorageRpc) getRpc(); } + /** @since 2.47.0 This new api is in preview and is subject to breaking changes. */ + @BetaApi @Override - public OpenTelemetrySdk getOpenTelemetrySdk() { - return openTelemetrySdk; + public OpenTelemetry getOpenTelemetry() { + return openTelemetry; } @Override @@ -100,7 +102,8 @@ public HttpStorageOptions.Builder toBuilder() { @Override public int hashCode() { - return Objects.hash(retryAlgorithmManager, blobWriteSessionConfig, baseHashCode()); + return Objects.hash( + retryAlgorithmManager, blobWriteSessionConfig, openTelemetry, baseHashCode()); } @Override @@ -114,12 +117,14 @@ public boolean equals(Object o) { HttpStorageOptions that = (HttpStorageOptions) o; return Objects.equals(retryAlgorithmManager, that.retryAlgorithmManager) && Objects.equals(blobWriteSessionConfig, that.blobWriteSessionConfig) + && Objects.equals(openTelemetry, that.openTelemetry) && this.baseEquals(that); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.retryDepsAdapter = new RetryDependenciesAdapter(); + this.openTelemetry = HttpStorageOptions.getDefaultInstance().getOpenTelemetry(); } public static HttpStorageOptions.Builder newBuilder() { @@ -144,7 +149,7 @@ public static class Builder extends StorageOptions.Builder { private StorageRetryStrategy storageRetryStrategy; private BlobWriteSessionConfig blobWriteSessionConfig = HttpStorageDefaults.INSTANCE.getDefaultStorageWriterConfig(); - private OpenTelemetrySdk openTelemetrySdk; + private OpenTelemetry openTelemetry = HttpStorageDefaults.INSTANCE.getDefaultOpenTelemetry(); Builder() {} @@ -153,7 +158,7 @@ public static class Builder extends StorageOptions.Builder { HttpStorageOptions hso = (HttpStorageOptions) options; this.storageRetryStrategy = hso.retryAlgorithmManager.retryStrategy; this.blobWriteSessionConfig = hso.blobWriteSessionConfig; - this.openTelemetrySdk = hso.getOpenTelemetrySdk(); + this.openTelemetry = hso.getOpenTelemetry(); } @Override @@ -283,13 +288,13 @@ public HttpStorageOptions build() { /** * Enable OpenTelemetry Tracing and provide an instance for the client to use. * - * @param openTelemetrySdk - * @since 2.46.1 This new api is in preview and is subject to breaking changes. + * @param openTelemetry User defined instance of OpenTelemetry to be used by the library + * @since 2.47.0 This new api is in preview and is subject to breaking changes. */ @BetaApi - public HttpStorageOptions.Builder setOpenTelemetrySdk(OpenTelemetrySdk openTelemetrySdk) { - requireNonNull(openTelemetrySdk, "openTelemetry must be non null"); - this.openTelemetrySdk = openTelemetrySdk; + public HttpStorageOptions.Builder setOpenTelemetry(OpenTelemetry openTelemetry) { + requireNonNull(openTelemetry, "openTelemetry must be non null"); + this.openTelemetry = openTelemetry; return this; } } @@ -325,6 +330,12 @@ public StorageRetryStrategy getStorageRetryStrategy() { public BlobWriteSessionConfig getDefaultStorageWriterConfig() { return BlobWriteSessionConfigs.getDefault(); } + + /** @since 2.47.0 This new api is in preview and is subject to breaking changes. */ + @BetaApi + public OpenTelemetry getDefaultOpenTelemetry() { + return OpenTelemetry.noop(); + } } /** @@ -365,8 +376,12 @@ public Storage create(StorageOptions options) { HttpStorageOptions httpStorageOptions = (HttpStorageOptions) options; Clock clock = Clock.systemUTC(); try { - return new StorageImpl( - httpStorageOptions, httpStorageOptions.blobWriteSessionConfig.createFactory(clock)); + StorageImpl storage = + new StorageImpl( + httpStorageOptions, + httpStorageOptions.blobWriteSessionConfig.createFactory(clock)); + return OtelStorageDecorator.decorate( + storage, httpStorageOptions.getOpenTelemetry(), Transport.HTTP); } catch (IOException e) { throw new IllegalStateException( "Unable to instantiate HTTP com.google.cloud.storage.Storage client.", e); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java new file mode 100644 index 0000000000..a1813a98c5 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/OtelStorageDecorator.java @@ -0,0 +1,1733 @@ +/* + * Copyright 2025 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.BetaApi; +import com.google.api.gax.paging.Page; +import com.google.cloud.Policy; +import com.google.cloud.ReadChannel; +import com.google.cloud.RestorableState; +import com.google.cloud.WriteChannel; +import com.google.cloud.storage.Acl.Entity; +import com.google.cloud.storage.HmacKey.HmacKeyMetadata; +import com.google.cloud.storage.HmacKey.HmacKeyState; +import com.google.cloud.storage.PostPolicyV4.PostConditionsV4; +import com.google.cloud.storage.PostPolicyV4.PostFieldsV4; +import com.google.cloud.storage.TransportCompatibility.Transport; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +@SuppressWarnings("DuplicatedCode") +final class OtelStorageDecorator implements Storage { + + /** Becomes the {@code otel.scope.name} attribute in a span */ + private static final String OTEL_SCOPE_NAME = "cloud.google.com/java/storage"; + + private final Storage delegate; + private final OpenTelemetry otel; + private final Attributes baseAttributes; + private final Tracer tracer; + + private OtelStorageDecorator(Storage delegate, OpenTelemetry otel, Attributes baseAttributes) { + this.delegate = delegate; + this.otel = otel; + this.baseAttributes = baseAttributes; + this.tracer = + TracerDecorator.decorate(null, otel, baseAttributes, Storage.class.getName() + "/"); + } + + @Override + public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { + Span span = + tracer + .spanBuilder("create") + .setAttribute("gsutil.uri", fmtBucket(bucketInfo.getName())) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.create(bucketInfo, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob create(BlobInfo blobInfo, BlobTargetOption... options) { + Span span = + tracer + .spanBuilder("create") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.create(blobInfo, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options) { + Span span = + tracer + .spanBuilder("create") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.create(blobInfo, content, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob create( + BlobInfo blobInfo, byte[] content, int offset, int length, BlobTargetOption... options) { + Span span = + tracer + .spanBuilder("create") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.create(blobInfo, content, offset, length, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + @Deprecated + public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { + Span span = + tracer + .spanBuilder("create") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.create(blobInfo, content, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options) + throws IOException { + Span span = + tracer + .spanBuilder("createFrom") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createFrom(blobInfo, path, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOption... options) + throws IOException { + Span span = + tracer + .spanBuilder("createFrom") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createFrom(blobInfo, path, bufferSize, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob createFrom(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) + throws IOException { + Span span = + tracer + .spanBuilder("createFrom") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createFrom(blobInfo, content, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob createFrom( + BlobInfo blobInfo, InputStream content, int bufferSize, BlobWriteOption... options) + throws IOException { + Span span = + tracer + .spanBuilder("createFrom") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createFrom(blobInfo, content, bufferSize, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Bucket get(String bucket, BucketGetOption... options) { + Span span = tracer.spanBuilder("get").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.get(bucket, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Bucket lockRetentionPolicy(BucketInfo bucket, BucketTargetOption... options) { + Span span = + tracer + .spanBuilder("lockRetentionPolicy") + .setAttribute("gsutil.uri", fmtBucket(bucket.getName())) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.lockRetentionPolicy(bucket, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob get(String bucket, String blob, BlobGetOption... options) { + Span span = + tracer + .spanBuilder("get") + .setAttribute("gsutil.uri", String.format("gs://%s/%s", bucket, blob)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.get(bucket, blob, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob get(BlobId blob, BlobGetOption... options) { + Span span = + tracer + .spanBuilder("get") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.get(blob, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob get(BlobId blob) { + Span span = + tracer + .spanBuilder("get") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.get(blob); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob restore(BlobId blob, BlobRestoreOption... options) { + Span span = + tracer + .spanBuilder("restore") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.restore(blob, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Page<Bucket> list(BucketListOption... options) { + Span span = tracer.spanBuilder("list").setAttribute("gsutil.uri", "gs://").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.list(options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Page<Blob> list(String bucket, BlobListOption... options) { + Span span = + tracer.spanBuilder("list").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.list(bucket, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Bucket update(BucketInfo bucketInfo, BucketTargetOption... options) { + Span span = + tracer + .spanBuilder("update") + .setAttribute("gsutil.uri", fmtBucket(bucketInfo.getName())) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.update(bucketInfo, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob update(BlobInfo blobInfo, BlobTargetOption... options) { + Span span = + tracer + .spanBuilder("update") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.update(blobInfo, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob update(BlobInfo blobInfo) { + Span span = + tracer + .spanBuilder("update") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.update(blobInfo); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean delete(String bucket, BucketSourceOption... options) { + Span span = + tracer.spanBuilder("delete").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.delete(bucket, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean delete(String bucket, String blob, BlobSourceOption... options) { + Span span = + tracer.spanBuilder("delete").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.delete(bucket, blob, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean delete(BlobId blob, BlobSourceOption... options) { + Span span = + tracer + .spanBuilder("delete") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.delete(blob, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean delete(BlobId blob) { + Span span = + tracer + .spanBuilder("delete") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.delete(blob); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Blob compose(ComposeRequest composeRequest) { + Span span = + tracer + .spanBuilder("compose") + .setAttribute("gsutil.uri", composeRequest.getTarget().getBlobId().toGsUtilUri()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.compose(composeRequest); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public CopyWriter copy(CopyRequest copyRequest) { + Span span = + tracer + .spanBuilder("copy") + .setAttribute("gsutil.uri", copyRequest.getTarget().getBlobId().toGsUtilUri()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + CopyWriter copyWriter = delegate.copy(copyRequest); + return new OtelDecoratedCopyWriter(copyWriter, span); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + span.end(); + throw t; + } + } + + @Override + public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... options) { + Span span = + tracer + .spanBuilder("readAllBytes") + .setAttribute("gsutil.uri", BlobId.of(bucket, blob).toGsUtilUri()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.readAllBytes(bucket, blob, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) { + Span span = + tracer + .spanBuilder("readAllBytes") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.readAllBytes(blob, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public StorageBatch batch() { + return delegate.batch(); + } + + @Override + public ReadChannel reader(String bucket, String blob, BlobSourceOption... options) { + Span span = + tracer + .spanBuilder("reader") + .setAttribute("gsutil.uri", BlobId.of(bucket, blob).toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + ReadChannel reader = delegate.reader(bucket, blob, options); + return new OtelDecoratedReadChannel(reader, span); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + span.end(); + throw t; + } + } + + @Override + public ReadChannel reader(BlobId blob, BlobSourceOption... options) { + Span span = + tracer + .spanBuilder("reader") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + ReadChannel reader = delegate.reader(blob, options); + return new OtelDecoratedReadChannel(reader, span); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + span.end(); + throw t; + } + } + + @Override + public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) { + Span span = + tracer + .spanBuilder("downloadTo") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + delegate.downloadTo(blob, path, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) { + Span span = + tracer + .spanBuilder("downloadTo") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + delegate.downloadTo(blob, outputStream, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public WriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { + Span sessionSpan = + tracer + .spanBuilder("writer") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = sessionSpan.makeCurrent()) { + WriteChannel writer = delegate.writer(blobInfo, options); + return new OtelDecoratedWriteChannel(writer, sessionSpan); + } catch (Throwable t) { + sessionSpan.recordException(t); + sessionSpan.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + sessionSpan.end(); + throw t; + } + } + + @Override + public WriteChannel writer(URL signedURL) { + Span sessionSpan = tracer.spanBuilder("writer").startSpan(); + try (Scope ignore = sessionSpan.makeCurrent()) { + WriteChannel writer = delegate.writer(signedURL); + return new OtelDecoratedWriteChannel(writer, sessionSpan); + } catch (Throwable t) { + sessionSpan.recordException(t); + sessionSpan.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + sessionSpan.end(); + throw t; + } + } + + @Override + public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options) { + Span span = + tracer + .spanBuilder("signUrl") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.signUrl(blobInfo, duration, unit, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostFieldsV4 fields, + PostConditionsV4 conditions, + PostPolicyV4Option... options) { + Span span = + tracer + .spanBuilder("generateSignedPostPolicyV4") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.generateSignedPostPolicyV4( + blobInfo, duration, unit, fields, conditions, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostFieldsV4 fields, + PostPolicyV4Option... options) { + Span span = + tracer + .spanBuilder("generateSignedPostPolicyV4") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.generateSignedPostPolicyV4(blobInfo, duration, unit, fields, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, + long duration, + TimeUnit unit, + PostConditionsV4 conditions, + PostPolicyV4Option... options) { + Span span = + tracer + .spanBuilder("generateSignedPostPolicyV4") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.generateSignedPostPolicyV4(blobInfo, duration, unit, conditions, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public PostPolicyV4 generateSignedPostPolicyV4( + BlobInfo blobInfo, long duration, TimeUnit unit, PostPolicyV4Option... options) { + Span span = + tracer + .spanBuilder("generateSignedPostPolicyV4") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.generateSignedPostPolicyV4(blobInfo, duration, unit, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Blob> get(BlobId... blobIds) { + Span span = tracer.spanBuilder("get").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.get(blobIds); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Blob> get(Iterable<BlobId> blobIds) { + Span span = tracer.spanBuilder("get").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.get(blobIds); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Blob> update(BlobInfo... blobInfos) { + Span span = tracer.spanBuilder("update").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.update(blobInfos); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Blob> update(Iterable<BlobInfo> blobInfos) { + Span span = tracer.spanBuilder("update").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.update(blobInfos); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Boolean> delete(BlobId... blobIds) { + Span span = tracer.spanBuilder("delete").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.delete(blobIds); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Boolean> delete(Iterable<BlobId> blobIds) { + Span span = tracer.spanBuilder("delete").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.delete(blobIds); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl getAcl(String bucket, Entity entity, BucketSourceOption... options) { + Span span = + tracer.spanBuilder("getAcl").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.getAcl(bucket, entity, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl getAcl(String bucket, Entity entity) { + Span span = + tracer.spanBuilder("getAcl").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.getAcl(bucket, entity); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean deleteAcl(String bucket, Entity entity, BucketSourceOption... options) { + Span span = + tracer.spanBuilder("deleteAcl").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.deleteAcl(bucket, entity, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean deleteAcl(String bucket, Entity entity) { + Span span = + tracer.spanBuilder("deleteAcl").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.deleteAcl(bucket, entity); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl createAcl(String bucket, Acl acl, BucketSourceOption... options) { + Span span = + tracer.spanBuilder("createAcl").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createAcl(bucket, acl, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl createAcl(String bucket, Acl acl) { + Span span = + tracer.spanBuilder("createAcl").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createAcl(bucket, acl); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl updateAcl(String bucket, Acl acl, BucketSourceOption... options) { + Span span = + tracer.spanBuilder("updateAcl").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.updateAcl(bucket, acl, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl updateAcl(String bucket, Acl acl) { + Span span = + tracer.spanBuilder("updateAcl").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.updateAcl(bucket, acl); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Acl> listAcls(String bucket, BucketSourceOption... options) { + Span span = + tracer.spanBuilder("listAcls").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.listAcls(bucket, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Acl> listAcls(String bucket) { + Span span = + tracer.spanBuilder("listAcls").setAttribute("gsutil.uri", fmtBucket(bucket)).startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.listAcls(bucket); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl getDefaultAcl(String bucket, Entity entity) { + Span span = + tracer + .spanBuilder("getDefaultAcl") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.getDefaultAcl(bucket, entity); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean deleteDefaultAcl(String bucket, Entity entity) { + Span span = + tracer + .spanBuilder("deleteDefaultAcl") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.deleteDefaultAcl(bucket, entity); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl createDefaultAcl(String bucket, Acl acl) { + Span span = + tracer + .spanBuilder("createDefaultAcl") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createDefaultAcl(bucket, acl); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl updateDefaultAcl(String bucket, Acl acl) { + Span span = + tracer + .spanBuilder("updateDefaultAcl") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.updateDefaultAcl(bucket, acl); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Acl> listDefaultAcls(String bucket) { + Span span = + tracer + .spanBuilder("listDefaultAcls") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.listDefaultAcls(bucket); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl getAcl(BlobId blob, Entity entity) { + Span span = + tracer + .spanBuilder("getAcl") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.getAcl(blob, entity); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean deleteAcl(BlobId blob, Entity entity) { + Span span = + tracer + .spanBuilder("deleteAcl") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.deleteAcl(blob, entity); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl createAcl(BlobId blob, Acl acl) { + Span span = + tracer + .spanBuilder("createAcl") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createAcl(blob, acl); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Acl updateAcl(BlobId blob, Acl acl) { + Span span = + tracer + .spanBuilder("updateAcl") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.updateAcl(blob, acl); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Acl> listAcls(BlobId blob) { + Span span = + tracer + .spanBuilder("listAcls") + .setAttribute("gsutil.uri", blob.toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.listAcls(blob); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public HmacKey createHmacKey(ServiceAccount serviceAccount, CreateHmacKeyOption... options) { + Span span = tracer.spanBuilder("createHmacKey").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createHmacKey(serviceAccount, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Page<HmacKeyMetadata> listHmacKeys(ListHmacKeysOption... options) { + Span span = tracer.spanBuilder("listHmacKeys").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.listHmacKeys(options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public HmacKeyMetadata getHmacKey(String accessId, GetHmacKeyOption... options) { + Span span = tracer.spanBuilder("getHmacKey").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.getHmacKey(accessId, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public void deleteHmacKey(HmacKeyMetadata hmacKeyMetadata, DeleteHmacKeyOption... options) { + Span span = tracer.spanBuilder("deleteHmacKey").startSpan(); + try (Scope ignore = span.makeCurrent()) { + delegate.deleteHmacKey(hmacKeyMetadata, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public HmacKeyMetadata updateHmacKeyState( + HmacKeyMetadata hmacKeyMetadata, HmacKeyState state, UpdateHmacKeyOption... options) { + Span span = tracer.spanBuilder("updateHmacKeyState").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.updateHmacKeyState(hmacKeyMetadata, state, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Policy getIamPolicy(String bucket, BucketSourceOption... options) { + Span span = + tracer + .spanBuilder("getIamPolicy") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.getIamPolicy(bucket, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Policy setIamPolicy(String bucket, Policy policy, BucketSourceOption... options) { + Span span = + tracer + .spanBuilder("setIamPolicy") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.setIamPolicy(bucket, policy, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Boolean> testIamPermissions( + String bucket, List<String> permissions, BucketSourceOption... options) { + Span span = + tracer + .spanBuilder("testIamPermissions") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.testIamPermissions(bucket, permissions, options); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public ServiceAccount getServiceAccount(String projectId) { + Span span = tracer.spanBuilder("getServiceAccount").startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.getServiceAccount(projectId); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Notification createNotification(String bucket, NotificationInfo notificationInfo) { + Span span = + tracer + .spanBuilder("createNotification") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.createNotification(bucket, notificationInfo); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public Notification getNotification(String bucket, String notificationId) { + Span span = + tracer + .spanBuilder("getNotification") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.getNotification(bucket, notificationId); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public List<Notification> listNotifications(String bucket) { + Span span = + tracer + .spanBuilder("listNotifications") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.listNotifications(bucket); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public boolean deleteNotification(String bucket, String notificationId) { + Span span = + tracer + .spanBuilder("deleteNotification") + .setAttribute("gsutil.uri", fmtBucket(bucket)) + .startSpan(); + try (Scope ignore = span.makeCurrent()) { + return delegate.deleteNotification(bucket, notificationId); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public void close() throws Exception { + delegate.close(); + } + + @Override + @BetaApi + public BlobWriteSession blobWriteSession(BlobInfo blobInfo, BlobWriteOption... options) { + Span sessionSpan = + tracer + .spanBuilder("blobWriteSession") + .setAttribute("gsutil.uri", blobInfo.getBlobId().toGsUtilUriWithGeneration()) + .startSpan(); + try (Scope ignore = sessionSpan.makeCurrent()) { + BlobWriteSession session = delegate.blobWriteSession(blobInfo, options); + return new OtelDecoratedBlobWriteSession(session); + } catch (Throwable t) { + sessionSpan.recordException(t); + sessionSpan.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + sessionSpan.end(); + } + } + + @Override + public StorageOptions getOptions() { + return delegate.getOptions(); + } + + static Storage decorate(Storage delegate, OpenTelemetry otel, Transport transport) { + requireNonNull(delegate, "delegate must be non null"); + requireNonNull(otel, "otel must be non null"); + if (otel == OpenTelemetry.noop()) { + return delegate; + } + Attributes baseAttributes = + Attributes.builder() + .put("gcp.client.service", "Storage") + .put("gcp.client.version", StorageOptions.getDefaultInstance().getLibraryVersion()) + .put("gcp.client.repo", "googleapis/java-storage") + .put("gcp.client.artifact", "com.google.cloud:google-cloud-storage") + .put("rpc.system", transport.toString().toLowerCase(Locale.ROOT)) + .put("service.name", "storage.googleapis.com") + .build(); + return new OtelStorageDecorator(delegate, otel, baseAttributes); + } + + private static @NonNull String fmtBucket(String bucket) { + return String.format("gs://%s/", bucket); + } + + private static final class TracerDecorator implements Tracer { + @Nullable private final Context parentContextOverride; + private final Tracer delegate; + private final Attributes baseAttributes; + private final String spanNamePrefix; + + private TracerDecorator( + @Nullable Context parentContextOverride, + Tracer delegate, + Attributes baseAttributes, + String spanNamePrefix) { + this.parentContextOverride = parentContextOverride; + this.delegate = delegate; + this.baseAttributes = baseAttributes; + this.spanNamePrefix = spanNamePrefix; + } + + private static TracerDecorator decorate( + @Nullable Context parentContextOverride, + OpenTelemetry otel, + Attributes baseAttributes, + String spanNamePrefix) { + requireNonNull(otel, "otel must be non null"); + requireNonNull(baseAttributes, "baseAttributes must be non null"); + requireNonNull(spanNamePrefix, "spanNamePrefix must be non null"); + Tracer tracer = + otel.getTracer(OTEL_SCOPE_NAME, StorageOptions.getDefaultInstance().getLibraryVersion()); + return new TracerDecorator(parentContextOverride, tracer, baseAttributes, spanNamePrefix); + } + + @Override + public SpanBuilder spanBuilder(String spanName) { + SpanBuilder spanBuilder = + delegate.spanBuilder(spanNamePrefix + spanName).setAllAttributes(baseAttributes); + if (parentContextOverride != null) { + spanBuilder.setParent(parentContextOverride); + } + return spanBuilder; + } + } + + private static final class OtelDecoratedReadChannel implements ReadChannel { + + private final ReadChannel reader; + private final Span span; + + private OtelDecoratedReadChannel(ReadChannel reader, Span span) { + this.reader = reader; + this.span = span; + } + + @Override + public void seek(long position) throws IOException { + reader.seek(position); + } + + @Override + public void setChunkSize(int chunkSize) { + reader.setChunkSize(chunkSize); + } + + @Override + public RestorableState<ReadChannel> capture() { + return reader.capture(); + } + + @Override + public ReadChannel limit(long limit) { + return reader.limit(limit); + } + + @Override + public long limit() { + return reader.limit(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return reader.read(dst); + } + + @Override + public boolean isOpen() { + return reader.isOpen(); + } + + @Override + public void close() { + try { + reader.close(); + } finally { + span.end(); + } + } + } + + private final class OtelDecoratedBlobWriteSession implements BlobWriteSession { + + private final BlobWriteSession delegate; + private final Tracer tracer; + + public OtelDecoratedBlobWriteSession(BlobWriteSession delegate) { + this.delegate = delegate; + this.tracer = + TracerDecorator.decorate( + Context.current(), + otel, + OtelStorageDecorator.this.baseAttributes, + BlobWriteSession.class.getName() + "/"); + } + + @Override + public WritableByteChannel open() throws IOException { + Span openSpan = tracer.spanBuilder("open").startSpan(); + try (Scope ignore = openSpan.makeCurrent()) { + WritableByteChannel delegate = this.delegate.open(); + return new OtelDecoratingWritableByteChannel(delegate, openSpan); + } catch (Throwable t) { + openSpan.recordException(t); + openSpan.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } + } + + @Override + public ApiFuture<BlobInfo> getResult() { + return delegate.getResult(); + } + + private class OtelDecoratingWritableByteChannel implements WritableByteChannel { + + private final WritableByteChannel delegate; + private final Span openSpan; + + private OtelDecoratingWritableByteChannel(WritableByteChannel delegate, Span openSpan) { + this.delegate = delegate; + this.openSpan = openSpan; + } + + @Override + public int write(ByteBuffer src) throws IOException { + return delegate.write(src); + } + + @Override + public boolean isOpen() { + return delegate.isOpen(); + } + + @Override + public void close() throws IOException { + try { + delegate.close(); + } finally { + openSpan.end(); + } + } + } + } + + private static final class OtelDecoratedWriteChannel implements WriteChannel { + private final WriteChannel delegate; + private final Span openSpan; + + private OtelDecoratedWriteChannel(WriteChannel delegate, Span openSpan) { + this.delegate = delegate; + this.openSpan = openSpan; + } + + @Override + public void setChunkSize(int chunkSize) { + delegate.setChunkSize(chunkSize); + } + + @Override + public RestorableState<WriteChannel> capture() { + return delegate.capture(); + } + + @Override + public int write(ByteBuffer src) throws IOException { + return delegate.write(src); + } + + @Override + public boolean isOpen() { + return delegate.isOpen(); + } + + @Override + public void close() throws IOException { + try { + delegate.close(); + } finally { + openSpan.end(); + } + } + } + + private final class OtelDecoratedCopyWriter extends CopyWriter { + + private final CopyWriter copyWriter; + private final Span span; + private final Context parentContext; + private final Tracer tracer; + + public OtelDecoratedCopyWriter(CopyWriter copyWriter, Span span) { + this.copyWriter = copyWriter; + this.span = span; + this.parentContext = Context.current(); + this.tracer = + TracerDecorator.decorate( + Context.current(), + otel, + OtelStorageDecorator.this.baseAttributes, + CopyWriter.class.getName() + "/"); + } + + @Override + public Blob getResult() { + try { + return copyWriter.getResult(); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + + @Override + public long getBlobSize() { + return copyWriter.getBlobSize(); + } + + @Override + public boolean isDone() { + boolean done = copyWriter.isDone(); + if (done) { + span.end(); + } + return done; + } + + @Override + public long getTotalBytesCopied() { + return copyWriter.getTotalBytesCopied(); + } + + @Override + public RestorableState<CopyWriter> capture() { + return copyWriter.capture(); + } + + @Override + public void copyChunk() { + Span copyChunkSpan = tracer.spanBuilder("copyChunk").setParent(parentContext).startSpan(); + try (Scope ignore = copyChunkSpan.makeCurrent()) { + copyWriter.copyChunk(); + } catch (Throwable t) { + copyChunkSpan.recordException(t); + copyChunkSpan.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + span.end(); + throw t; + } finally { + copyChunkSpan.end(); + } + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java index db807ce6ce..b824acf4d5 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java @@ -54,6 +54,7 @@ import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import io.grpc.Status.Code; +import io.opentelemetry.context.Context; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousCloseException; @@ -145,7 +146,7 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab BlobInfo ultimateObject, Opts<ObjectTargetOpt> opts) { this.bufferPool = bufferPool; - this.exec = exec; + this.exec = Context.current().wrap(exec); this.partNamingStrategy = partNamingStrategy; this.partCleanupStrategy = partCleanupStrategy; this.maxElementsPerCompact = maxElementsPerCompact; @@ -154,7 +155,7 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab this.storage = storage; this.ultimateObject = ultimateObject; this.opts = opts; - this.queue = AsyncAppendingQueue.of(exec, maxElementsPerCompact, this::compose); + this.queue = AsyncAppendingQueue.of(this.exec, maxElementsPerCompact, this::compose); this.pendingParts = new ArrayList<>(); // this can be modified by another thread this.successfulParts = Collections.synchronizedList(new ArrayList<>()); 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 1284067c1e..ee538bcde8 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 @@ -17,7 +17,6 @@ package com.google.cloud.storage; import static com.google.cloud.storage.SignedUrlEncodingHelper.Rfc3986UriEncode; -import static com.google.cloud.storage.otel.OpenTelemetryTraceUtil.MODULE_STORAGE; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -51,9 +50,6 @@ import com.google.cloud.storage.UnifiedOpts.ObjectSourceOpt; import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt; import com.google.cloud.storage.UnifiedOpts.Opts; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Scope; -import com.google.cloud.storage.otel.OpenTelemetryTraceUtil.Span; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.cloud.storage.spi.v1.StorageRpc.RewriteRequest; import com.google.common.base.CharMatcher; @@ -69,7 +65,6 @@ import com.google.common.io.BaseEncoding; import com.google.common.io.CountingOutputStream; import com.google.common.primitives.Ints; -import io.opentelemetry.api.trace.StatusCode; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -127,38 +122,26 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage, final HttpRetryAlgorithmManager retryAlgorithmManager; final StorageRpc storageRpc; final WriterFactory writerFactory; - private final OpenTelemetryTraceUtil openTelemetryTraceUtil; StorageImpl(HttpStorageOptions options, WriterFactory writerFactory) { super(options); this.retryAlgorithmManager = options.getRetryAlgorithmManager(); this.storageRpc = options.getStorageRpcV1(); this.writerFactory = writerFactory; - this.openTelemetryTraceUtil = OpenTelemetryTraceUtil.getInstance(options); } @Override public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - final com.google.api.services.storage.model.Bucket bucketPb = - codecs.bucketInfo().encode(bucketInfo); - final Map<StorageRpc.Option, ?> optionsMap = - Opts.unwrap(options).resolveFrom(bucketInfo).getRpcOptions(); - ResultRetryAlgorithm<?> algorithm = - retryAlgorithmManager.getForBucketsCreate(bucketPb, optionsMap); - return run( - algorithm, - () -> storageRpc.create(bucketPb, optionsMap), - (b) -> Conversions.json().bucketInfo().decode(b).asBucket(this)); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + final com.google.api.services.storage.model.Bucket bucketPb = + codecs.bucketInfo().encode(bucketInfo); + final Map<StorageRpc.Option, ?> optionsMap = + Opts.unwrap(options).resolveFrom(bucketInfo).getRpcOptions(); + ResultRetryAlgorithm<?> algorithm = + retryAlgorithmManager.getForBucketsCreate(bucketPb, optionsMap); + return run( + algorithm, + () -> storageRpc.create(bucketPb, optionsMap), + (b) -> Conversions.json().bucketInfo().decode(b).asBucket(this)); } @Override @@ -211,29 +194,19 @@ public Blob create( @Override @Deprecated public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); - Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); - BlobInfo.Builder builder = blobInfo.toBuilder().setMd5(null).setCrc32c(null); - BlobInfo updated = opts.blobInfoMapper().apply(builder).build(); - StorageObject blobPb = codecs.blobInfo().encode(updated); - InputStream inputStreamParam = - firstNonNull(content, new ByteArrayInputStream(EMPTY_BYTE_ARRAY)); - // retries are not safe when the input is an InputStream, so we can't retry. - BlobInfo info = - Conversions.json() - .blobInfo() - .decode(storageRpc.create(blobPb, inputStreamParam, optionsMap)); - return info.asBlob(this); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); + Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); + BlobInfo.Builder builder = blobInfo.toBuilder().setMd5(null).setCrc32c(null); + BlobInfo updated = opts.blobInfoMapper().apply(builder).build(); + StorageObject blobPb = codecs.blobInfo().encode(updated); + InputStream inputStreamParam = + firstNonNull(content, new ByteArrayInputStream(EMPTY_BYTE_ARRAY)); + // retries are not safe when the input is an InputStream, so we can't retry. + BlobInfo info = + Conversions.json() + .blobInfo() + .decode(storageRpc.create(blobPb, inputStreamParam, optionsMap)); + return info.asBlob(this); } private Blob internalCreate( @@ -242,32 +215,22 @@ private Blob internalCreate( final int offset, final int length, Opts<ObjectTargetOpt> opts) { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("create", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - Preconditions.checkNotNull(content); - final Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); - - BlobInfo updated = opts.blobInfoMapper().apply(info.toBuilder()).build(); - final StorageObject blobPb = codecs.blobInfo().encode(updated); - ResultRetryAlgorithm<?> algorithm = - retryAlgorithmManager.getForObjectsCreate(blobPb, optionsMap); - return run( - algorithm, - () -> - storageRpc.create( - blobPb, new ByteArrayInputStream(content, offset, length), optionsMap), - (x) -> { - BlobInfo info1 = Conversions.json().blobInfo().decode(x); - return info1.asBlob(this); - }); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + Preconditions.checkNotNull(content); + final Map<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); + + BlobInfo updated = opts.blobInfoMapper().apply(info.toBuilder()).build(); + final StorageObject blobPb = codecs.blobInfo().encode(updated); + ResultRetryAlgorithm<?> algorithm = + retryAlgorithmManager.getForObjectsCreate(blobPb, optionsMap); + return run( + algorithm, + () -> + storageRpc.create( + blobPb, new ByteArrayInputStream(content, offset, length), optionsMap), + (x) -> { + BlobInfo info1 = Conversions.json().blobInfo().decode(x); + return info1.asBlob(this); + }); } @Override @@ -279,55 +242,45 @@ public Blob createFrom(BlobInfo blobInfo, Path path, BlobWriteOption... options) @Override public Blob createFrom(BlobInfo blobInfo, Path path, int bufferSize, BlobWriteOption... options) throws IOException { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - if (Files.isDirectory(path)) { - throw new StorageException(0, path + " is a directory"); - } - long size = Files.size(path); - if (size == 0L) { - return create(blobInfo, null, options); - } - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); - final Map<StorageRpc.Option, ?> 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<String> uploadIdSupplier = - ResumableMedia.startUploadForBlobInfo( - getOptions(), - updated, - optionsMap, - retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); - JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); - - JsonResumableSession session = - ResumableSession.json( - HttpClientContext.from(storageRpc), - getOptions().asRetryDependencies(), - retryAlgorithmManager.idempotent(), - jsonResumableWrite); - HttpContentRange contentRange = HttpContentRange.of(ByteRangeSpec.explicit(0L, size), size); - ResumableOperationResult<StorageObject> put = - session.put(RewindableContent.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); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); + if (Files.isDirectory(path)) { + throw new StorageException(0, path + " is a directory"); + } + long size = Files.size(path); + if (size == 0L) { + return create(blobInfo, null, options); } + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); + final Map<StorageRpc.Option, ?> 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<String> uploadIdSupplier = + ResumableMedia.startUploadForBlobInfo( + getOptions(), + updated, + optionsMap, + retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); + + JsonResumableSession session = + ResumableSession.json( + HttpClientContext.from(storageRpc), + getOptions().asRetryDependencies(), + retryAlgorithmManager.idempotent(), + jsonResumableWrite); + HttpContentRange contentRange = HttpContentRange.of(ByteRangeSpec.explicit(0L, size), size); + ResumableOperationResult<StorageObject> put = + session.put(RewindableContent.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 @@ -340,35 +293,20 @@ public Blob createFrom(BlobInfo blobInfo, InputStream content, BlobWriteOption.. public Blob createFrom( BlobInfo blobInfo, InputStream content, int bufferSize, BlobWriteOption... options) throws IOException { - OpenTelemetryTraceUtil.Span otelSpan = - openTelemetryTraceUtil.startSpan("createFrom", MODULE_STORAGE); - try (OpenTelemetryTraceUtil.Scope ignored = otelSpan.makeCurrent()) { - - ApiFuture<BlobInfo> objectFuture; - try (StorageWriteChannel writer = writer(blobInfo, options)) { - objectFuture = writer.getObject(); - uploadHelper(Channels.newChannel(content), writer, bufferSize); - } - // 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) { - otelSpan.recordException(e); - otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); - throw StorageException.coalesce(e); - } - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(StatusCode.ERROR, e.getClass().getSimpleName()); - // We don't want to wrap the storage exception, but we want to record any other exception - // we simply throw the exception after recording in the span. - throw e; - - } finally { - otelSpan.end(); + + ApiFuture<BlobInfo> objectFuture; + try (StorageWriteChannel writer = writer(blobInfo, options)) { + objectFuture = writer.getObject(); + uploadHelper(Channels.newChannel(content), writer, bufferSize); + } + // 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); } } @@ -722,40 +660,30 @@ public Blob compose(final ComposeRequest composeRequest) { @Override public CopyWriter copy(final CopyRequest copyRequest) { - Span otelSpan = openTelemetryTraceUtil.startSpan("copy", MODULE_STORAGE); - try (Scope ignored = otelSpan.makeCurrent()) { - BlobId source = copyRequest.getSource(); - BlobInfo target = copyRequest.getTarget(); - Opts<ObjectSourceOpt> sourceOpts = - Opts.unwrap(copyRequest.getSourceOptions()).resolveFrom(source).projectAsSource(); - Opts<ObjectTargetOpt> targetOpts = - Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(target); - - StorageObject sourcePb = codecs.blobId().encode(source); - StorageObject targetPb = codecs.blobInfo().encode(target); - ImmutableMap<StorageRpc.Option, ?> sourceOptions = sourceOpts.getRpcOptions(); - ImmutableMap<StorageRpc.Option, ?> targetOptions = targetOpts.getRpcOptions(); - RewriteRequest rewriteRequest = - new RewriteRequest( - sourcePb, - sourceOptions, - copyRequest.overrideInfo(), - targetPb, - targetOptions, - copyRequest.getMegabytesCopiedPerChunk()); - ResultRetryAlgorithm<?> algorithm = - retryAlgorithmManager.getForObjectsRewrite(rewriteRequest); - return run( - algorithm, - () -> storageRpc.openRewrite(rewriteRequest), - (r) -> new HttpCopyWriter(getOptions(), r)); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + BlobId source = copyRequest.getSource(); + BlobInfo target = copyRequest.getTarget(); + Opts<ObjectSourceOpt> sourceOpts = + Opts.unwrap(copyRequest.getSourceOptions()).resolveFrom(source).projectAsSource(); + Opts<ObjectTargetOpt> targetOpts = + Opts.unwrap(copyRequest.getTargetOptions()).resolveFrom(target); + + StorageObject sourcePb = codecs.blobId().encode(source); + StorageObject targetPb = codecs.blobInfo().encode(target); + ImmutableMap<StorageRpc.Option, ?> sourceOptions = sourceOpts.getRpcOptions(); + ImmutableMap<StorageRpc.Option, ?> targetOptions = targetOpts.getRpcOptions(); + RewriteRequest rewriteRequest = + new RewriteRequest( + sourcePb, + sourceOptions, + copyRequest.overrideInfo(), + targetPb, + targetOptions, + copyRequest.getMegabytesCopiedPerChunk()); + ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsRewrite(rewriteRequest); + return run( + algorithm, + () -> storageRpc.openRewrite(rewriteRequest), + (r) -> new HttpCopyWriter(getOptions(), r)); } @Override @@ -765,22 +693,13 @@ public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... optio @Override public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("readAllBytes", MODULE_STORAGE); - try (Scope ignored = otelSpan.makeCurrent()) { - final StorageObject storageObject = codecs.blobId().encode(blob); - Opts<ObjectSourceOpt> unwrap = Opts.unwrap(options); - Opts<ObjectSourceOpt> resolve = unwrap.resolveFrom(blob); - ImmutableMap<StorageRpc.Option, ?> optionsMap = resolve.getRpcOptions(); - ResultRetryAlgorithm<?> algorithm = - retryAlgorithmManager.getForObjectsGet(storageObject, optionsMap); - return run(algorithm, () -> storageRpc.load(storageObject, optionsMap), Function.identity()); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + final StorageObject storageObject = codecs.blobId().encode(blob); + Opts<ObjectSourceOpt> unwrap = Opts.unwrap(options); + Opts<ObjectSourceOpt> resolve = unwrap.resolveFrom(blob); + ImmutableMap<StorageRpc.Option, ?> optionsMap = resolve.getRpcOptions(); + ResultRetryAlgorithm<?> algorithm = + retryAlgorithmManager.getForObjectsGet(storageObject, optionsMap); + return run(algorithm, () -> storageRpc.load(storageObject, optionsMap), Function.identity()); } @Override @@ -795,19 +714,10 @@ public StorageReadChannel reader(String bucket, String blob, BlobSourceOption... @Override public StorageReadChannel reader(BlobId blob, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("reader", MODULE_STORAGE); - try (Scope ignored = otelSpan.makeCurrent()) { - Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob); - StorageObject storageObject = Conversions.json().blobId().encode(blob); - ImmutableMap<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); - return new BlobReadChannelV2(storageObject, optionsMap, BlobReadChannelContext.from(this)); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + Opts<ObjectSourceOpt> opts = Opts.unwrap(options).resolveFrom(blob); + StorageObject storageObject = Conversions.json().blobId().encode(blob); + ImmutableMap<StorageRpc.Option, ?> optionsMap = opts.getRpcOptions(); + return new BlobReadChannelV2(storageObject, optionsMap, BlobReadChannelContext.from(this)); } @Override @@ -821,84 +731,57 @@ public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) { @Override public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("downloadTo", MODULE_STORAGE); - try (Scope ignored = otelSpan.makeCurrent()) { - final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream); - final StorageObject pb = codecs.blobId().encode(blob); - ImmutableMap<StorageRpc.Option, ?> optionsMap = - Opts.unwrap(options).resolveFrom(blob).getRpcOptions(); - ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsGet(pb, optionsMap); - run( - algorithm, - callable( - () -> { - storageRpc.read( - pb, optionsMap, countingOutputStream.getCount(), countingOutputStream); - }), - Function.identity()); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream); + final StorageObject pb = codecs.blobId().encode(blob); + ImmutableMap<StorageRpc.Option, ?> optionsMap = + Opts.unwrap(options).resolveFrom(blob).getRpcOptions(); + ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsGet(pb, optionsMap); + run( + algorithm, + callable( + () -> { + storageRpc.read( + pb, optionsMap, countingOutputStream.getCount(), countingOutputStream); + }), + Function.identity()); } @Override public StorageWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) { - Span otelSpan = openTelemetryTraceUtil.startSpan("writer", MODULE_STORAGE); - try (Scope ignored = otelSpan.makeCurrent()) { - Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); - final Map<StorageRpc.Option, ?> 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); - // open the resumable session outside the write channel - // the exception behavior of open is different from #write(ByteBuffer) - Supplier<String> uploadIdSupplier = - ResumableMedia.startUploadForBlobInfo( - getOptions(), - updated, - optionsMap, - retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); - JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); - return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + Opts<ObjectTargetOpt> opts = Opts.unwrap(options).resolveFrom(blobInfo); + final Map<StorageRpc.Option, ?> 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); + // open the resumable session outside the write channel + // the exception behavior of open is different from #write(ByteBuffer) + Supplier<String> uploadIdSupplier = + ResumableMedia.startUploadForBlobInfo( + getOptions(), + updated, + optionsMap, + retryAlgorithmManager.getForResumableUploadSessionCreate(optionsMap)); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(encode, optionsMap, uploadIdSupplier.get(), 0); + return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); } @Override public StorageWriteChannel writer(URL signedURL) { - Span otelSpan = openTelemetryTraceUtil.startSpan("writer", MODULE_STORAGE); - try (Scope ignored = otelSpan.makeCurrent()) { - // 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()); - // open the resumable session outside the write channel - // the exception behavior of open is different from #write(ByteBuffer) - String signedUrlString = signedURL.toString(); - Supplier<String> uploadIdSupplier = - ResumableMedia.startUploadForSignedUrl( - getOptions(), signedURL, forResumableUploadSessionCreate); - JsonResumableWrite jsonResumableWrite = - JsonResumableWrite.of(signedUrlString, uploadIdSupplier.get(), 0); - return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); - } catch (Exception e) { - otelSpan.recordException(e); - otelSpan.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.getClass().getSimpleName()); - throw e; - } finally { - otelSpan.end(); - } + // 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()); + // open the resumable session outside the write channel + // the exception behavior of open is different from #write(ByteBuffer) + String signedUrlString = signedURL.toString(); + Supplier<String> uploadIdSupplier = + ResumableMedia.startUploadForSignedUrl( + getOptions(), signedURL, forResumableUploadSessionCreate); + JsonResumableWrite jsonResumableWrite = + JsonResumableWrite.of(signedUrlString, uploadIdSupplier.get(), 0); + return new BlobWriteChannelV2(BlobReadChannelContext.from(getOptions()), jsonResumableWrite); } @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java index 9381181683..f149db1107 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java @@ -29,7 +29,7 @@ import com.google.cloud.storage.Storage.BlobWriteOption; import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.spi.StorageRpcFactory; -import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.api.OpenTelemetry; import java.io.IOException; import java.io.InputStream; import java.util.Properties; @@ -111,10 +111,14 @@ public abstract static class Builder public abstract StorageOptions.Builder setBlobWriteSessionConfig( @NonNull BlobWriteSessionConfig blobWriteSessionConfig); - /** @since 2.46.1 This new api is in preview and is subject to breaking changes. */ + /** + * Enable OpenTelemetry Tracing and provide an instance for the client to use. + * + * @param openTelemetry User defined instance of OpenTelemetry to be used by the library + * @since 2.47.0 This new api is in preview and is subject to breaking changes. + */ @BetaApi - public abstract StorageOptions.Builder setOpenTelemetrySdk( - @NonNull OpenTelemetrySdk openTelemetrySdk); + public abstract StorageOptions.Builder setOpenTelemetry(OpenTelemetry openTelemetry); @Override public abstract StorageOptions build(); @@ -150,7 +154,9 @@ public static String version() { return VERSION; } - public abstract OpenTelemetrySdk getOpenTelemetrySdk(); + /** @since 2.47.0 This new api is in preview and is subject to breaking changes. */ + @BetaApi + public abstract OpenTelemetry getOpenTelemetry(); @SuppressWarnings("unchecked") @Override diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java deleted file mode 100644 index 0106f18f86..0000000000 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/NoOpOpenTelemetryInstance.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.storage.otel; - -import com.google.api.core.ApiFuture; -import io.opentelemetry.api.trace.StatusCode; -import java.util.Map; -import javax.annotation.Nonnull; - -class NoOpOpenTelemetryInstance implements OpenTelemetryTraceUtil { - - @Override - public OpenTelemetryTraceUtil.Span startSpan(String spanName, String module) { - return new Span(); - } - - @Override - public OpenTelemetryTraceUtil.Span startSpan( - String spanName, String module, OpenTelemetryTraceUtil.Context parent) { - return new Span(); - } - - @Nonnull - @Override - public Span currentSpan() { - return new Span(); - } - - @Nonnull - @Override - public Context currentContext() { - return new Context(); - } - - static class Span implements OpenTelemetryTraceUtil.Span { - @Override - public void end() {} - - @Override - public void end(Throwable error) {} - - @Override - public <T> void endAtFuture(ApiFuture<T> futureValue) {} - - @Override - public OpenTelemetryTraceUtil.Span recordException(Throwable error) { - return this; - } - - @Override - public OpenTelemetryTraceUtil.Span setStatus(StatusCode status, String name) { - return this; - } - - @Override - public OpenTelemetryTraceUtil.Span addEvent(String name) { - return this; - } - - @Override - public OpenTelemetryTraceUtil.Span addEvent(String name, Map<String, Object> attributes) { - return this; - } - - @Override - public Scope makeCurrent() { - return new Scope(); - } - } - - static class Context implements OpenTelemetryTraceUtil.Context { - @Override - public Scope makeCurrent() { - return new Scope(); - } - } - - static class Scope implements OpenTelemetryTraceUtil.Scope { - @Override - public void close() {} - } -} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java deleted file mode 100644 index dd9f5d4233..0000000000 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryInstance.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.storage.otel; - -import static com.google.common.base.Preconditions.checkArgument; - -import com.google.api.core.ApiFuture; -import com.google.cloud.storage.GrpcStorageOptions; -import com.google.cloud.storage.StorageOptions; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.Tracer; -import java.util.Map; -import javax.annotation.Nonnull; - -class OpenTelemetryInstance implements OpenTelemetryTraceUtil { - private final Tracer tracer; - private final OpenTelemetry openTelemetry; - private final StorageOptions storageOptions; - - private static final String LIBRARY_NAME = "cloud.google.com/java/storage"; - - private final String transport; - - OpenTelemetryInstance(StorageOptions storageOptions) { - this.storageOptions = storageOptions; - this.openTelemetry = storageOptions.getOpenTelemetrySdk(); - this.tracer = openTelemetry.getTracer(LIBRARY_NAME, storageOptions.getLibraryVersion()); - this.transport = storageOptions instanceof GrpcStorageOptions ? "grpc" : "http"; - } - - static class Span implements OpenTelemetryTraceUtil.Span { - private final io.opentelemetry.api.trace.Span span; - private final String spanName; - - private Span(io.opentelemetry.api.trace.Span span, String spanName) { - this.span = span; - this.spanName = spanName; - } - - @Override - public OpenTelemetryTraceUtil.Span recordException(Throwable error) { - span.recordException( - error, - Attributes.of( - AttributeKey.stringKey("exception.message"), error.getMessage(), - AttributeKey.stringKey("exception.type"), error.getClass().getName(), - AttributeKey.stringKey("exception.stacktrace"), error.getStackTrace().toString())); - return this; - } - - @Override - public OpenTelemetryTraceUtil.Span setStatus(StatusCode status, String name) { - span.setStatus(status, name); - return this; - } - - @Override - public OpenTelemetryTraceUtil.Span addEvent(String name) { - span.addEvent(name); - return this; - } - - @Override - public OpenTelemetryTraceUtil.Span addEvent(String name, Map<String, Object> attributes) { - AttributesBuilder attributesBuilder = Attributes.builder(); - attributes.forEach( - (key, value) -> { - if (value instanceof Integer) { - attributesBuilder.put(key, (int) value); - } else if (value instanceof Long) { - attributesBuilder.put(key, (long) value); - } else if (value instanceof Double) { - attributesBuilder.put(key, (double) value); - } else if (value instanceof Float) { - attributesBuilder.put(key, (float) value); - } else if (value instanceof Boolean) { - attributesBuilder.put(key, (boolean) value); - } else if (value instanceof String) { - attributesBuilder.put(key, (String) value); - } else { - // OpenTelemetry APIs do not support any other type. - throw new IllegalArgumentException( - "Unknown attribute type:" + value.getClass().getSimpleName()); - } - }); - span.addEvent(name, attributesBuilder.build()); - return this; - } - - @Override - public Scope makeCurrent() { - return new Scope(span.makeCurrent()); - } - - @Override - public void end() { - span.end(); - } - - @Override - public void end(Throwable error) {} - - @Override - public <T> void endAtFuture(ApiFuture<T> futureValue) {} - } - - static class Scope implements OpenTelemetryTraceUtil.Scope { - private final io.opentelemetry.context.Scope scope; - - private Scope(io.opentelemetry.context.Scope scope) { - this.scope = scope; - } - - @Override - public void close() { - scope.close(); - } - } - - static class Context implements OpenTelemetryTraceUtil.Context { - private final io.opentelemetry.context.Context context; - - private Context(io.opentelemetry.context.Context context) { - this.context = context; - } - - @Override - public Scope makeCurrent() { - return new Scope(context.makeCurrent()); - } - } - - @Override - public OpenTelemetryTraceUtil.Span startSpan(String methodName, String module) { - String formatSpanName = String.format("%s/%s", module, methodName); - SpanBuilder spanBuilder = tracer.spanBuilder(formatSpanName); - io.opentelemetry.api.trace.Span span = - addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); - return new Span(span, formatSpanName); - } - - @Override - public OpenTelemetryTraceUtil.Span startSpan( - String methodName, String module, OpenTelemetryTraceUtil.Context parent) { - checkArgument( - parent instanceof OpenTelemetryInstance.Context, - "parent must be an instance of " + OpenTelemetryInstance.Context.class.getName()); - String formatSpanName = String.format("%s/%s", module, methodName); - Context p2 = (Context) parent; - SpanBuilder spanBuilder = - tracer.spanBuilder(formatSpanName).setSpanKind(SpanKind.CLIENT).setParent(p2.context); - io.opentelemetry.api.trace.Span span = - addSettingsAttributesToCurrentSpan(spanBuilder).startSpan(); - return new Span(span, formatSpanName); - } - - @Nonnull - @Override - public OpenTelemetryTraceUtil.Span currentSpan() { - return new Span(io.opentelemetry.api.trace.Span.current(), ""); - } - - @Nonnull - @Override - public OpenTelemetryTraceUtil.Context currentContext() { - return new Context(io.opentelemetry.context.Context.current()); - } - - private SpanBuilder addSettingsAttributesToCurrentSpan(SpanBuilder spanBuilder) { - spanBuilder = spanBuilder.setAttribute("gcp.client.service", "Storage"); - spanBuilder = - spanBuilder.setAllAttributes( - Attributes.builder() - .put("gcp.client.version", storageOptions.getLibraryVersion()) - .put("gcp.client.repo", "googleapis/java-storage") - .put("gcp.client.artifact", "com.google.cloud:google-cloud-storage") - .put("rpc.system", transport) - .build()); - return spanBuilder; - } -} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java deleted file mode 100644 index ed76c31e39..0000000000 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/OpenTelemetryTraceUtil.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.storage.otel; - -import com.google.api.core.ApiFuture; -import com.google.api.core.InternalApi; -import com.google.cloud.storage.Storage; -import com.google.cloud.storage.StorageOptions; -import com.google.cloud.storage.spi.v1.StorageRpc; -import io.opentelemetry.api.trace.StatusCode; -import java.util.Map; -import javax.annotation.Nonnull; - -@InternalApi -public interface OpenTelemetryTraceUtil { - String MODULE_STORAGE = Storage.class.getName(); - String MODULE_STORAGE_RPC = StorageRpc.class.getName(); - - @InternalApi - static OpenTelemetryTraceUtil getInstance(@Nonnull StorageOptions storageOptions) { - boolean createNoOp = storageOptions.getOpenTelemetrySdk() == null; - - if (createNoOp) { - return new NoOpOpenTelemetryInstance(); - } else { - return new OpenTelemetryInstance(storageOptions); - } - } - - /** Represents a trace span. */ - @InternalApi - interface Span { - @InternalApi - Span recordException(Throwable error); - - @InternalApi - Span setStatus(StatusCode status, String name); - /** Adds the given event to this span. */ - @InternalApi - Span addEvent(String name); - - /** Adds the given event with the given attributes to this span. */ - @InternalApi - Span addEvent(String name, Map<String, Object> attributes); - - /** Marks this span as the current span. */ - @InternalApi - Scope makeCurrent(); - - /** Ends this span. */ - @InternalApi - void end(); - - /** Ends this span in an error. */ - @InternalApi - void end(Throwable error); - - /** - * If an operation ends in the future, its relevant span should end _after_ the future has been - * completed. This method "appends" the span completion code at the completion of the given - * future. In order for telemetry info to be recorded, the future returned by this method should - * be completed. - */ - @InternalApi - <T> void endAtFuture(ApiFuture<T> futureValue); - } - - /** Represents a trace context. */ - @InternalApi - interface Context { - /** Makes this context the current context. */ - @InternalApi - Scope makeCurrent(); - } - - /** Represents a trace scope. */ - @InternalApi - interface Scope extends AutoCloseable { - /** Closes the current scope. */ - @InternalApi - void close(); - } - - /** Starts a new span with the given name, sets it as the current span, and returns it. */ - @InternalApi - Span startSpan(String spanName, String module); - - /** - * Starts a new span with the given name and the given context as its parent, sets it as the - * current span, and returns it. - */ - @InternalApi - Span startSpan(String spanName, String module, Context parent); - - /** Returns the current span. */ - @Nonnull - @InternalApi - Span currentSpan(); - - /** Returns the current Context. */ - @Nonnull - @InternalApi - Context currentContext(); -} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/package-info.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/package-info.java deleted file mode 100644 index d8fac3ca8e..0000000000 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/otel/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Set of internal utilities to make our OTel use a bit more terse. - * - * <p>All classes, interfaces, etc are considered to be for internal library use only and can break - * at any time. - */ -@InternalApi -package com.google.cloud.storage.otel; - -import com.google.api.core.InternalApi; diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java index b85e6a72f1..3b8957bbac 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTest.java @@ -16,21 +16,22 @@ package com.google.cloud.storage; +import static com.google.cloud.storage.TestUtils.assertAll; +import static com.google.common.truth.Truth.assertThat; + 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.CrossRun; import com.google.cloud.storage.it.runner.annotations.Inject; +import com.google.cloud.storage.it.runner.registry.Generator; import com.google.cloud.storage.otel.TestExporter; -import com.google.cloud.storage.testing.RemoteStorageHelper; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.util.UUID; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,12 +40,17 @@ backends = Backend.PROD, transports = {Transport.HTTP, Transport.GRPC}) public final class ITOpenTelemetryTest { + @Inject public Storage storage; + + @Inject public BucketInfo bucket; + + @Inject public Generator generator; @Inject public Transport transport; @Test - public void checkInstrumentation() { - SpanExporter exporter = new TestExporter(); + public void checkInstrumentation() throws Exception { + TestExporter exporter = new TestExporter(); OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder() @@ -53,46 +59,33 @@ public void checkInstrumentation() { .addSpanProcessor(SimpleSpanProcessor.create(exporter)) .build()) .build(); - StorageOptions storageOptions = - storage.getOptions().toBuilder().setOpenTelemetrySdk(openTelemetrySdk).build(); - storage = storageOptions.getService(); - String bucket = randomBucketName(); - try { - storage.create(BucketInfo.of(bucket)); - TestExporter testExported = (TestExporter) exporter; - SpanData spanData = testExported.getExportedSpans().get(0); - Assert.assertEquals("Storage", getAttributeValue(spanData, "gcp.client.service")); - Assert.assertEquals( - "googleapis/java-storage", getAttributeValue(spanData, "gcp.client.repo")); - Assert.assertEquals( - "com.google.cloud:google-cloud-storage", - getAttributeValue(spanData, "gcp.client.artifact")); - Assert.assertEquals( - transport.name().toLowerCase(), getAttributeValue(spanData, "rpc.system")); - } finally { - // Cleanup - RemoteStorageHelper.forceDelete(storage, bucket); + storage.getOptions().toBuilder().setOpenTelemetry(openTelemetrySdk).build(); + try (Storage storage = storageOptions.getService()) { + storage.create(BlobInfo.newBuilder(bucket, generator.randomObjectName()).build()); } + + SpanData spanData = exporter.getExportedSpans().get(0); + assertAll( + () -> assertThat(getAttributeValue(spanData, "gcp.client.service")).isEqualTo("Storage"), + () -> + assertThat(getAttributeValue(spanData, "gcp.client.repo")) + .isEqualTo("googleapis/java-storage"), + () -> + assertThat(getAttributeValue(spanData, "gcp.client.artifact")) + .isEqualTo("com.google.cloud:google-cloud-storage"), + () -> + assertThat(getAttributeValue(spanData, "rpc.system")) + .isEqualTo(transport.name().toLowerCase())); } @Test public void noOpDoesNothing() { - String bucket = randomBucketName(); - try { - storage.create(BucketInfo.of(bucket)); - Assert.assertNull(storage.getOptions().getOpenTelemetrySdk()); - } finally { - // cleanup - RemoteStorageHelper.forceDelete(storage, bucket); - } - } - - private String getAttributeValue(SpanData spanData, String key) { - return spanData.getAttributes().get(AttributeKey.stringKey(key)).toString(); + assertThat(storage.getOptions().getOpenTelemetry()).isSameInstanceAs(OpenTelemetry.noop()); + storage.create(BlobInfo.newBuilder(bucket, generator.randomObjectName()).build()); } - public String randomBucketName() { - return "java-storage-grpc-rand-" + UUID.randomUUID(); + private static String getAttributeValue(SpanData spanData, String key) { + return spanData.getAttributes().get(AttributeKey.stringKey(key)); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java index 66f08c61f3..ed999ad0a4 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java @@ -43,6 +43,7 @@ import java.nio.file.Paths; import java.util.List; import java.util.UUID; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -57,7 +58,6 @@ public class ITOpenTelemetryTestbenchTest { @Inject public Generator generator; @Inject public BucketInfo testBucket; @Inject public Storage storage; - private StorageOptions options; private SpanExporter exporter; private static final byte[] helloWorldTextBytes = "hello world".getBytes(); private BlobId blobId; @@ -73,12 +73,20 @@ public void setUp() { .addSpanProcessor(SimpleSpanProcessor.create(exporter)) .build()) .build(); - options = storage.getOptions().toBuilder().setOpenTelemetrySdk(openTelemetrySdk).build(); + StorageOptions options = + storage.getOptions().toBuilder().setOpenTelemetry(openTelemetrySdk).build(); storage = options.getService(); String objectString = generator.randomObjectName(); blobId = BlobId.of(testBucket.getName(), objectString); } + @After + public void tearDown() throws Exception { + if (storage != null) { + storage.close(); + } + } + @Test public void runCreateBucket() { String bucket = "random-bucket" + UUID.randomUUID(); From 114a960292fbfe0ebb2ee1e4a4835838e13409ee Mon Sep 17 00:00:00 2001 From: BenWhitehead <BenWhitehead@users.noreply.github.com> Date: Mon, 6 Jan 2025 22:26:38 +0000 Subject: [PATCH 28/28] chore: fix copy test --- .../google/cloud/storage/ITOpenTelemetryTestbenchTest.java | 5 ++++- .../google/cloud/storage/it/runner/registry/TestBench.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java index ed999ad0a4..f813fcf41c 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITOpenTelemetryTestbenchTest.java @@ -16,6 +16,7 @@ package com.google.cloud.storage; +import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.cloud.ReadChannel; @@ -166,7 +167,9 @@ public void runCopy() { .setSourceOptions(BlobSourceOption.generationMatch(cpySrc.getGeneration())) .setTarget(dst, BlobTargetOption.doesNotExist()) .build(); - storage.copy(copyRequest); + CopyWriter copyWriter = storage.copy(copyRequest); + BlobInfo result = copyWriter.getResult(); + assertThat(result).isNotNull(); TestExporter testExported = (TestExporter) exporter; List<SpanData> spanData = testExported.getExportedSpans(); checkCommonAttributes(spanData); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java index d40a29954d..470006c9cc 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java @@ -112,7 +112,7 @@ private TestBench( String dockerImageName, String dockerImageTag, String containerName) { - this.ignorePullError = ignorePullError; + this.ignorePullError = true; this.baseUri = baseUri; this.gRPCBaseUri = gRPCBaseUri; this.dockerImageName = dockerImageName;