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,54 @@ 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 ;
95107 private Listener2 listener ;
96108 private boolean succeeded ;
97109 private boolean resolving ;
98110 private boolean shutdown ;
99111
100- GoogleCloudToProdNameResolver (URI targetUri , Args args , Resource <Executor > executorResource ,
101- BootstrapSetter bootstrapSetter ) {
102- this (targetUri , args , executorResource , new Random (), bootstrapSetter ,
112+ GoogleCloudToProdNameResolver (URI targetUri , Args args , Resource <Executor > executorResource ) {
113+ this (targetUri , args , executorResource ,
103114 NameResolverRegistry .getDefaultRegistry ().asFactory ());
104115 }
105116
106117 @ VisibleForTesting
107118 GoogleCloudToProdNameResolver (URI targetUri , Args args , Resource <Executor > executorResource ,
108- Random rand , BootstrapSetter bootstrapSetter , NameResolver .Factory nameResolverFactory ) {
119+ NameResolver .Factory nameResolverFactory ) {
109120 this .executorResource = checkNotNull (executorResource , "executorResource" );
110- this .bootstrapSetter = checkNotNull (bootstrapSetter , "bootstrapSetter" );
111- this .rand = checkNotNull (rand , "rand" );
112121 String targetPath = checkNotNull (checkNotNull (targetUri , "targetUri" ).getPath (), "targetPath" );
113122 Preconditions .checkArgument (
114123 targetPath .startsWith ("/" ),
@@ -118,9 +127,14 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
118127 authority = GrpcUtil .checkAuthority (targetPath .substring (1 ));
119128 syncContext = checkNotNull (args , "args" ).getSynchronizationContext ();
120129 targetUri = overrideUriScheme (targetUri , schemeOverride );
121- if (schemeOverride .equals ("xds" ) && enableFederation ) {
130+ if (schemeOverride .equals ("xds" )) {
122131 targetUri = overrideUriAuthority (targetUri , C2P_AUTHORITY );
132+ args = args .toBuilder ()
133+ .setArg (XdsNameResolverProvider .XDS_CLIENT_SUPPLIER , () -> xdsClient )
134+ .build ();
123135 }
136+ target = targetUri .toString ();
137+ metricRecorder = args .getMetricRecorder ();
124138 delegate = checkNotNull (nameResolverFactory , "nameResolverFactory" ).newNameResolver (
125139 targetUri , args );
126140 executor = args .getOffloadExecutor ();
@@ -150,7 +164,7 @@ private void resolve() {
150164
151165 resolving = true ;
152166 if (logger .isLoggable (Level .FINE )) {
153- logger .fine ( "resolve with schemaOverride = " + schemeOverride );
167+ logger .log ( Level . FINE , "start with schemaOverride = {0}" , schemeOverride );
154168 }
155169
156170 if (schemeOverride .equals ("dns" )) {
@@ -168,28 +182,28 @@ private void resolve() {
168182 class Resolve implements Runnable {
169183 @ Override
170184 public void run () {
171- ImmutableMap < String , ?> rawBootstrap = null ;
185+ BootstrapInfo bootstrapInfo = null ;
172186 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- }
187+ bootstrapInfo = getBootstrapInfo ();
181188 } catch (IOException e ) {
182189 listener .onError (
183190 Status .INTERNAL .withDescription ("Unable to get metadata" ).withCause (e ));
191+ } catch (XdsInitializationException e ) {
192+ listener .onError (
193+ Status .INTERNAL .withDescription ("Unable to create c2p bootstrap" ).withCause (e ));
194+ } catch (Throwable t ) {
195+ listener .onError (
196+ Status .INTERNAL .withDescription ("Unexpected error creating c2p bootstrap" )
197+ .withCause (t ));
184198 } finally {
185- final ImmutableMap < String , ?> finalRawBootstrap = rawBootstrap ;
199+ final BootstrapInfo finalBootstrapInfo = bootstrapInfo ;
186200 syncContext .execute (new Runnable () {
187201 @ Override
188202 public void run () {
189- if (!shutdown ) {
190- if ( finalRawBootstrap != null ) {
191- bootstrapSetter . setBootstrap ( finalRawBootstrap );
192- }
203+ if (!shutdown && finalBootstrapInfo != null ) {
204+ xdsClientPool = InternalSharedXdsClientPoolProvider . getOrCreate (
205+ target , finalBootstrapInfo , metricRecorder , null );
206+ xdsClient = xdsClientPool . getObject ();
193207 delegate .start (listener );
194208 succeeded = true ;
195209 }
@@ -203,9 +217,16 @@ public void run() {
203217 executor .execute (new Resolve ());
204218 }
205219
206- private ImmutableMap <String , ?> generateBootstrap (String zone , boolean supportIpv6 ) {
220+ @ VisibleForTesting
221+ static ImmutableMap <String , ?> generateBootstrap () throws IOException {
222+ return generateBootstrap (
223+ queryZoneMetadata (METADATA_URL_ZONE ),
224+ queryIpv6SupportMetadata (METADATA_URL_SUPPORT_IPV6 ));
225+ }
226+
227+ private static ImmutableMap <String , ?> generateBootstrap (String zone , boolean supportIpv6 ) {
207228 ImmutableMap .Builder <String , Object > nodeBuilder = ImmutableMap .builder ();
208- nodeBuilder .put ("id" , "C2P-" + (rand . nextInt () & Integer .MAX_VALUE ));
229+ nodeBuilder .put ("id" , "C2P-" + (c2pId & Integer .MAX_VALUE ));
209230 if (!zone .isEmpty ()) {
210231 nodeBuilder .put ("locality" , ImmutableMap .of ("zone" , zone ));
211232 }
@@ -250,12 +271,15 @@ public void shutdown() {
250271 if (delegate != null ) {
251272 delegate .shutdown ();
252273 }
274+ if (xdsClient != null ) {
275+ xdsClient = xdsClientPool .returnObject (xdsClient );
276+ }
253277 if (executor != null && usingExecutorResource ) {
254278 executor = SharedResourceHolder .release (executorResource , executor );
255279 }
256280 }
257281
258- private String queryZoneMetadata (String url ) throws IOException {
282+ private static String queryZoneMetadata (String url ) throws IOException {
259283 HttpURLConnection con = null ;
260284 String respBody ;
261285 try {
@@ -275,7 +299,7 @@ private String queryZoneMetadata(String url) throws IOException {
275299 return index == -1 ? "" : respBody .substring (index + 1 );
276300 }
277301
278- private boolean queryIpv6SupportMetadata (String url ) throws IOException {
302+ private static boolean queryIpv6SupportMetadata (String url ) throws IOException {
279303 HttpURLConnection con = null ;
280304 try {
281305 con = httpConnectionProvider .createConnection (url );
@@ -294,8 +318,17 @@ private boolean queryIpv6SupportMetadata(String url) throws IOException {
294318 }
295319
296320 @ VisibleForTesting
297- void setHttpConnectionProvider (HttpConnectionProvider httpConnectionProvider ) {
298- this .httpConnectionProvider = httpConnectionProvider ;
321+ static void setHttpConnectionProvider (HttpConnectionProvider httpConnectionProvider ) {
322+ if (httpConnectionProvider == null ) {
323+ GoogleCloudToProdNameResolver .httpConnectionProvider = HttpConnectionFactory .INSTANCE ;
324+ } else {
325+ GoogleCloudToProdNameResolver .httpConnectionProvider = httpConnectionProvider ;
326+ }
327+ }
328+
329+ @ VisibleForTesting
330+ static void setC2pId (int c2pId ) {
331+ GoogleCloudToProdNameResolver .c2pId = c2pId ;
299332 }
300333
301334 private static URI overrideUriScheme (URI uri , String scheme ) {
@@ -335,8 +368,4 @@ public HttpURLConnection createConnection(String url) throws IOException {
335368 interface HttpConnectionProvider {
336369 HttpURLConnection createConnection (String url ) throws IOException ;
337370 }
338-
339- public interface BootstrapSetter {
340- void setBootstrap (Map <String , ?> bootstrap );
341- }
342371}
0 commit comments