2020
2121import com .google .common .annotations .VisibleForTesting ;
2222import com .google .common .base .Preconditions ;
23- import com .google .common .base .Strings ;
2423import com .google .common .collect .ImmutableList ;
2524import com .google .common .collect .ImmutableMap ;
2625import com .google .common .io .CharStreams ;
26+ import com .google .errorprone .annotations .concurrent .GuardedBy ;
27+ import io .grpc .MetricRecorder ;
2728import io .grpc .NameResolver ;
2829import io .grpc .NameResolverRegistry ;
2930import io .grpc .Status ;
3233import io .grpc .internal .GrpcUtil ;
3334import io .grpc .internal .SharedResourceHolder ;
3435import io .grpc .internal .SharedResourceHolder .Resource ;
36+ import io .grpc .xds .InternalGrpcBootstrapperImpl ;
37+ import io .grpc .xds .InternalSharedXdsClientPoolProvider ;
38+ import io .grpc .xds .InternalSharedXdsClientPoolProvider .XdsClientResult ;
39+ import io .grpc .xds .XdsNameResolverProvider ;
40+ import io .grpc .xds .client .Bootstrapper .BootstrapInfo ;
41+ import io .grpc .xds .client .XdsClient ;
42+ import io .grpc .xds .client .XdsInitializationException ;
3543import java .io .IOException ;
3644import java .io .InputStream ;
3745import java .io .InputStreamReader ;
4149import java .net .URISyntaxException ;
4250import java .net .URL ;
4351import java .nio .charset .StandardCharsets ;
44- import java .util .Map ;
4552import java .util .Random ;
4653import java .util .concurrent .Executor ;
4754import java .util .logging .Level ;
@@ -63,52 +70,53 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
6370 static final String C2P_AUTHORITY = "traffic-director-c2p.xds.googleapis.com" ;
6471 @ VisibleForTesting
6572 static boolean isOnGcp = InternalCheckGcpEnvironment .isOnGcp ();
66- @ VisibleForTesting
67- static boolean xdsBootstrapProvided =
68- System .getenv ("GRPC_XDS_BOOTSTRAP" ) != null
69- || System .getProperty ("io.grpc.xds.bootstrap" ) != null
70- || System .getenv ("GRPC_XDS_BOOTSTRAP_CONFIG" ) != null
71- || System .getProperty ("io.grpc.xds.bootstrapConfig" ) != null ;
72- @ VisibleForTesting
73- static boolean enableFederation =
74- Strings .isNullOrEmpty (System .getenv ("GRPC_EXPERIMENTAL_XDS_FEDERATION" ))
75- || Boolean .parseBoolean (System .getenv ("GRPC_EXPERIMENTAL_XDS_FEDERATION" ));
7673
7774 private static final String serverUriOverride =
7875 System .getenv ("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI" );
7976
80- private HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory .INSTANCE ;
77+ @ GuardedBy ("GoogleCloudToProdNameResolver.class" )
78+ private static BootstrapInfo bootstrapInfo ;
79+ private static HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory .INSTANCE ;
80+ private static int c2pId = new Random ().nextInt ();
81+
82+ private static synchronized BootstrapInfo getBootstrapInfo ()
83+ throws XdsInitializationException , IOException {
84+ if (bootstrapInfo != null ) {
85+ return bootstrapInfo ;
86+ }
87+ BootstrapInfo bootstrapInfoTmp =
88+ InternalGrpcBootstrapperImpl .parseBootstrap (generateBootstrap ());
89+ // Avoid setting global when testing
90+ if (httpConnectionProvider == HttpConnectionFactory .INSTANCE ) {
91+ bootstrapInfo = bootstrapInfoTmp ;
92+ }
93+ return bootstrapInfoTmp ;
94+ }
95+
8196 private final String authority ;
8297 private final SynchronizationContext syncContext ;
8398 private final Resource <Executor > executorResource ;
84- private final BootstrapSetter bootstrapSetter ;
99+ private final String target ;
100+ private final MetricRecorder metricRecorder ;
85101 private final NameResolver delegate ;
86- private final Random rand ;
87102 private final boolean usingExecutorResource ;
88- // It's not possible to use both PSM and DirectPath C2P in the same application.
89- // Delegate to DNS if user-provided bootstrap is found.
90- private final String schemeOverride =
91- !isOnGcp
92- || (xdsBootstrapProvided && !enableFederation )
93- ? "dns" : "xds" ;
103+ private final String schemeOverride = !isOnGcp ? "dns" : "xds" ;
104+ private XdsClientResult xdsClientPool ;
105+ private XdsClient xdsClient ;
94106 private Executor executor ;
95- private Listener2 listener ;
96107 private boolean succeeded ;
97108 private boolean resolving ;
98109 private boolean shutdown ;
99110
100- GoogleCloudToProdNameResolver (URI targetUri , Args args , Resource <Executor > executorResource ,
101- BootstrapSetter bootstrapSetter ) {
102- this (targetUri , args , executorResource , new Random (), bootstrapSetter ,
111+ GoogleCloudToProdNameResolver (URI targetUri , Args args , Resource <Executor > executorResource ) {
112+ this (targetUri , args , executorResource ,
103113 NameResolverRegistry .getDefaultRegistry ().asFactory ());
104114 }
105115
106116 @ VisibleForTesting
107117 GoogleCloudToProdNameResolver (URI targetUri , Args args , Resource <Executor > executorResource ,
108- Random rand , BootstrapSetter bootstrapSetter , NameResolver .Factory nameResolverFactory ) {
118+ NameResolver .Factory nameResolverFactory ) {
109119 this .executorResource = checkNotNull (executorResource , "executorResource" );
110- this .bootstrapSetter = checkNotNull (bootstrapSetter , "bootstrapSetter" );
111- this .rand = checkNotNull (rand , "rand" );
112120 String targetPath = checkNotNull (checkNotNull (targetUri , "targetUri" ).getPath (), "targetPath" );
113121 Preconditions .checkArgument (
114122 targetPath .startsWith ("/" ),
@@ -118,9 +126,14 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
118126 authority = GrpcUtil .checkAuthority (targetPath .substring (1 ));
119127 syncContext = checkNotNull (args , "args" ).getSynchronizationContext ();
120128 targetUri = overrideUriScheme (targetUri , schemeOverride );
121- if (schemeOverride .equals ("xds" ) && enableFederation ) {
129+ if (schemeOverride .equals ("xds" )) {
122130 targetUri = overrideUriAuthority (targetUri , C2P_AUTHORITY );
131+ args = args .toBuilder ()
132+ .setArg (XdsNameResolverProvider .XDS_CLIENT_SUPPLIER , () -> xdsClient )
133+ .build ();
123134 }
135+ target = targetUri .toString ();
136+ metricRecorder = args .getMetricRecorder ();
124137 delegate = checkNotNull (nameResolverFactory , "nameResolverFactory" ).newNameResolver (
125138 targetUri , args );
126139 executor = args .getOffloadExecutor ();
@@ -150,7 +163,7 @@ private void resolve() {
150163
151164 resolving = true ;
152165 if (logger .isLoggable (Level .FINE )) {
153- logger .fine ( "resolve with schemaOverride = " + schemeOverride );
166+ logger .log ( Level . FINE , "start with schemaOverride = {0}" , schemeOverride );
154167 }
155168
156169 if (schemeOverride .equals ("dns" )) {
@@ -168,28 +181,28 @@ private void resolve() {
168181 class Resolve implements Runnable {
169182 @ Override
170183 public void run () {
171- ImmutableMap < String , ?> rawBootstrap = null ;
184+ BootstrapInfo bootstrapInfo = null ;
172185 try {
173- // User provided bootstrap configs are only supported with federation. If federation is
174- // not enabled or there is no user provided config, we set a custom bootstrap override.
175- // Otherwise, we don't set the override, which will allow a user provided bootstrap config
176- // to take effect.
177- if (!enableFederation || !xdsBootstrapProvided ) {
178- rawBootstrap = generateBootstrap (queryZoneMetadata (METADATA_URL_ZONE ),
179- queryIpv6SupportMetadata (METADATA_URL_SUPPORT_IPV6 ));
180- }
186+ bootstrapInfo = getBootstrapInfo ();
181187 } catch (IOException e ) {
182188 listener .onError (
183189 Status .INTERNAL .withDescription ("Unable to get metadata" ).withCause (e ));
190+ } catch (XdsInitializationException e ) {
191+ listener .onError (
192+ Status .INTERNAL .withDescription ("Unable to create c2p bootstrap" ).withCause (e ));
193+ } catch (Throwable t ) {
194+ listener .onError (
195+ Status .INTERNAL .withDescription ("Unexpected error creating c2p bootstrap" )
196+ .withCause (t ));
184197 } finally {
185- final ImmutableMap < String , ?> finalRawBootstrap = rawBootstrap ;
198+ final BootstrapInfo finalBootstrapInfo = bootstrapInfo ;
186199 syncContext .execute (new Runnable () {
187200 @ Override
188201 public void run () {
189- if (!shutdown ) {
190- if ( finalRawBootstrap != null ) {
191- bootstrapSetter . setBootstrap ( finalRawBootstrap );
192- }
202+ if (!shutdown && finalBootstrapInfo != null ) {
203+ xdsClientPool = InternalSharedXdsClientPoolProvider . getOrCreate (
204+ target , finalBootstrapInfo , metricRecorder , null );
205+ xdsClient = xdsClientPool . getObject ();
193206 delegate .start (listener );
194207 succeeded = true ;
195208 }
@@ -203,9 +216,16 @@ public void run() {
203216 executor .execute (new Resolve ());
204217 }
205218
206- private ImmutableMap <String , ?> generateBootstrap (String zone , boolean supportIpv6 ) {
219+ @ VisibleForTesting
220+ static ImmutableMap <String , ?> generateBootstrap () throws IOException {
221+ return generateBootstrap (
222+ queryZoneMetadata (METADATA_URL_ZONE ),
223+ queryIpv6SupportMetadata (METADATA_URL_SUPPORT_IPV6 ));
224+ }
225+
226+ private static ImmutableMap <String , ?> generateBootstrap (String zone , boolean supportIpv6 ) {
207227 ImmutableMap .Builder <String , Object > nodeBuilder = ImmutableMap .builder ();
208- nodeBuilder .put ("id" , "C2P-" + (rand . nextInt () & Integer .MAX_VALUE ));
228+ nodeBuilder .put ("id" , "C2P-" + (c2pId & Integer .MAX_VALUE ));
209229 if (!zone .isEmpty ()) {
210230 nodeBuilder .put ("locality" , ImmutableMap .of ("zone" , zone ));
211231 }
@@ -250,12 +270,15 @@ public void shutdown() {
250270 if (delegate != null ) {
251271 delegate .shutdown ();
252272 }
273+ if (xdsClient != null ) {
274+ xdsClient = xdsClientPool .returnObject (xdsClient );
275+ }
253276 if (executor != null && usingExecutorResource ) {
254277 executor = SharedResourceHolder .release (executorResource , executor );
255278 }
256279 }
257280
258- private String queryZoneMetadata (String url ) throws IOException {
281+ private static String queryZoneMetadata (String url ) throws IOException {
259282 HttpURLConnection con = null ;
260283 String respBody ;
261284 try {
@@ -275,7 +298,7 @@ private String queryZoneMetadata(String url) throws IOException {
275298 return index == -1 ? "" : respBody .substring (index + 1 );
276299 }
277300
278- private boolean queryIpv6SupportMetadata (String url ) throws IOException {
301+ private static boolean queryIpv6SupportMetadata (String url ) throws IOException {
279302 HttpURLConnection con = null ;
280303 try {
281304 con = httpConnectionProvider .createConnection (url );
@@ -294,8 +317,17 @@ private boolean queryIpv6SupportMetadata(String url) throws IOException {
294317 }
295318
296319 @ VisibleForTesting
297- void setHttpConnectionProvider (HttpConnectionProvider httpConnectionProvider ) {
298- this .httpConnectionProvider = httpConnectionProvider ;
320+ static void setHttpConnectionProvider (HttpConnectionProvider httpConnectionProvider ) {
321+ if (httpConnectionProvider == null ) {
322+ GoogleCloudToProdNameResolver .httpConnectionProvider = HttpConnectionFactory .INSTANCE ;
323+ } else {
324+ GoogleCloudToProdNameResolver .httpConnectionProvider = httpConnectionProvider ;
325+ }
326+ }
327+
328+ @ VisibleForTesting
329+ static void setC2pId (int c2pId ) {
330+ GoogleCloudToProdNameResolver .c2pId = c2pId ;
299331 }
300332
301333 private static URI overrideUriScheme (URI uri , String scheme ) {
@@ -335,8 +367,4 @@ public HttpURLConnection createConnection(String url) throws IOException {
335367 interface HttpConnectionProvider {
336368 HttpURLConnection createConnection (String url ) throws IOException ;
337369 }
338-
339- public interface BootstrapSetter {
340- void setBootstrap (Map <String , ?> bootstrap );
341- }
342370}
0 commit comments