3030import static org .mockito .Mockito .verifyNoMoreInteractions ;
3131import static org .mockito .Mockito .when ;
3232
33+ import com .google .common .base .Stopwatch ;
3334import com .google .common .collect .Iterables ;
3435import com .google .common .net .InetAddresses ;
36+ import com .google .common .testing .FakeTicker ;
3537import io .grpc .Attributes ;
3638import io .grpc .EquivalentAddressGroup ;
3739import io .grpc .NameResolver ;
5658import java .util .Map ;
5759import java .util .Random ;
5860import java .util .concurrent .ExecutorService ;
61+ import java .util .concurrent .TimeUnit ;
62+ import javax .annotation .Nullable ;
5963import org .junit .After ;
6064import org .junit .Before ;
6165import org .junit .Rule ;
@@ -107,28 +111,45 @@ public void close(ExecutorService instance) {
107111 private NameResolver .Listener mockListener ;
108112 @ Captor
109113 private ArgumentCaptor <List <EquivalentAddressGroup >> resultCaptor ;
114+ @ Nullable
115+ private String networkaddressCacheTtlPropertyValue ;
110116
111117 private DnsNameResolver newResolver (String name , int port ) {
112- return newResolver (name , port , GrpcUtil .NOOP_PROXY_DETECTOR );
118+ return newResolver (name , port , GrpcUtil .NOOP_PROXY_DETECTOR , Stopwatch . createUnstarted () );
113119 }
114120
115121 private DnsNameResolver newResolver (
116122 String name ,
117123 int port ,
118- ProxyDetector proxyDetector ) {
124+ ProxyDetector proxyDetector ,
125+ Stopwatch stopwatch ) {
119126 DnsNameResolver dnsResolver = new DnsNameResolver (
120127 null ,
121128 name ,
122129 Attributes .newBuilder ().set (NameResolver .Factory .PARAMS_DEFAULT_PORT , port ).build (),
123130 fakeExecutorResource ,
124- proxyDetector );
131+ proxyDetector ,
132+ stopwatch );
125133 return dnsResolver ;
126134 }
127135
128136 @ Before
129137 public void setUp () {
130138 MockitoAnnotations .initMocks (this );
131139 DnsNameResolver .enableJndi = true ;
140+ networkaddressCacheTtlPropertyValue =
141+ System .getProperty (DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY );
142+ }
143+
144+ @ After
145+ public void restoreSystemProperty () {
146+ if (networkaddressCacheTtlPropertyValue == null ) {
147+ System .clearProperty (DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY );
148+ } else {
149+ System .setProperty (
150+ DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY ,
151+ networkaddressCacheTtlPropertyValue );
152+ }
132153 }
133154
134155 @ After
@@ -180,7 +201,8 @@ public void invalidDnsName_containsUnderscore() {
180201 }
181202
182203 @ Test
183- public void resolve () throws Exception {
204+ public void resolve_neverCache () throws Exception {
205+ System .setProperty (DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY , "0" );
184206 final List <InetAddress > answer1 = createAddressList (2 );
185207 final List <InetAddress > answer2 = createAddressList (1 );
186208 String name = "foo.googleapis.com" ;
@@ -219,7 +241,7 @@ public List<InetAddress> resolveAddress(String host) throws Exception {
219241 }
220242 });
221243
222- new DnsNameResolver .Resolve (nrf ).resolveInternal (mockListener );
244+ new DnsNameResolver .Resolve (nrf , Stopwatch . createUnstarted () ).resolveInternal (mockListener );
223245
224246 ArgumentCaptor <Status > ac = ArgumentCaptor .forClass (Status .class );
225247 verify (mockListener ).onError (ac .capture ());
@@ -228,6 +250,156 @@ public List<InetAddress> resolveAddress(String host) throws Exception {
228250 assertThat (ac .getValue ().getDescription ()).contains ("No DNS backend or balancer addresses" );
229251 }
230252
253+ @ Test
254+ public void resolve_cacheForever () throws Exception {
255+ System .setProperty (DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY , "-1" );
256+ final List <InetAddress > answer1 = createAddressList (2 );
257+ String name = "foo.googleapis.com" ;
258+ FakeTicker fakeTicker = new FakeTicker ();
259+
260+ DnsNameResolver resolver =
261+ newResolver (name , 81 , GrpcUtil .NOOP_PROXY_DETECTOR , Stopwatch .createUnstarted (fakeTicker ));
262+ AddressResolver mockResolver = mock (AddressResolver .class );
263+ when (mockResolver .resolveAddress (Matchers .anyString ()))
264+ .thenReturn (answer1 )
265+ .thenThrow (new AssertionError ("should not called twice" ));
266+ resolver .setAddressResolver (mockResolver );
267+
268+ resolver .start (mockListener );
269+ assertEquals (1 , fakeExecutor .runDueTasks ());
270+ verify (mockListener ).onAddresses (resultCaptor .capture (), any (Attributes .class ));
271+ assertAnswerMatches (answer1 , 81 , resultCaptor .getValue ());
272+ assertEquals (0 , fakeClock .numPendingTasks ());
273+
274+ fakeTicker .advance (1 , TimeUnit .DAYS );
275+ resolver .refresh ();
276+ assertEquals (1 , fakeExecutor .runDueTasks ());
277+ verifyNoMoreInteractions (mockListener );
278+ assertAnswerMatches (answer1 , 81 , resultCaptor .getValue ());
279+ assertEquals (0 , fakeClock .numPendingTasks ());
280+
281+ resolver .shutdown ();
282+
283+ verify (mockResolver ).resolveAddress (Matchers .anyString ());
284+ }
285+
286+ @ Test
287+ public void resolve_usingCache () throws Exception {
288+ long ttl = 60 ;
289+ System .setProperty (DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY , Long .toString (ttl ));
290+ final List <InetAddress > answer = createAddressList (2 );
291+ String name = "foo.googleapis.com" ;
292+ FakeTicker fakeTicker = new FakeTicker ();
293+
294+ DnsNameResolver resolver =
295+ newResolver (name , 81 , GrpcUtil .NOOP_PROXY_DETECTOR , Stopwatch .createUnstarted (fakeTicker ));
296+ AddressResolver mockResolver = mock (AddressResolver .class );
297+ when (mockResolver .resolveAddress (Matchers .anyString ()))
298+ .thenReturn (answer )
299+ .thenThrow (new AssertionError ("should not reach here." ));
300+ resolver .setAddressResolver (mockResolver );
301+
302+ resolver .start (mockListener );
303+ assertEquals (1 , fakeExecutor .runDueTasks ());
304+ verify (mockListener ).onAddresses (resultCaptor .capture (), any (Attributes .class ));
305+ assertAnswerMatches (answer , 81 , resultCaptor .getValue ());
306+ assertEquals (0 , fakeClock .numPendingTasks ());
307+
308+ // this refresh should return cached result
309+ fakeTicker .advance (ttl - 1 , TimeUnit .SECONDS );
310+ resolver .refresh ();
311+ assertEquals (1 , fakeExecutor .runDueTasks ());
312+ verifyNoMoreInteractions (mockListener );
313+ assertAnswerMatches (answer , 81 , resultCaptor .getValue ());
314+ assertEquals (0 , fakeClock .numPendingTasks ());
315+
316+ resolver .shutdown ();
317+
318+ verify (mockResolver ).resolveAddress (Matchers .anyString ());
319+ }
320+
321+ @ Test
322+ public void resolve_cacheExpired () throws Exception {
323+ long ttl = 60 ;
324+ System .setProperty (DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY , Long .toString (ttl ));
325+ final List <InetAddress > answer1 = createAddressList (2 );
326+ final List <InetAddress > answer2 = createAddressList (1 );
327+ String name = "foo.googleapis.com" ;
328+ FakeTicker fakeTicker = new FakeTicker ();
329+
330+ DnsNameResolver resolver =
331+ newResolver (name , 81 , GrpcUtil .NOOP_PROXY_DETECTOR , Stopwatch .createUnstarted (fakeTicker ));
332+ AddressResolver mockResolver = mock (AddressResolver .class );
333+ when (mockResolver .resolveAddress (Matchers .anyString ())).thenReturn (answer1 ).thenReturn (answer2 );
334+ resolver .setAddressResolver (mockResolver );
335+
336+ resolver .start (mockListener );
337+ assertEquals (1 , fakeExecutor .runDueTasks ());
338+ verify (mockListener ).onAddresses (resultCaptor .capture (), any (Attributes .class ));
339+ assertAnswerMatches (answer1 , 81 , resultCaptor .getValue ());
340+ assertEquals (0 , fakeClock .numPendingTasks ());
341+
342+ fakeTicker .advance (ttl + 1 , TimeUnit .SECONDS );
343+ resolver .refresh ();
344+ assertEquals (1 , fakeExecutor .runDueTasks ());
345+ verify (mockListener , times (2 )).onAddresses (resultCaptor .capture (), any (Attributes .class ));
346+ assertAnswerMatches (answer2 , 81 , resultCaptor .getValue ());
347+ assertEquals (0 , fakeClock .numPendingTasks ());
348+
349+ resolver .shutdown ();
350+
351+ verify (mockResolver , times (2 )).resolveAddress (Matchers .anyString ());
352+ }
353+
354+ @ Test
355+ public void resolve_invalidTtlPropertyValue () throws Exception {
356+ System .setProperty (DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY , "not_a_number" );
357+ resolveDefaultValue ();
358+ }
359+
360+ @ Test
361+ public void resolve_noPropertyValue () throws Exception {
362+ System .clearProperty (DnsNameResolver .NETWORKADDRESS_CACHE_TTL_PROPERTY );
363+ resolveDefaultValue ();
364+ }
365+
366+ private void resolveDefaultValue () throws Exception {
367+ final List <InetAddress > answer1 = createAddressList (2 );
368+ final List <InetAddress > answer2 = createAddressList (1 );
369+ String name = "foo.googleapis.com" ;
370+ FakeTicker fakeTicker = new FakeTicker ();
371+
372+ DnsNameResolver resolver =
373+ newResolver (name , 81 , GrpcUtil .NOOP_PROXY_DETECTOR , Stopwatch .createUnstarted (fakeTicker ));
374+ AddressResolver mockResolver = mock (AddressResolver .class );
375+ when (mockResolver .resolveAddress (Matchers .anyString ())).thenReturn (answer1 ).thenReturn (answer2 );
376+ resolver .setAddressResolver (mockResolver );
377+
378+ resolver .start (mockListener );
379+ assertEquals (1 , fakeExecutor .runDueTasks ());
380+ verify (mockListener ).onAddresses (resultCaptor .capture (), any (Attributes .class ));
381+ assertAnswerMatches (answer1 , 81 , resultCaptor .getValue ());
382+ assertEquals (0 , fakeClock .numPendingTasks ());
383+
384+ fakeTicker .advance (DnsNameResolver .DEFAULT_NETWORK_CACHE_TTL_SECONDS , TimeUnit .SECONDS );
385+ resolver .refresh ();
386+ assertEquals (1 , fakeExecutor .runDueTasks ());
387+ verifyNoMoreInteractions (mockListener );
388+ assertAnswerMatches (answer1 , 81 , resultCaptor .getValue ());
389+ assertEquals (0 , fakeClock .numPendingTasks ());
390+
391+ fakeTicker .advance (1 , TimeUnit .SECONDS );
392+ resolver .refresh ();
393+ assertEquals (1 , fakeExecutor .runDueTasks ());
394+ verify (mockListener , times (2 )).onAddresses (resultCaptor .capture (), any (Attributes .class ));
395+ assertAnswerMatches (answer2 , 81 , resultCaptor .getValue ());
396+ assertEquals (0 , fakeClock .numPendingTasks ());
397+
398+ resolver .shutdown ();
399+
400+ verify (mockResolver , times (2 )).resolveAddress (Matchers .anyString ());
401+ }
402+
231403 @ Test
232404 public void resolveAll_nullResourceResolver () throws Exception {
233405 final String hostname = "addr.fake" ;
@@ -371,7 +543,8 @@ public void doNotResolveWhenProxyDetected() throws Exception {
371543 "password" );
372544 when (alwaysDetectProxy .proxyFor (any (SocketAddress .class )))
373545 .thenReturn (proxyParameters );
374- DnsNameResolver resolver = newResolver (name , port , alwaysDetectProxy );
546+ DnsNameResolver resolver =
547+ newResolver (name , port , alwaysDetectProxy , Stopwatch .createUnstarted ());
375548 AddressResolver mockAddressResolver = mock (AddressResolver .class );
376549 when (mockAddressResolver .resolveAddress (Matchers .anyString ())).thenThrow (new AssertionError ());
377550 resolver .setAddressResolver (mockAddressResolver );
0 commit comments