diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/UtilAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/UtilAutoConfiguration.java new file mode 100644 index 00000000..c38d3a5f --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/UtilAutoConfiguration.java @@ -0,0 +1,31 @@ +package org.springframework.cloud; + +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.util.InetUtils; +import org.springframework.cloud.util.InetUtilsProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Spencer Gibb + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.util.enabled", matchIfMissing = true) +@AutoConfigureOrder(0) +@EnableConfigurationProperties +public class UtilAutoConfiguration { + + @Bean + public InetUtilsProperties inetUtilsProperties() { + return new InetUtilsProperties(); + } + + @Bean + @ConditionalOnMissingBean + public InetUtils inetUtils(InetUtilsProperties properties) { + return new InetUtils(properties); + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/util/InetUtils.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/util/InetUtils.java index 9b93975d..d79077ac 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/util/InetUtils.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/util/InetUtils.java @@ -26,6 +26,103 @@ import lombok.extern.apachecommons.CommonsLog; @CommonsLog public class InetUtils { + private final ExecutorService executorService; + private final InetUtilsProperties properties; + + public InetUtils(final InetUtilsProperties properties) { + this.properties = properties; + this.executorService = Executors + .newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName(properties.getExecutorThreadName()); + thread.setDaemon(true); + return thread; + } + }); + } + + public HostInfo findFirstNonLoopbackHostInfo() { + InetAddress address = findFirstNonLoopbackAddress(); + if (address != null) { + return convertAddress(address); + } + HostInfo hostInfo = new HostInfo(); + hostInfo.setHostname(this.properties.getDefaultHostname()); + hostInfo.setIpAddress(this.properties.getDefaultIpAddress()); + return hostInfo; + } + + public InetAddress findFirstNonLoopbackAddress() { + try { + for (Enumeration nics = NetworkInterface + .getNetworkInterfaces(); nics.hasMoreElements();) { + NetworkInterface ifc = nics.nextElement(); + if (ifc.isUp()) { + log.debug("Testing interface: " + ifc.getDisplayName()); + + // @formatter:off + if (!ignoreInterface(ifc.getDisplayName())) { + for (Enumeration addrs = ifc .getInetAddresses(); addrs.hasMoreElements(); ) { + InetAddress address = addrs.nextElement(); + if (address instanceof Inet4Address && !address.isLoopbackAddress()) { + log.debug("Found non-loopback interface: " + ifc.getDisplayName()); + return address; + } + } + } + // @formatter:on + } + } + } + catch (IOException ex) { + log.error("Cannot get first non-loopback address", ex); + } + + try { + return InetAddress.getLocalHost(); + } + catch (UnknownHostException e) { + log.warn("Unable to retrieve localhost"); + } + + return null; + } + + boolean ignoreInterface(String interfaceName) { + for (String regex : this.properties.getIgnoredInterfaces()) { + if (interfaceName.matches(regex)) { + log.debug("Ignoring interface: " + interfaceName); + return true; + } + } + return false; + } + + public HostInfo convertAddress(final InetAddress address) { + HostInfo hostInfo = new HostInfo(); + Future result = this.executorService.submit(new Callable() { + @Override + public String call() throws Exception { + return address.getHostName(); + } + }); + + String hostname; + try { + hostname = result.get(this.properties.getTimeoutSeconds(), TimeUnit.SECONDS); + } + catch (Exception e) { + log.info("Cannot determine local hostname"); + hostname = "localhost"; + } + hostInfo.setHostname(hostname); + hostInfo.setIpAddress(address.getHostAddress()); + return hostInfo; + } + + @Deprecated private static ExecutorService executor = Executors .newSingleThreadExecutor(new ThreadFactory() { @Override @@ -41,6 +138,7 @@ public class InetUtils { * Find the first non-loopback host info. If there were errors return a hostinfo with * 'localhost' and '127.0.0.1' for hostname and ipAddress respectively. */ + @Deprecated public static HostInfo getFirstNonLoopbackHostInfo() { InetAddress address = getFirstNonLoopbackAddress(); if (address != null) { @@ -56,6 +154,7 @@ public class InetUtils { * Find the first non-loopback InetAddress */ @SneakyThrows + @Deprecated public static InetAddress getFirstNonLoopbackAddress() { try { for (Enumeration enumNic = NetworkInterface @@ -90,6 +189,7 @@ public class InetUtils { return null; } + @Deprecated public static HostInfo convert(final InetAddress address) { HostInfo hostInfo = new HostInfo(); Future result = executor.submit(new Callable() { diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/util/InetUtilsProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/util/InetUtilsProperties.java new file mode 100644 index 00000000..703462b8 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/util/InetUtilsProperties.java @@ -0,0 +1,44 @@ +package org.springframework.cloud.util; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Data; + +/** + * @author Spencer Gibb + */ +@Data +@ConfigurationProperties(InetUtilsProperties.PREFIX) +public class InetUtilsProperties { + public static final String PREFIX = "spring.cloud.inetutils"; + + /** + * Name of InetUtils executor thread + */ + private String executorThreadName = PREFIX; + + /** + * The default hostname. Used in case of errors. + */ + private String defaultHostname = "localhost"; + + /** + * The default ipaddress. Used in case of errors. + */ + private String defaultIpAddress = "127.0.0.1"; + + /** + * Timeout in seconds for calculating hostname. + */ + @Value("${spring.util.timeout.sec:${SPRING_UTIL_TIMEOUT_SEC:1}}") + private int timeoutSeconds = 1; + + /** + * List of Java regex expressions for network interfaces that will be ignored. + */ + private List ignoredInterfaces = new ArrayList<>(); +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/util/InetUtilsTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/util/InetUtilsTests.java index c723ea3b..c1548abd 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/util/InetUtilsTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/util/InetUtilsTests.java @@ -16,13 +16,16 @@ package org.springframework.cloud.util; -import static org.junit.Assert.assertNotNull; - import java.net.InetAddress; +import java.util.Arrays; import org.junit.Test; import org.springframework.cloud.util.InetUtils.HostInfo; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + /** * @author Dave Syer * @@ -31,23 +34,51 @@ public class InetUtilsTests { @Test public void testGetFirstNonLoopbackHostInfo() { + assertNotNull(new InetUtils(new InetUtilsProperties()) + .findFirstNonLoopbackHostInfo()); assertNotNull(InetUtils.getFirstNonLoopbackHostInfo()); } @Test public void testGetFirstNonLoopbackAddress() { + assertNotNull(new InetUtils(new InetUtilsProperties()) + .findFirstNonLoopbackAddress()); assertNotNull(InetUtils.getFirstNonLoopbackAddress()); } @Test public void testConvert() throws Exception { + assertNotNull(new InetUtils(new InetUtilsProperties()).convertAddress(InetAddress + .getByName("localhost"))); assertNotNull(InetUtils.convert(InetAddress.getByName("localhost"))); } @Test public void testHostInfo() throws Exception { - HostInfo info = InetUtils.getFirstNonLoopbackHostInfo(); + HostInfo info = new InetUtils(new InetUtilsProperties()) + .findFirstNonLoopbackHostInfo(); + assertNotNull(info.getIpAddressAsInt()); + + info = InetUtils.getFirstNonLoopbackHostInfo(); assertNotNull(info.getIpAddressAsInt()); } + @Test + public void testIgnoreInterface() { + InetUtilsProperties properties = new InetUtilsProperties(); + // These interfaces are not usable for "outside" communication, so they're + // probably not a good fit for the simple strategy of getting the first "usable" + // interface. + // https://docs.docker.com/v1.7/articles/networking/ + properties.setIgnoredInterfaces(Arrays.asList("docker0", "veth.*")); + InetUtils inetUtils = new InetUtils(properties); + + assertTrue("docker0 not ignored", inetUtils.ignoreInterface("docker0")); + assertTrue("vethAQI2QT0 not ignored", inetUtils.ignoreInterface("vethAQI2QT")); + assertFalse("docker1 ignored", inetUtils.ignoreInterface("docker1")); + + assertFalse("docker0 ignored", + new InetUtils(new InetUtilsProperties()).ignoreInterface("docker0")); + } + }