• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 The gRPC Authors
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 io.grpc.googleapis;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.mockito.Mockito.mock;
21 import static org.mockito.Mockito.verify;
22 import static org.mockito.Mockito.when;
23 
24 import com.google.common.collect.ImmutableList;
25 import com.google.common.collect.ImmutableMap;
26 import com.google.common.collect.Iterables;
27 import io.grpc.ChannelLogger;
28 import io.grpc.NameResolver;
29 import io.grpc.NameResolver.Args;
30 import io.grpc.NameResolver.ServiceConfigParser;
31 import io.grpc.NameResolverProvider;
32 import io.grpc.NameResolverRegistry;
33 import io.grpc.Status;
34 import io.grpc.Status.Code;
35 import io.grpc.SynchronizationContext;
36 import io.grpc.googleapis.GoogleCloudToProdNameResolver.HttpConnectionProvider;
37 import io.grpc.internal.FakeClock;
38 import io.grpc.internal.GrpcUtil;
39 import io.grpc.internal.SharedResourceHolder.Resource;
40 import java.io.ByteArrayInputStream;
41 import java.io.IOException;
42 import java.net.HttpURLConnection;
43 import java.net.URI;
44 import java.nio.charset.StandardCharsets;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Random;
49 import java.util.concurrent.Executor;
50 import java.util.concurrent.atomic.AtomicReference;
51 import org.junit.After;
52 import org.junit.Before;
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.junit.runners.JUnit4;
57 import org.mockito.ArgumentCaptor;
58 import org.mockito.Captor;
59 import org.mockito.Mock;
60 import org.mockito.junit.MockitoJUnit;
61 import org.mockito.junit.MockitoRule;
62 
63 @RunWith(JUnit4.class)
64 public class GoogleCloudToProdNameResolverTest {
65 
66   @Rule
67   public final MockitoRule mocks = MockitoJUnit.rule();
68 
69   private static final URI TARGET_URI = URI.create("google-c2p:///googleapis.com");
70   private static final String ZONE = "us-central1-a";
71   private static final int DEFAULT_PORT = 887;
72 
73   private final SynchronizationContext syncContext = new SynchronizationContext(
74       new Thread.UncaughtExceptionHandler() {
75         @Override
76         public void uncaughtException(Thread t, Throwable e) {
77           throw new AssertionError(e);
78         }
79       });
80   private final NameResolver.Args args = NameResolver.Args.newBuilder()
81       .setDefaultPort(DEFAULT_PORT)
82       .setProxyDetector(GrpcUtil.DEFAULT_PROXY_DETECTOR)
83       .setSynchronizationContext(syncContext)
84       .setServiceConfigParser(mock(ServiceConfigParser.class))
85       .setChannelLogger(mock(ChannelLogger.class))
86       .build();
87   private final FakeClock fakeExecutor = new FakeClock();
88   private final FakeBootstrapSetter fakeBootstrapSetter = new FakeBootstrapSetter();
89   private final Resource<Executor> fakeExecutorResource = new Resource<Executor>() {
90     @Override
91     public Executor create() {
92       return fakeExecutor.getScheduledExecutorService();
93     }
94 
95     @Override
96     public void close(Executor instance) {}
97   };
98 
99   private final NameResolverRegistry nsRegistry = new NameResolverRegistry();
100   private final Map<String, NameResolver> delegatedResolver = new HashMap<>();
101 
102   @Mock
103   private NameResolver.Listener2 mockListener;
104   private Random random = new Random(1);
105   @Captor
106   private ArgumentCaptor<Status> errorCaptor;
107   private boolean originalIsOnGcp;
108   private boolean originalXdsBootstrapProvided;
109   private GoogleCloudToProdNameResolver resolver;
110 
111   @Before
setUp()112   public void setUp() {
113     nsRegistry.register(new FakeNsProvider("dns"));
114     nsRegistry.register(new FakeNsProvider("xds"));
115     originalIsOnGcp = GoogleCloudToProdNameResolver.isOnGcp;
116     originalXdsBootstrapProvided = GoogleCloudToProdNameResolver.xdsBootstrapProvided;
117   }
118 
119   @After
tearDown()120   public void tearDown() {
121     GoogleCloudToProdNameResolver.isOnGcp = originalIsOnGcp;
122     GoogleCloudToProdNameResolver.xdsBootstrapProvided = originalXdsBootstrapProvided;
123     resolver.shutdown();
124     verify(Iterables.getOnlyElement(delegatedResolver.values())).shutdown();
125   }
126 
createResolver()127   private void createResolver() {
128     HttpConnectionProvider httpConnections = new HttpConnectionProvider() {
129       @Override
130       public HttpURLConnection createConnection(String url) throws IOException {
131         HttpURLConnection con = mock(HttpURLConnection.class);
132         when(con.getResponseCode()).thenReturn(200);
133         if (url.equals(GoogleCloudToProdNameResolver.METADATA_URL_ZONE)) {
134           when(con.getInputStream()).thenReturn(
135               new ByteArrayInputStream(("/" + ZONE).getBytes(StandardCharsets.UTF_8)));
136           return con;
137         } else if (url.equals(GoogleCloudToProdNameResolver.METADATA_URL_SUPPORT_IPV6)) {
138           return con;
139         }
140         throw new AssertionError("Unknown http query");
141       }
142     };
143     resolver = new GoogleCloudToProdNameResolver(
144         TARGET_URI, args, fakeExecutorResource, random, fakeBootstrapSetter,
145         nsRegistry.asFactory());
146     resolver.setHttpConnectionProvider(httpConnections);
147   }
148 
149   @Test
notOnGcpDelegateToDns()150   public void notOnGcpDelegateToDns() {
151     GoogleCloudToProdNameResolver.isOnGcp = false;
152     createResolver();
153     resolver.start(mockListener);
154     assertThat(delegatedResolver.keySet()).containsExactly("dns");
155     verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
156   }
157 
158   @Test
hasProvidedBootstrapDelegateToDns()159   public void hasProvidedBootstrapDelegateToDns() {
160     GoogleCloudToProdNameResolver.isOnGcp = true;
161     GoogleCloudToProdNameResolver.xdsBootstrapProvided = true;
162     GoogleCloudToProdNameResolver.enableFederation = false;
163     createResolver();
164     resolver.start(mockListener);
165     assertThat(delegatedResolver.keySet()).containsExactly("dns");
166     verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
167   }
168 
169   @SuppressWarnings("unchecked")
170   @Test
onGcpAndNoProvidedBootstrapDelegateToXds()171   public void onGcpAndNoProvidedBootstrapDelegateToXds() {
172     GoogleCloudToProdNameResolver.isOnGcp = true;
173     GoogleCloudToProdNameResolver.xdsBootstrapProvided = false;
174     createResolver();
175     resolver.start(mockListener);
176     fakeExecutor.runDueTasks();
177     assertThat(delegatedResolver.keySet()).containsExactly("xds");
178     verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
179     Map<String, ?> bootstrap = fakeBootstrapSetter.bootstrapRef.get();
180     Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
181     assertThat(node).containsExactly(
182         "id", "C2P-991614323",
183         "locality", ImmutableMap.of("zone", ZONE),
184         "metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true));
185     Map<String, ?> server = Iterables.getOnlyElement(
186         (List<Map<String, ?>>) bootstrap.get("xds_servers"));
187     assertThat(server).containsExactly(
188         "server_uri", "directpath-pa.googleapis.com",
189         "channel_creds", ImmutableList.of(ImmutableMap.of("type", "google_default")),
190         "server_features", ImmutableList.of("xds_v3", "ignore_resource_deletion"));
191     Map<String, ?> authorities = (Map<String, ?>) bootstrap.get("authorities");
192     assertThat(authorities).containsExactly(
193         "traffic-director-c2p.xds.googleapis.com",
194         ImmutableMap.of("xds_servers", ImmutableList.of(server)));
195   }
196 
197   @SuppressWarnings("unchecked")
198   @Test
onGcpAndNoProvidedBootstrapAndFederationEnabledDelegateToXds()199   public void onGcpAndNoProvidedBootstrapAndFederationEnabledDelegateToXds() {
200     GoogleCloudToProdNameResolver.isOnGcp = true;
201     GoogleCloudToProdNameResolver.xdsBootstrapProvided = false;
202     GoogleCloudToProdNameResolver.enableFederation = true;
203     createResolver();
204     resolver.start(mockListener);
205     fakeExecutor.runDueTasks();
206     assertThat(delegatedResolver.keySet()).containsExactly("xds");
207     verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
208     // check bootstrap
209     Map<String, ?> bootstrap = fakeBootstrapSetter.bootstrapRef.get();
210     Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
211     assertThat(node).containsExactly(
212         "id", "C2P-991614323",
213         "locality", ImmutableMap.of("zone", ZONE),
214         "metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true));
215     Map<String, ?> server = Iterables.getOnlyElement(
216         (List<Map<String, ?>>) bootstrap.get("xds_servers"));
217     assertThat(server).containsExactly(
218         "server_uri", "directpath-pa.googleapis.com",
219         "channel_creds", ImmutableList.of(ImmutableMap.of("type", "google_default")),
220         "server_features", ImmutableList.of("xds_v3", "ignore_resource_deletion"));
221     Map<String, ?> authorities = (Map<String, ?>) bootstrap.get("authorities");
222     assertThat(authorities).containsExactly(
223         "traffic-director-c2p.xds.googleapis.com",
224         ImmutableMap.of("xds_servers", ImmutableList.of(server)));
225   }
226 
227   @SuppressWarnings("unchecked")
228   @Test
onGcpAndProvidedBootstrapAndFederationEnabledDontDelegateToXds()229   public void onGcpAndProvidedBootstrapAndFederationEnabledDontDelegateToXds() {
230     GoogleCloudToProdNameResolver.isOnGcp = true;
231     GoogleCloudToProdNameResolver.xdsBootstrapProvided = true;
232     GoogleCloudToProdNameResolver.enableFederation = true;
233     createResolver();
234     resolver.start(mockListener);
235     fakeExecutor.runDueTasks();
236     assertThat(delegatedResolver.keySet()).containsExactly("xds");
237     verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
238     // Bootstrapper should not have been set, since there was no user provided config.
239     assertThat(fakeBootstrapSetter.bootstrapRef.get()).isNull();
240   }
241 
242   @Test
failToQueryMetadata()243   public void failToQueryMetadata() {
244     GoogleCloudToProdNameResolver.isOnGcp = true;
245     GoogleCloudToProdNameResolver.xdsBootstrapProvided = false;
246     createResolver();
247     HttpConnectionProvider httpConnections = new HttpConnectionProvider() {
248       @Override
249       public HttpURLConnection createConnection(String url) throws IOException {
250         HttpURLConnection con = mock(HttpURLConnection.class);
251         when(con.getResponseCode()).thenThrow(new IOException("unknown error"));
252         return con;
253       }
254     };
255     resolver.setHttpConnectionProvider(httpConnections);
256     resolver.start(mockListener);
257     fakeExecutor.runDueTasks();
258     verify(mockListener).onError(errorCaptor.capture());
259     assertThat(errorCaptor.getValue().getCode()).isEqualTo(Code.INTERNAL);
260     assertThat(errorCaptor.getValue().getDescription()).isEqualTo("Unable to get metadata");
261   }
262 
263   private final class FakeNsProvider extends NameResolverProvider {
264     private final String scheme;
265 
FakeNsProvider(String scheme)266     private FakeNsProvider(String scheme) {
267       this.scheme = scheme;
268     }
269 
270     @Override
newNameResolver(URI targetUri, Args args)271     public NameResolver newNameResolver(URI targetUri, Args args) {
272       if (scheme.equals(targetUri.getScheme())) {
273         NameResolver resolver = mock(NameResolver.class);
274         delegatedResolver.put(scheme, resolver);
275         return resolver;
276       }
277       return null;
278     }
279 
280     @Override
isAvailable()281     protected boolean isAvailable() {
282       return true;
283     }
284 
285     @Override
priority()286     protected int priority() {
287       return 5;
288     }
289 
290     @Override
getDefaultScheme()291     public String getDefaultScheme() {
292       return scheme;
293     }
294   }
295 
296   private static final class FakeBootstrapSetter
297       implements GoogleCloudToProdNameResolver.BootstrapSetter {
298     private final AtomicReference<Map<String, ?>> bootstrapRef = new AtomicReference<>();
299 
300     @Override
setBootstrap(Map<String, ?> bootstrap)301     public void setBootstrap(Map<String, ?> bootstrap) {
302       bootstrapRef.set(bootstrap);
303     }
304   }
305 }
306