diff options
author | Håkon Hallingstad <hakon@yahoo-inc.com> | 2016-10-13 14:39:55 +0200 |
---|---|---|
committer | Håkon Hallingstad <hakon@yahoo-inc.com> | 2016-10-13 14:44:16 +0200 |
commit | 351e31fe39e6dec924abec367691c158fe471345 (patch) | |
tree | f7b4bb7a60f77db83b5deadbb922993b02c93f37 | |
parent | 4781e77d144f932913d0e37924ed931bbd880e13 (diff) |
Fall back to picking another resolvable and reachable address if the system
hostname isn't.
4 files changed, 117 insertions, 24 deletions
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java index 87fb5e567a5..8eca2bb52ce 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java @@ -23,7 +23,6 @@ import com.yahoo.container.core.ContainerHttpConfig; import com.yahoo.document.config.DocumentmanagerConfig; import com.yahoo.messagebus.MessagebusConfig; import com.yahoo.net.HostName; -import com.yahoo.net.LinuxInetAddress; import com.yahoo.vespa.config.ConfigKey; import com.yahoo.vespa.config.ConfigPayload; import com.yahoo.vespa.config.ConfigPayloadBuilder; diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java b/messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java index 45887b072ab..585fe680ba9 100644 --- a/messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java +++ b/messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java @@ -1,12 +1,7 @@ // Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.messagebus.network; -import com.yahoo.log.LogLevel; import com.yahoo.net.HostName; -import com.yahoo.net.LinuxInetAddress; - -import java.net.Inet6Address; -import java.net.InetAddress; /** * This class encapsulates the identity of the application that uses this instance of message bus. This identity @@ -28,11 +23,7 @@ public class Identity { * @param configId The config identifier for the application. */ public Identity(String configId) { - InetAddress addr = LinuxInetAddress.getLocalHost(); // try hard to get a resolvable address - if (addr instanceof Inet6Address) // - hostname = HostName.getLocalhost(); // ... but fallback to hostname if we get an IPv6 address - else - hostname = addr.getCanonicalHostName(); + hostname = HostName.getLocalhost(); // ... but fallback to hostname if we get an IPv6 address servicePrefix = configId; } diff --git a/vespajlib/src/main/java/com/yahoo/net/HostName.java b/vespajlib/src/main/java/com/yahoo/net/HostName.java index 4e791ca117a..21f8c8ede04 100644 --- a/vespajlib/src/main/java/com/yahoo/net/HostName.java +++ b/vespajlib/src/main/java/com/yahoo/net/HostName.java @@ -2,7 +2,18 @@ package com.yahoo.net; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; /** * Utilities for getting the hostname on a system running with the JVM. This is moved here from the old @@ -11,31 +22,118 @@ import java.io.InputStreamReader; * @author lulf */ public class HostName { + private static final Logger logger = Logger.getLogger(HostName.class.getName()); - private static String myHost = null; + private static String cachedHostName = null; /** - * Static method that returns the name of localhost using shell command "hostname". - * If you need a guaranteed resolvable name see LinuxINetAddress. + * Return a fully qualified hostname that resolves to an IP address on a network interface. + * Normally this is the same as the 'hostname' command, but on dev machines on WiFi only, + * that IP isn't configured while a WiFi network interface IP address is both reachable and + * has a DNS entry, enough to . * * @return the name of localhost. * @throws RuntimeException if executing the command 'hostname' fails. */ public static synchronized String getLocalhost() { - if (myHost == null) { + if (cachedHostName == null) { try { - Process p = Runtime.getRuntime().exec("hostname"); - BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream())); - myHost = in.readLine(); - p.waitFor(); - if (p.exitValue() != 0) { - throw new RuntimeException("Command 'hostname' failed: exit("+p.exitValue()+")"); - } + Address preferredAddress = getPreferredAddress(); + cachedHostName = preferredAddress.canonicalHostName; } catch (Exception e) { - throw new RuntimeException("Failed when executing command 'hostname'", e); + throw new RuntimeException("Failed to find the preferred hostname", e); } } - return myHost; + return cachedHostName; + } + + private static Address getPreferredAddress() throws Exception { + List<Address> addresses = getReachableNetworkInterfaceAddresses(); + + // Prefer address matching the system hostname + String systemHostName = getSystemHostName(); + List<Address> systemAddresses = addresses.stream() + .filter(address -> Objects.equals(address.canonicalHostName, systemHostName)) + .collect(Collectors.toList()); + if (systemAddresses.size() >= 1) { + // Is it OK to pick the first of many? + return systemAddresses.iterator().next(); + } + + // Otherwise, prefer non-local address. + List<Address> nonLocalAddresses = addresses.stream() + .filter(address -> !address.ipAddress.isAnyLocalAddress()) + .collect(Collectors.toList()); + if (nonLocalAddresses.size() >= 1) { + // Is it OK to pick the first of many? + return nonLocalAddresses.iterator().next(); + } + + // Otherwise, pick a local address. + List<Address> localAddresses = addresses.stream() + .filter(address -> address.ipAddress.isAnyLocalAddress()) + .collect(Collectors.toList()); + if (localAddresses.size() >= 1) { + // Is it OK to pick the first of many? + return localAddresses.iterator().next(); + } + + throw new RuntimeException("Failed to find any addresses on the network interfaces that resolves to a DNS name"); + } + + // public for testing purposes (all testing machines should have a hostname + public static String getSystemHostName() throws Exception { + Process process = Runtime.getRuntime().exec("hostname"); + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); + String hostname = in.readLine(); + process.waitFor(); + if (process.exitValue() != 0) { + throw new RuntimeException("Command 'hostname' failed with exit code " + process.exitValue()); + } + + return hostname; } + private static class Address { + public final InetAddress ipAddress; + public final String canonicalHostName; + + public Address(InetAddress ipAddress, String canonicalHostName) { + this.ipAddress = ipAddress; + this.canonicalHostName = canonicalHostName; + } + } + + private static List<Address> getReachableNetworkInterfaceAddresses() throws SocketException { + List<Address> addresses = new ArrayList<>(); + + for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { + for (InetAddress ipAddress : Collections.list(networkInterface.getInetAddresses())) { + String hostname = ipAddress.getCanonicalHostName(); + if (Objects.equals(hostname, ipAddress.getHostAddress())) { + // getCanonicalHostName() failed to get the fully qualified domain name + continue; + } + + try { + // ping says ~50ms on my Fedora Lenovo, but that seems a lot for pinging oneself + int timeoutMs = 100; + if (!ipAddress.isReachable(timeoutMs)) { + // The network interface may be down, ignore address + logger.log(Level.INFO, ipAddress.toString() + + " is unreachable w/" + timeoutMs + "ms timeout, ignoring address"); + continue; + } + } catch (IOException e) { + // Why would this be different from !isReachable ? + logger.log(Level.INFO, "Failed testing reachability of " + ipAddress + ", ignoring address", e); + continue; + } + + addresses.add(new Address(ipAddress, hostname)); + } + } + + return addresses; + } } diff --git a/vespajlib/src/test/java/com/yahoo/net/HostNameTestCase.java b/vespajlib/src/test/java/com/yahoo/net/HostNameTestCase.java index 98be9f0ef6f..bcb766d6cb8 100644 --- a/vespajlib/src/test/java/com/yahoo/net/HostNameTestCase.java +++ b/vespajlib/src/test/java/com/yahoo/net/HostNameTestCase.java @@ -13,4 +13,9 @@ public class HostNameTestCase { public void testHostnameIsFound() { assertFalse(HostName.getLocalhost().isEmpty()); } + + @Test + public void testSystemHostnameIsFound() throws Exception { + assertFalse(HostName.getSystemHostName().isEmpty()); + } } |