summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@yahoo-inc.com>2016-10-13 14:39:55 +0200
committerHåkon Hallingstad <hakon@yahoo-inc.com>2016-10-13 14:44:16 +0200
commit351e31fe39e6dec924abec367691c158fe471345 (patch)
treef7b4bb7a60f77db83b5deadbb922993b02c93f37
parent4781e77d144f932913d0e37924ed931bbd880e13 (diff)
Fall back to picking another resolvable and reachable address if the system
hostname isn't.
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTestCase.java1
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/Identity.java11
-rw-r--r--vespajlib/src/main/java/com/yahoo/net/HostName.java124
-rw-r--r--vespajlib/src/test/java/com/yahoo/net/HostNameTestCase.java5
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());
+ }
}