Skip to content

Commit bfe0415

Browse files
authored
fix: plumb GrpcInterceptorProvider to constructed InstantiatingGrpcChannelProvider (#2031)
1 parent 3675514 commit bfe0415

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.api.gax.core.FixedCredentialsProvider;
2626
import com.google.api.gax.core.GaxProperties;
2727
import com.google.api.gax.core.NoCredentialsProvider;
28+
import com.google.api.gax.grpc.GrpcInterceptorProvider;
2829
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
2930
import com.google.api.gax.retrying.RetrySettings;
3031
import com.google.api.gax.retrying.StreamResumptionStrategy;
@@ -46,13 +47,16 @@
4647
import com.google.cloud.storage.UnifiedOpts.UserProject;
4748
import com.google.cloud.storage.spi.StorageRpcFactory;
4849
import com.google.common.base.MoreObjects;
50+
import com.google.common.collect.ImmutableList;
4951
import com.google.common.collect.ImmutableSet;
5052
import com.google.storage.v2.ReadObjectRequest;
5153
import com.google.storage.v2.ReadObjectResponse;
5254
import com.google.storage.v2.StorageClient;
5355
import com.google.storage.v2.StorageSettings;
56+
import io.grpc.ClientInterceptor;
5457
import io.grpc.ManagedChannelBuilder;
5558
import java.io.IOException;
59+
import java.io.Serializable;
5660
import java.net.URI;
5761
import java.util.List;
5862
import java.util.Locale;
@@ -77,6 +81,7 @@ public final class GrpcStorageOptions extends StorageOptions
7781
private final GrpcRetryAlgorithmManager retryAlgorithmManager;
7882
private final Duration terminationAwaitDuration;
7983
private final boolean attemptDirectPath;
84+
private final GrpcInterceptorProvider grpcInterceptorProvider;
8085

8186
private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults) {
8287
super(builder, serviceDefaults);
@@ -88,6 +93,7 @@ private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults)
8893
MoreObjects.firstNonNull(
8994
builder.terminationAwaitDuration, serviceDefaults.getTerminationAwaitDuration());
9095
this.attemptDirectPath = builder.attemptDirectPath;
96+
this.grpcInterceptorProvider = builder.grpcInterceptorProvider;
9197
}
9298

9399
@Override
@@ -224,6 +230,10 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw
224230
.setAllowNonDefaultServiceAccount(true)
225231
.setAttemptDirectPath(attemptDirectPath);
226232

233+
if (!NoopGrpcInterceptorProvider.INSTANCE.equals(grpcInterceptorProvider)) {
234+
channelProviderBuilder.setInterceptorProvider(grpcInterceptorProvider);
235+
}
236+
227237
if (attemptDirectPath) {
228238
channelProviderBuilder.setAttemptDirectPathXds();
229239
}
@@ -334,6 +344,8 @@ public static final class Builder extends StorageOptions.Builder {
334344
private StorageRetryStrategy storageRetryStrategy;
335345
private Duration terminationAwaitDuration;
336346
private boolean attemptDirectPath = GrpcStorageDefaults.INSTANCE.isAttemptDirectPath();
347+
private GrpcInterceptorProvider grpcInterceptorProvider =
348+
GrpcStorageDefaults.INSTANCE.grpcInterceptorProvider();
337349

338350
Builder() {}
339351

@@ -488,6 +500,15 @@ public GrpcStorageOptions.Builder setQuotaProjectId(String quotaProjectId) {
488500
return this;
489501
}
490502

503+
/** @since 2.22.3 This new api is in preview and is subject to breaking changes. */
504+
@BetaApi
505+
public GrpcStorageOptions.Builder setGrpcInterceptorProvider(
506+
@NonNull GrpcInterceptorProvider grpcInterceptorProvider) {
507+
requireNonNull(grpcInterceptorProvider, "grpcInterceptorProvider must be non null");
508+
this.grpcInterceptorProvider = grpcInterceptorProvider;
509+
return this;
510+
}
511+
491512
/** @since 2.14.0 This new api is in preview and is subject to breaking changes. */
492513
@BetaApi
493514
@Override
@@ -502,6 +523,8 @@ public static final class GrpcStorageDefaults extends StorageDefaults {
502523
static final GrpcStorageDefaults INSTANCE = new GrpcStorageOptions.GrpcStorageDefaults();
503524
static final StorageFactory STORAGE_FACTORY = new GrpcStorageFactory();
504525
static final StorageRpcFactory STORAGE_RPC_FACTORY = new GrpcStorageRpcFactory();
526+
static final GrpcInterceptorProvider INTERCEPTOR_PROVIDER =
527+
NoopGrpcInterceptorProvider.INSTANCE;
505528

506529
private GrpcStorageDefaults() {}
507530

@@ -543,6 +566,12 @@ public Duration getTerminationAwaitDuration() {
543566
public boolean isAttemptDirectPath() {
544567
return false;
545568
}
569+
570+
/** @since 2.22.3 This new api is in preview and is subject to breaking changes. */
571+
@BetaApi
572+
public GrpcInterceptorProvider grpcInterceptorProvider() {
573+
return INTERCEPTOR_PROVIDER;
574+
}
546575
}
547576

548577
/**
@@ -694,4 +723,19 @@ protected StorageSettings.Builder setInternalHeaderProvider(
694723
return super.setInternalHeaderProvider(internalHeaderProvider);
695724
}
696725
}
726+
727+
private static final class NoopGrpcInterceptorProvider
728+
implements GrpcInterceptorProvider, Serializable {
729+
private static final NoopGrpcInterceptorProvider INSTANCE = new NoopGrpcInterceptorProvider();
730+
731+
@Override
732+
public List<ClientInterceptor> getInterceptors() {
733+
return ImmutableList.of();
734+
}
735+
736+
/** prevent java serialization from using a new instance */
737+
private Object readResolve() {
738+
return INSTANCE;
739+
}
740+
}
697741
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage.it;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import com.google.api.gax.paging.Page;
22+
import com.google.cloud.storage.Bucket;
23+
import com.google.cloud.storage.BucketInfo;
24+
import com.google.cloud.storage.GrpcStorageOptions;
25+
import com.google.cloud.storage.Storage;
26+
import com.google.cloud.storage.Storage.BucketListOption;
27+
import com.google.cloud.storage.StorageOptions;
28+
import com.google.cloud.storage.TransportCompatibility.Transport;
29+
import com.google.cloud.storage.it.runner.StorageITRunner;
30+
import com.google.cloud.storage.it.runner.annotations.Backend;
31+
import com.google.cloud.storage.it.runner.annotations.Inject;
32+
import com.google.cloud.storage.it.runner.annotations.SingleBackend;
33+
import com.google.cloud.storage.it.runner.annotations.StorageFixture;
34+
import com.google.common.collect.ImmutableList;
35+
import io.grpc.Attributes;
36+
import io.grpc.CallOptions;
37+
import io.grpc.Channel;
38+
import io.grpc.ClientCall;
39+
import io.grpc.ClientInterceptor;
40+
import io.grpc.ClientStreamTracer;
41+
import io.grpc.ClientStreamTracer.StreamInfo;
42+
import io.grpc.Metadata;
43+
import io.grpc.MethodDescriptor;
44+
import java.util.ArrayList;
45+
import java.util.Collections;
46+
import java.util.List;
47+
import java.util.stream.Collectors;
48+
import org.junit.Test;
49+
import org.junit.runner.RunWith;
50+
51+
@RunWith(StorageITRunner.class)
52+
@SingleBackend(Backend.PROD)
53+
public class ITGrpcInterceptorTest {
54+
private static final Metadata.Key<String> X_GOOG_REQUEST_PARAMS =
55+
Metadata.Key.of("x-goog-request-params", Metadata.ASCII_STRING_MARSHALLER);
56+
57+
@Inject
58+
@StorageFixture(Transport.GRPC)
59+
public Storage storage;
60+
61+
@Inject public BucketInfo bucket;
62+
63+
@Test
64+
public void grpcStorageOptions_allowSpecifyingInterceptor() throws Exception {
65+
TracerFactory factory = new TracerFactory();
66+
Interceptor interceptor = new Interceptor(factory);
67+
StorageOptions options =
68+
((GrpcStorageOptions) storage.getOptions())
69+
.toBuilder()
70+
.setGrpcInterceptorProvider(() -> ImmutableList.of(interceptor))
71+
.build();
72+
73+
try (Storage storage = options.getService()) {
74+
Page<Bucket> page = storage.list(BucketListOption.prefix(bucket.getName()));
75+
List<String> bucketNames =
76+
page.streamAll().map(BucketInfo::getName).collect(Collectors.toList());
77+
assertThat(bucketNames).contains(bucket.getName());
78+
}
79+
80+
assertThat(factory.metadatas).isNotEmpty();
81+
List<String> requestParams =
82+
factory.metadatas.stream()
83+
.map(m -> m.get(X_GOOG_REQUEST_PARAMS))
84+
.collect(Collectors.toList());
85+
86+
System.out.println("requestParams = " + requestParams);
87+
88+
String expected = String.format("project=projects/%s", options.getProjectId());
89+
assertThat(requestParams).contains(expected);
90+
}
91+
92+
private static final class Interceptor implements ClientInterceptor {
93+
94+
private final TracerFactory factory;
95+
96+
public Interceptor(TracerFactory factory) {
97+
this.factory = factory;
98+
}
99+
100+
@Override
101+
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
102+
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
103+
CallOptions callOptions1 = callOptions.withStreamTracerFactory(factory);
104+
return next.newCall(method, callOptions1);
105+
}
106+
}
107+
108+
private static final class TracerFactory extends ClientStreamTracer.Factory {
109+
110+
private final List<Metadata> metadatas = Collections.synchronizedList(new ArrayList<>());
111+
112+
@Override
113+
public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
114+
return new ClientStreamTracer() {
115+
@Override
116+
public void streamCreated(Attributes transportAttrs, Metadata headers) {
117+
metadatas.add(headers);
118+
}
119+
};
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)