2020
2121import com .google .common .annotations .VisibleForTesting ;
2222import com .google .common .base .Preconditions ;
23+ import com .google .common .base .Splitter ;
2324import com .google .common .collect .ImmutableList ;
2425import com .google .common .collect .ImmutableMap ;
2526import com .google .common .io .CharStreams ;
@@ -76,23 +77,43 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
7677 private static final String serverUriOverride =
7778 System .getenv ("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI" );
7879
79- @ GuardedBy ("GoogleCloudToProdNameResolver.class" )
80+ private static final Object BOOTSTRAP_LOCK = new Object ();
81+ private static final Object FORCE_XDS_BOOTSTRAP_LOCK = new Object ();
82+
83+ @ GuardedBy ("BOOTSTRAP_LOCK" )
8084 private static BootstrapInfo bootstrapInfo ;
85+ @ GuardedBy ("FORCE_XDS_BOOTSTRAP_LOCK" )
86+ private static BootstrapInfo forceXdsBootstrapInfo ;
8187 private static HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory .INSTANCE ;
8288 private static int c2pId = new Random ().nextInt ();
8389
84- private static synchronized BootstrapInfo getBootstrapInfo ()
90+ private static BootstrapInfo getBootstrapInfo (boolean isForcedXds )
8591 throws XdsInitializationException , IOException {
86- if (bootstrapInfo != null ) {
87- return bootstrapInfo ;
88- }
89- BootstrapInfo bootstrapInfoTmp =
90- InternalGrpcBootstrapperImpl .parseBootstrap (generateBootstrap ());
91- // Avoid setting global when testing
92- if (httpConnectionProvider == HttpConnectionFactory .INSTANCE ) {
93- bootstrapInfo = bootstrapInfoTmp ;
92+ if (isForcedXds ) {
93+ synchronized (FORCE_XDS_BOOTSTRAP_LOCK ) {
94+ if (forceXdsBootstrapInfo != null ) {
95+ return forceXdsBootstrapInfo ;
96+ }
97+ BootstrapInfo newInfo = InternalGrpcBootstrapperImpl .parseBootstrap (
98+ generateBootstrap ("" , true , true ));
99+ if (httpConnectionProvider == HttpConnectionFactory .INSTANCE ) {
100+ forceXdsBootstrapInfo = newInfo ;
101+ }
102+ return newInfo ;
103+ }
104+ } else {
105+ synchronized (BOOTSTRAP_LOCK ) {
106+ if (bootstrapInfo != null ) {
107+ return bootstrapInfo ;
108+ }
109+ BootstrapInfo newInfo = InternalGrpcBootstrapperImpl .parseBootstrap (
110+ generateBootstrap ());
111+ if (httpConnectionProvider == HttpConnectionFactory .INSTANCE ) {
112+ bootstrapInfo = newInfo ;
113+ }
114+ return newInfo ;
115+ }
94116 }
95- return bootstrapInfoTmp ;
96117 }
97118
98119 private final String authority ;
@@ -102,7 +123,8 @@ private static synchronized BootstrapInfo getBootstrapInfo()
102123 private final MetricRecorder metricRecorder ;
103124 private final NameResolver delegate ;
104125 private final boolean usingExecutorResource ;
105- private final String schemeOverride = !isOnGcp ? "dns" : "xds" ;
126+ private final boolean forceXds ;
127+ private final String schemeOverride ;
106128 private XdsClientResult xdsClientPool ;
107129 private XdsClient xdsClient ;
108130 private Executor executor ;
@@ -121,6 +143,11 @@ private static synchronized BootstrapInfo getBootstrapInfo()
121143 GoogleCloudToProdNameResolver (URI targetUri , Args args , Resource <Executor > executorResource ,
122144 NameResolver .Factory nameResolverFactory ) {
123145 this .executorResource = checkNotNull (executorResource , "executorResource" );
146+ String query = targetUri .getRawQuery ();
147+ this .forceXds = checkForceXds (query );
148+ this .schemeOverride = (forceXds || isOnGcp ) ? "xds" : "dns" ;
149+ String newQuery = stripForceXds (query );
150+
124151 String targetPath = checkNotNull (checkNotNull (targetUri , "targetUri" ).getPath (), "targetPath" );
125152 Preconditions .checkArgument (
126153 targetPath .startsWith ("/" ),
@@ -129,9 +156,31 @@ private static synchronized BootstrapInfo getBootstrapInfo()
129156 targetUri );
130157 authority = GrpcUtil .checkAuthority (targetPath .substring (1 ));
131158 syncContext = checkNotNull (args , "args" ).getSynchronizationContext ();
132- targetUri = overrideUriScheme (targetUri , schemeOverride );
159+
160+ String rawAuthority = schemeOverride .equals ("xds" )
161+ ? C2P_AUTHORITY
162+ : targetUri .getRawAuthority ();
163+ String rawPath = targetUri .getRawPath ();
164+ String rawFragment = targetUri .getRawFragment ();
165+ try {
166+ StringBuilder uriStr = new StringBuilder ();
167+ uriStr .append (schemeOverride ).append (":" );
168+ if (rawAuthority != null ) {
169+ uriStr .append ("//" ).append (rawAuthority );
170+ }
171+ uriStr .append (rawPath );
172+ if (newQuery != null ) {
173+ uriStr .append ("?" ).append (newQuery );
174+ }
175+ if (rawFragment != null ) {
176+ uriStr .append ("#" ).append (rawFragment );
177+ }
178+ targetUri = new URI (uriStr .toString ());
179+ } catch (URISyntaxException e ) {
180+ throw new IllegalArgumentException ("Invalid URI" , e );
181+ }
182+
133183 if (schemeOverride .equals ("xds" )) {
134- targetUri = overrideUriAuthority (targetUri , C2P_AUTHORITY );
135184 args = args .toBuilder ()
136185 .setArg (XdsNameResolverProvider .XDS_CLIENT_SUPPLIER , () -> xdsClient )
137186 .build ();
@@ -155,6 +204,11 @@ private static synchronized BootstrapInfo getBootstrapInfo()
155204 Resource <Executor > executorResource ,
156205 NameResolver .Factory nameResolverFactory ) {
157206 this .executorResource = checkNotNull (executorResource , "executorResource" );
207+ String query = targetUri .getRawQuery ();
208+ this .forceXds = checkForceXds (query );
209+ this .schemeOverride = (forceXds || isOnGcp ) ? "xds" : "dns" ;
210+ String newQuery = stripForceXds (query );
211+
158212 Preconditions .checkArgument (
159213 targetUri .isPathAbsolute (),
160214 "the path component of the target (%s) must start with '/'" ,
@@ -167,6 +221,12 @@ private static synchronized BootstrapInfo getBootstrapInfo()
167221 authority = GrpcUtil .checkAuthority (pathSegments .get (0 ));
168222 syncContext = checkNotNull (args , "args" ).getSynchronizationContext ();
169223 Uri .Builder modifiedTargetBuilder = targetUri .toBuilder ().setScheme (schemeOverride );
224+ if (newQuery != null ) {
225+ modifiedTargetBuilder .setRawQuery (newQuery );
226+ } else {
227+ modifiedTargetBuilder .setQuery (null );
228+ }
229+
170230 if (schemeOverride .equals ("xds" )) {
171231 modifiedTargetBuilder .setRawAuthority (C2P_AUTHORITY );
172232 args =
@@ -226,7 +286,7 @@ class Resolve implements Runnable {
226286 public void run () {
227287 BootstrapInfo bootstrapInfo = null ;
228288 try {
229- bootstrapInfo = getBootstrapInfo ();
289+ bootstrapInfo = getBootstrapInfo (forceXds );
230290 } catch (IOException e ) {
231291 listener .onError (
232292 Status .INTERNAL .withDescription ("Unable to get metadata" ).withCause (e ));
@@ -263,16 +323,18 @@ public void run() {
263323 static ImmutableMap <String , ?> generateBootstrap () throws IOException {
264324 return generateBootstrap (
265325 queryZoneMetadata (METADATA_URL_ZONE ),
266- queryIpv6SupportMetadata (METADATA_URL_SUPPORT_IPV6 ));
326+ queryIpv6SupportMetadata (METADATA_URL_SUPPORT_IPV6 ), false );
267327 }
268328
269- private static ImmutableMap <String , ?> generateBootstrap (String zone , boolean supportIpv6 ) {
329+ static ImmutableMap <String , ?> generateBootstrap (
330+ String zone , boolean supportIpv6 , boolean isForcedXds ) {
270331 ImmutableMap .Builder <String , Object > nodeBuilder = ImmutableMap .builder ();
271- nodeBuilder .put ("id" , "C2P-" + (c2pId & Integer .MAX_VALUE ));
272- if (!zone .isEmpty ()) {
332+ String nodeIdPrefix = isOnGcp ? "C2P-" : "C2P-non-gcp-" ;
333+ nodeBuilder .put ("id" , nodeIdPrefix + (c2pId & Integer .MAX_VALUE ));
334+ if (!isForcedXds && !zone .isEmpty ()) {
273335 nodeBuilder .put ("locality" , ImmutableMap .of ("zone" , zone ));
274336 }
275- if (supportIpv6 ) {
337+ if (isForcedXds || supportIpv6 ) {
276338 nodeBuilder .put ("metadata" ,
277339 ImmutableMap .of ("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE" , true ));
278340 }
@@ -373,24 +435,32 @@ static void setC2pId(int c2pId) {
373435 GoogleCloudToProdNameResolver .c2pId = c2pId ;
374436 }
375437
376- private static URI overrideUriScheme (URI uri , String scheme ) {
377- URI res ;
378- try {
379- res = new URI (scheme , uri .getAuthority (), uri .getPath (), uri .getQuery (), uri .getFragment ());
380- } catch (URISyntaxException ex ) {
381- throw new IllegalArgumentException ("Invalid scheme: " + scheme , ex );
438+ private static boolean checkForceXds (String query ) {
439+ if (query == null ) {
440+ return false ;
441+ }
442+ for (String part : Splitter .on ('&' ).split (query )) {
443+ if (part .equals ("force-xds" ) || part .startsWith ("force-xds=" )) {
444+ return true ;
445+ }
382446 }
383- return res ;
447+ return false ;
384448 }
385449
386- private static URI overrideUriAuthority (URI uri , String authority ) {
387- URI res ;
388- try {
389- res = new URI (uri .getScheme (), authority , uri .getPath (), uri .getQuery (), uri .getFragment ());
390- } catch (URISyntaxException ex ) {
391- throw new IllegalArgumentException ("Invalid authority: " + authority , ex );
450+ private static String stripForceXds (String query ) {
451+ if (query == null ) {
452+ return null ;
453+ }
454+ StringBuilder sb = new StringBuilder ();
455+ for (String part : Splitter .on ('&' ).split (query )) {
456+ if (!part .equals ("force-xds" ) && !part .startsWith ("force-xds=" )) {
457+ if (sb .length () > 0 ) {
458+ sb .append ("&" );
459+ }
460+ sb .append (part );
461+ }
392462 }
393- return res ;
463+ return sb . length () == 0 ? null : sb . toString () ;
394464 }
395465
396466 private enum HttpConnectionFactory implements HttpConnectionProvider {
0 commit comments