Skip to content
This repository was archived by the owner on Mar 11, 2022. It is now read-only.

Commit 30b68c2

Browse files
authored
Merge pull request #289 from cloudant/270-dns-log
270 dns log
2 parents dc58e79 + c6ab90a commit 30b68c2

3 files changed

Lines changed: 204 additions & 4 deletions

File tree

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
- [FIX] `java.lang.StringIndexOutOfBoundsException` when trying to parse `Set-Cookie` headers.
1111
- [FIX] `NullPointerException` in `CookieInterceptor` when no body was present on response.
1212
- [UPGRADED] Upgraded GSON to 2.7
13+
- [IMPROVED] Added warning messages for JVM DNS cache configuration settings that could impede
14+
client operation during cluster failover.
1315

1416
# 2.5.1 (2016-07-19)
1517
- [IMPROVED] Made the 429 response code backoff optional and configurable. To enable the backoff add

cloudant-client/src/main/java/com/cloudant/client/api/ClientBuilder.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.MalformedURLException;
2121
import java.net.PasswordAuthentication;
2222
import java.net.URL;
23+
import java.security.Security;
2324
import java.util.ArrayList;
2425
import java.util.List;
2526
import java.util.concurrent.TimeUnit;
@@ -223,10 +224,47 @@ public CloudantClient build() {
223224
}
224225
}
225226

226-
227-
//If setter methods for read and connection timeout are not called, default values are used.
228-
logger.config(String.format("Connect timeout: %s %s", connectTimeout, connectTimeoutUnit));
227+
//If setter methods for read and connection timeout are not called, default values
228+
// are used.
229+
logger.config(String.format("Connect timeout: %s %s", connectTimeout,
230+
connectTimeoutUnit));
229231
logger.config(String.format("Read timeout: %s %s", readTimeout, readTimeoutUnit));
232+
233+
// Log a warning if the DNS cache time is too long
234+
try {
235+
boolean shouldLogValueWarning = false;
236+
boolean isUsingDefaultTTLValue = true;
237+
String ttlString = Security.getProperty("networkaddress.cache.ttl");
238+
// Was able to access the property
239+
if (ttlString != null) {
240+
try {
241+
int ttl = Integer.parseInt(ttlString);
242+
isUsingDefaultTTLValue = false;
243+
logger.finest("networkaddress.cache.ttl was " + ttl);
244+
if (ttl > 30 || ttl < 0) {
245+
shouldLogValueWarning = true;
246+
}
247+
} catch (NumberFormatException nfe) {
248+
// Suppress the exception, this will result in the default being used
249+
logger.finest("networkaddress.cache.ttl was not an int.");
250+
}
251+
}
252+
253+
if (isUsingDefaultTTLValue && System.getSecurityManager() != null) {
254+
//If we're using a default value and there is a SecurityManager we need to warn
255+
shouldLogValueWarning = true;
256+
}
257+
258+
if (shouldLogValueWarning) {
259+
logger.warning("DNS cache lifetime may be too long. DNS cache lifetimes in excess" +
260+
" of 30 seconds may impede client operation during cluster failover.");
261+
}
262+
} catch (SecurityException e) {
263+
// Couldn't access the property; log a warning
264+
logger.warning("Permission denied to check Java DNS cache TTL. If the cache " +
265+
"lifetime is too long cluster failover will be impeded.");
266+
}
267+
230268
props.addRequestInterceptors(new TimeoutCustomizationInterceptor(connectTimeout,
231269
connectTimeoutUnit, readTimeout, readTimeoutUnit));
232270

cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static org.junit.Assert.assertEquals;
1818
import static org.junit.Assert.assertTrue;
19+
import static org.junit.Assert.fail;
1920

2021
import com.cloudant.client.api.ClientBuilder;
2122
import com.cloudant.client.api.CloudantClient;
@@ -32,8 +33,15 @@
3233
import org.junit.ClassRule;
3334
import org.junit.Test;
3435

36+
import mockit.Expectations;
37+
import mockit.Mocked;
38+
import mockit.StrictExpectations;
39+
import mockit.Verifications;
40+
3541
import java.io.ByteArrayInputStream;
3642
import java.net.URL;
43+
import java.security.Security;
44+
import java.security.SecurityPermission;
3745
import java.util.ArrayList;
3846
import java.util.List;
3947
import java.util.logging.Handler;
@@ -51,7 +59,7 @@ public class LoggingTest {
5159
public static MockWebServer mockWebServer = new MockWebServer();
5260

5361
private static CloudantClient client;
54-
private Logger logger;
62+
private volatile Logger logger;
5563
private VerificationLogHandler handler;
5664

5765
@BeforeClass
@@ -180,6 +188,158 @@ public void clientBuilderLogging() throws Exception {
180188
assertLogMessage("Using default GSON builder", 4);
181189
}
182190

191+
/**
192+
* A basic DNS log test that can be called with different values.
193+
*
194+
* @param cacheValue the value to set for the cache lifetime
195+
*
196+
* @throws Exception if the test fails or errors
197+
*/
198+
private void basicDnsLogTest(String cacheValue) throws Exception {
199+
logger = setupLogger(ClientBuilder.class, Level.WARNING);
200+
String previous = Security.getProperty("networkaddress.cache.ttl");
201+
try {
202+
Security.setProperty("networkaddress.cache.ttl", cacheValue);
203+
CloudantClientHelper.getClientBuilder().build();
204+
} finally {
205+
// No way to unset a property, just reset to previous value
206+
// or set to 30 if it was null
207+
Security.setProperty("networkaddress.cache.ttl", (previous == null) ? "30" : previous);
208+
}
209+
}
210+
211+
/**
212+
* Test that no warning is logged if the DNS lifetime is less than 30 s
213+
*
214+
* @throws Exception
215+
*/
216+
@Test
217+
public void dnsNoWarningLessThan30() throws Exception {
218+
basicDnsLogTest("29");
219+
// Assert no warning was received
220+
assertEquals("There should be no log entry", 0, handler.logEntries.size());
221+
}
222+
223+
/**
224+
* Test that no warning is logged if DNS caching is disabled
225+
*
226+
* @throws Exception
227+
*/
228+
@Test
229+
public void dnsNoWarning0() throws Exception {
230+
basicDnsLogTest("0");
231+
// Assert no warning was received
232+
assertEquals("There should be no log entry", 0, handler.logEntries.size());
233+
}
234+
235+
/**
236+
* Test that a warning is logged if DNS caching is set to cache forever
237+
*
238+
* @throws Exception
239+
*/
240+
@Test
241+
public void dnsWarningForever() throws Exception {
242+
basicDnsLogTest("-1");
243+
// Assert a warning was received
244+
assertEquals("There should be 1 log entry", 1, handler.logEntries.size());
245+
// Assert that it matches the expected pattern
246+
assertLogMessage("DNS cache lifetime may be too long\\. .*", 0);
247+
}
248+
249+
/**
250+
* Test that no warning is logged if the DNS lifetime is 30 s
251+
*
252+
* @throws Exception
253+
*/
254+
@Test
255+
public void dnsNoWarning30() throws Exception {
256+
basicDnsLogTest("30");
257+
// Assert no warning was received
258+
assertEquals("There should be no log entry", 0, handler.logEntries.size());
259+
}
260+
261+
262+
/**
263+
* Test that a warning is logged if the DNS lifetime is longer than 30 s
264+
*
265+
* @throws Exception
266+
*/
267+
@Test
268+
public void dnsWarning31() throws Exception {
269+
basicDnsLogTest("31");
270+
// Assert a warning was received
271+
assertEquals("There should be 1 log entry", 1, handler.logEntries.size());
272+
// Assert that it matches the expected pattern
273+
assertLogMessage("DNS cache lifetime may be too long\\. .*", 0);
274+
}
275+
276+
/**
277+
* Test that a warning is logged if the DNS lifetime cannot be checked because of security
278+
* permissions.
279+
*
280+
* @throws Exception
281+
*/
282+
@Test
283+
public void dnsWarningPermissionDenied(@Mocked final SecurityManager mockSecurityManager)
284+
throws Exception {
285+
286+
// Record the mock expectations
287+
new Expectations() {
288+
{
289+
mockSecurityManager.checkPermission(new SecurityPermission("getProperty" +
290+
".networkaddress.cache.ttl"));
291+
result = new SecurityException("Test exception to deny property access.");
292+
times = 1;
293+
}
294+
};
295+
logger = setupLogger(ClientBuilder.class, Level.WARNING);
296+
try {
297+
System.setSecurityManager(mockSecurityManager);
298+
CloudantClientHelper.getClientBuilder().build();
299+
} finally {
300+
// Unset the mock security manager
301+
System.setSecurityManager(null);
302+
}
303+
// Assert a warning was received
304+
assertEquals("There should be 1 log entry", 1, handler.logEntries.size());
305+
// Assert that it matches the expected pattern
306+
assertLogMessage("Permission denied to check Java DNS cache TTL\\. .*", 0);
307+
}
308+
309+
/**
310+
* Test that a warning is logged if a security manager is in use and the DNS cache lifetime
311+
* property is unset.
312+
*
313+
* @throws Exception
314+
*/
315+
@Test
316+
public void dnsWarningDefaultWithSecurityManager(@Mocked final SecurityManager
317+
mockSecurityManager) throws Exception {
318+
// Record the mock expectations
319+
new Expectations() {
320+
{
321+
mockSecurityManager.checkPermission(new SecurityPermission("getProperty" +
322+
".networkaddress.cache.ttl"));
323+
minTimes = 2; // Once to set, once to get, and once to reset
324+
maxTimes = 3; // Possible third call to reset the value, depending on test ordering
325+
}
326+
};
327+
try {
328+
System.setSecurityManager(mockSecurityManager);
329+
// We can't set null as a value and there are no APIs for clearing a value. Another test
330+
// may already have changed the value so we just set it to something invalid "a" to get
331+
// a default value.
332+
basicDnsLogTest("a");
333+
} finally {
334+
// Unset the mock security manager
335+
System.setSecurityManager(null);
336+
}
337+
// Assert a warning was received
338+
assertEquals("There should be 1 log entry", 1, handler.logEntries.size());
339+
// Assert that it matches the expected pattern
340+
assertLogMessage("DNS cache lifetime may be too long\\. .*", 0);
341+
}
342+
183343
/**
184344
* Set a LogManager configuration property and assert it was set correctly
185345
*

0 commit comments

Comments
 (0)