Skip to content

Commit

Permalink
fix: plumb GrpcInterceptorProvider to constructed InstantiatingGrpcCh…
Browse files Browse the repository at this point in the history
…annelProvider (#2031)
  • Loading branch information
BenWhitehead authored May 23, 2023
1 parent 3675514 commit bfe0415
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.core.GaxProperties;
import com.google.api.gax.core.NoCredentialsProvider;
import com.google.api.gax.grpc.GrpcInterceptorProvider;
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
import com.google.api.gax.retrying.RetrySettings;
import com.google.api.gax.retrying.StreamResumptionStrategy;
Expand All @@ -46,13 +47,16 @@
import com.google.cloud.storage.UnifiedOpts.UserProject;
import com.google.cloud.storage.spi.StorageRpcFactory;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.storage.v2.ReadObjectRequest;
import com.google.storage.v2.ReadObjectResponse;
import com.google.storage.v2.StorageClient;
import com.google.storage.v2.StorageSettings;
import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannelBuilder;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.util.List;
import java.util.Locale;
Expand All @@ -77,6 +81,7 @@ public final class GrpcStorageOptions extends StorageOptions
private final GrpcRetryAlgorithmManager retryAlgorithmManager;
private final Duration terminationAwaitDuration;
private final boolean attemptDirectPath;
private final GrpcInterceptorProvider grpcInterceptorProvider;

private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) {
super(builder, serviceDefaults);
Expand All @@ -88,6 +93,7 @@ private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults)
MoreObjects.firstNonNull(
builder.terminationAwaitDuration, serviceDefaults.getTerminationAwaitDuration());
this.attemptDirectPath = builder.attemptDirectPath;
this.grpcInterceptorProvider = builder.grpcInterceptorProvider;
}

@Override
Expand Down Expand Up @@ -224,6 +230,10 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw
.setAllowNonDefaultServiceAccount(true)
.setAttemptDirectPath(attemptDirectPath);

if (!NoopGrpcInterceptorProvider.INSTANCE.equals(grpcInterceptorProvider)) {
channelProviderBuilder.setInterceptorProvider(grpcInterceptorProvider);
}

if (attemptDirectPath) {
channelProviderBuilder.setAttemptDirectPathXds();
}
Expand Down Expand Up @@ -334,6 +344,8 @@ public static final class Builder extends StorageOptions.Builder {
private StorageRetryStrategy storageRetryStrategy;
private Duration terminationAwaitDuration;
private boolean attemptDirectPath = GrpcStorageDefaults.INSTANCE.isAttemptDirectPath();
private GrpcInterceptorProvider grpcInterceptorProvider =
GrpcStorageDefaults.INSTANCE.grpcInterceptorProvider();

Builder() {}

Expand Down Expand Up @@ -488,6 +500,15 @@ public GrpcStorageOptions.Builder setQuotaProjectId(String quotaProjectId) {
return this;
}

/** @since 2.22.3 This new api is in preview and is subject to breaking changes. */
@BetaApi
public GrpcStorageOptions.Builder setGrpcInterceptorProvider(
@NonNull GrpcInterceptorProvider grpcInterceptorProvider) {
requireNonNull(grpcInterceptorProvider, "grpcInterceptorProvider must be non null");
this.grpcInterceptorProvider = grpcInterceptorProvider;
return this;
}

/** @since 2.14.0 This new api is in preview and is subject to breaking changes. */
@BetaApi
@Override
Expand All @@ -502,6 +523,8 @@ public static final class GrpcStorageDefaults extends StorageDefaults {
static final GrpcStorageDefaults INSTANCE = new GrpcStorageOptions.GrpcStorageDefaults();
static final StorageFactory STORAGE_FACTORY = new GrpcStorageFactory();
static final StorageRpcFactory STORAGE_RPC_FACTORY = new GrpcStorageRpcFactory();
static final GrpcInterceptorProvider INTERCEPTOR_PROVIDER =
NoopGrpcInterceptorProvider.INSTANCE;

private GrpcStorageDefaults() {}

Expand Down Expand Up @@ -543,6 +566,12 @@ public Duration getTerminationAwaitDuration() {
public boolean isAttemptDirectPath() {
return false;
}

/** @since 2.22.3 This new api is in preview and is subject to breaking changes. */
@BetaApi
public GrpcInterceptorProvider grpcInterceptorProvider() {
return INTERCEPTOR_PROVIDER;
}
}

/**
Expand Down Expand Up @@ -694,4 +723,19 @@ protected StorageSettings.Builder setInternalHeaderProvider(
return super.setInternalHeaderProvider(internalHeaderProvider);
}
}

private static final class NoopGrpcInterceptorProvider
implements GrpcInterceptorProvider, Serializable {
private static final NoopGrpcInterceptorProvider INSTANCE = new NoopGrpcInterceptorProvider();

@Override
public List<ClientInterceptor> getInterceptors() {
return ImmutableList.of();
}

/** prevent java serialization from using a new instance */
private Object readResolve() {
return INSTANCE;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.storage.it;

import static com.google.common.truth.Truth.assertThat;

import com.google.api.gax.paging.Page;
import com.google.cloud.storage.Bucket;
import com.google.cloud.storage.BucketInfo;
import com.google.cloud.storage.GrpcStorageOptions;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.Storage.BucketListOption;
import com.google.cloud.storage.StorageOptions;
import com.google.cloud.storage.TransportCompatibility.Transport;
import com.google.cloud.storage.it.runner.StorageITRunner;
import com.google.cloud.storage.it.runner.annotations.Backend;
import com.google.cloud.storage.it.runner.annotations.Inject;
import com.google.cloud.storage.it.runner.annotations.SingleBackend;
import com.google.cloud.storage.it.runner.annotations.StorageFixture;
import com.google.common.collect.ImmutableList;
import io.grpc.Attributes;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ClientStreamTracer;
import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(StorageITRunner.class)
@SingleBackend(Backend.PROD)
public class ITGrpcInterceptorTest {
private static final Metadata.Key<String> X_GOOG_REQUEST_PARAMS =
Metadata.Key.of("x-goog-request-params", Metadata.ASCII_STRING_MARSHALLER);

@Inject
@StorageFixture(Transport.GRPC)
public Storage storage;

@Inject public BucketInfo bucket;

@Test
public void grpcStorageOptions_allowSpecifyingInterceptor() throws Exception {
TracerFactory factory = new TracerFactory();
Interceptor interceptor = new Interceptor(factory);
StorageOptions options =
((GrpcStorageOptions) storage.getOptions())
.toBuilder()
.setGrpcInterceptorProvider(() -> ImmutableList.of(interceptor))
.build();

try (Storage storage = options.getService()) {
Page<Bucket> page = storage.list(BucketListOption.prefix(bucket.getName()));
List<String> bucketNames =
page.streamAll().map(BucketInfo::getName).collect(Collectors.toList());
assertThat(bucketNames).contains(bucket.getName());
}

assertThat(factory.metadatas).isNotEmpty();
List<String> requestParams =
factory.metadatas.stream()
.map(m -> m.get(X_GOOG_REQUEST_PARAMS))
.collect(Collectors.toList());

System.out.println("requestParams = " + requestParams);

String expected = String.format("project=projects/%s", options.getProjectId());
assertThat(requestParams).contains(expected);
}

private static final class Interceptor implements ClientInterceptor {

private final TracerFactory factory;

public Interceptor(TracerFactory factory) {
this.factory = factory;
}

@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
CallOptions callOptions1 = callOptions.withStreamTracerFactory(factory);
return next.newCall(method, callOptions1);
}
}

private static final class TracerFactory extends ClientStreamTracer.Factory {

private final List<Metadata> metadatas = Collections.synchronizedList(new ArrayList<>());

@Override
public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
return new ClientStreamTracer() {
@Override
public void streamCreated(Attributes transportAttrs, Metadata headers) {
metadatas.add(headers);
}
};
}
}
}

0 comments on commit bfe0415

Please sign in to comment.