Move bootstrap checks to node start

This commit moves the execution of the bootstrap checks to after network
services are started. This gives us the flexibility to not merely check
if any of the network settings are set, but instead be smarter about it
and check if the network settings are set in a way that means that the
node will be communicating over an external network (either directly, or
via a proxy). As an bonanza, executing the bootstrap checks in this way
enables us to have the node name in the logs!

Closes #17570
This commit is contained in:
Jason Tedor 2016-04-07 09:10:46 -04:00
parent 64f5a4f848
commit cfc23a9d67
8 changed files with 130 additions and 62 deletions

View File

@ -33,6 +33,7 @@ import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.LogConfigurator; import org.elasticsearch.common.logging.LogConfigurator;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.BoundTransportAddress;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.monitor.jvm.JvmInfo; import org.elasticsearch.monitor.jvm.JvmInfo;
import org.elasticsearch.monitor.os.OsProbe; import org.elasticsearch.monitor.os.OsProbe;
@ -184,9 +185,12 @@ final class Bootstrap {
.put(InternalSettingsPreparer.IGNORE_SYSTEM_PROPERTIES_SETTING.getKey(), true) .put(InternalSettingsPreparer.IGNORE_SYSTEM_PROPERTIES_SETTING.getKey(), true)
.build(); .build();
BootstrapCheck.check(nodeSettings); node = new Node(nodeSettings) {
@Override
node = new Node(nodeSettings); protected void validateNodeBeforeAcceptingRequests(Settings settings, BoundTransportAddress boundTransportAddress) {
BootstrapCheck.check(settings, boundTransportAddress);
}
};
} }
private static Environment initialSettings(boolean foreground, String pidFile) { private static Environment initialSettings(boolean foreground, String pidFile) {

View File

@ -22,20 +22,18 @@ package org.elasticsearch.bootstrap;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.BoundTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.discovery.zen.elect.ElectMasterService; import org.elasticsearch.discovery.zen.elect.ElectMasterService;
import org.elasticsearch.monitor.process.ProcessProbe; import org.elasticsearch.monitor.process.ProcessProbe;
import org.elasticsearch.transport.TransportSettings; import org.elasticsearch.node.Node;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
/** /**
* We enforce limits once any network host is configured. In this case we assume the node is running in production * We enforce limits once any network host is configured. In this case we assume the node is running in production
@ -55,9 +53,10 @@ final class BootstrapCheck {
* checks * checks
* *
* @param settings the current node settings * @param settings the current node settings
* @param boundTransportAddress the node network bindings
*/ */
public static void check(final Settings settings) { static void check(final Settings settings, final BoundTransportAddress boundTransportAddress) {
check(enforceLimits(settings), checks(settings)); check(enforceLimits(boundTransportAddress), checks(settings), Node.NODE_NAME_SETTING.get(settings));
} }
/** /**
@ -67,10 +66,11 @@ final class BootstrapCheck {
* @param enforceLimits true if the checks should be enforced or * @param enforceLimits true if the checks should be enforced or
* warned * warned
* @param checks the checks to execute * @param checks the checks to execute
* @param nodeName the node name to be used as a logging prefix
*/ */
// visible for testing // visible for testing
static void check(final boolean enforceLimits, final List<Check> checks) { static void check(final boolean enforceLimits, final List<Check> checks, final String nodeName) {
final ESLogger logger = Loggers.getLogger(BootstrapCheck.class); final ESLogger logger = Loggers.getLogger(BootstrapCheck.class, nodeName);
for (final Check check : checks) { for (final Check check : checks) {
final boolean fail = check.check(); final boolean fail = check.check();
@ -84,33 +84,16 @@ final class BootstrapCheck {
} }
} }
/**
* The set of settings such that if any are set for the node, then
* the checks are enforced
*
* @return the enforcement settings
*/
// visible for testing
static Set<Setting> enforceSettings() {
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
TransportSettings.BIND_HOST,
TransportSettings.HOST,
TransportSettings.PUBLISH_HOST,
NetworkService.GLOBAL_NETWORK_HOST_SETTING,
NetworkService.GLOBAL_NETWORK_BINDHOST_SETTING,
NetworkService.GLOBAL_NETWORK_PUBLISHHOST_SETTING
)));
}
/** /**
* Tests if the checks should be enforced * Tests if the checks should be enforced
* *
* @param settings the current node settings * @param boundTransportAddress the node network bindings
* @return true if the checks should be enforced * @return true if the checks should be enforced
*/ */
// visible for testing // visible for testing
static boolean enforceLimits(final Settings settings) { static boolean enforceLimits(BoundTransportAddress boundTransportAddress) {
return enforceSettings().stream().anyMatch(s -> s.exists(settings)); return !(Arrays.stream(boundTransportAddress.boundAddresses()).allMatch(TransportAddress::isLoopbackOrLinkLocalAddress) &&
boundTransportAddress.publishAddress().isLoopbackOrLinkLocalAddress());
} }
// the list of checks to execute // the list of checks to execute

View File

@ -44,6 +44,11 @@ public class DummyTransportAddress implements TransportAddress {
return other == INSTANCE; return other == INSTANCE;
} }
@Override
public boolean isLoopbackOrLinkLocalAddress() {
return false;
}
@Override @Override
public String getHost() { public String getHost() {
return "dummy"; return "dummy";

View File

@ -74,6 +74,11 @@ public final class InetSocketTransportAddress implements TransportAddress {
address.getAddress().equals(((InetSocketTransportAddress) other).address.getAddress()); address.getAddress().equals(((InetSocketTransportAddress) other).address.getAddress());
} }
@Override
public boolean isLoopbackOrLinkLocalAddress() {
return address.getAddress().isLinkLocalAddress() || address.getAddress().isLoopbackAddress();
}
@Override @Override
public String getHost() { public String getHost() {
return getAddress(); // just delegate no resolving return getAddress(); // just delegate no resolving

View File

@ -55,6 +55,11 @@ public final class LocalTransportAddress implements TransportAddress {
return other instanceof LocalTransportAddress && id.equals(((LocalTransportAddress) other).id); return other instanceof LocalTransportAddress && id.equals(((LocalTransportAddress) other).id);
} }
@Override
public boolean isLoopbackOrLinkLocalAddress() {
return false;
}
@Override @Override
public String getHost() { public String getHost() {
return "local"; return "local";

View File

@ -46,5 +46,7 @@ public interface TransportAddress extends Writeable<TransportAddress> {
boolean sameHost(TransportAddress other); boolean sameHost(TransportAddress other);
boolean isLoopbackOrLinkLocalAddress();
public String toString(); public String toString();
} }

View File

@ -266,10 +266,6 @@ public class Node implements Closeable {
* Start the node. If the node is already started, this method is no-op. * Start the node. If the node is already started, this method is no-op.
*/ */
public Node start() { public Node start() {
if (!lifecycle.moveToStarted()) {
return this;
}
ESLogger logger = Loggers.getLogger(Node.class, NODE_NAME_SETTING.get(settings)); ESLogger logger = Loggers.getLogger(Node.class, NODE_NAME_SETTING.get(settings));
logger.info("starting ..."); logger.info("starting ...");
// hack around dependency injection problem (for now...) // hack around dependency injection problem (for now...)
@ -308,10 +304,12 @@ public class Node implements Closeable {
final TribeService tribeService = injector.getInstance(TribeService.class); final TribeService tribeService = injector.getInstance(TribeService.class);
tribeService.start(); tribeService.start();
// Start the transport service now so the publish address will be added to the local disco node in ClusterService // Start the transport service now so the publish address will be added to the local disco node in ClusterService
TransportService transportService = injector.getInstance(TransportService.class); TransportService transportService = injector.getInstance(TransportService.class);
transportService.start(); transportService.start();
validateNodeBeforeAcceptingRequests(settings, transportService.boundAddress());
DiscoveryNode localNode = injector.getInstance(DiscoveryNodeService.class) DiscoveryNode localNode = injector.getInstance(DiscoveryNodeService.class)
.buildLocalNode(transportService.boundAddress().publishAddress()); .buildLocalNode(transportService.boundAddress().publishAddress());
@ -381,6 +379,9 @@ public class Node implements Closeable {
} }
private Node stop() { private Node stop() {
if (lifecycle.moveToStarted()) {
return this;
}
if (!lifecycle.moveToStopped()) { if (!lifecycle.moveToStopped()) {
return this; return this;
} }
@ -521,6 +522,20 @@ public class Node implements Closeable {
return this.injector; return this.injector;
} }
/**
* Hook for validating the node after network
* services are started but before the cluster service is started
* and before the network service starts accepting incoming network
* requests.
*
* @param settings the fully-resolved settings
* @param boundTransportAddress the network addresses the node is
* bound and publishing to
*/
@SuppressWarnings("unused")
protected void validateNodeBeforeAcceptingRequests(Settings settings, BoundTransportAddress boundTransportAddress) {
}
/** Writes a file to the logs dir containing the ports for the given transport type */ /** Writes a file to the logs dir containing the ports for the given transport type */
private void writePortsFile(String type, BoundTransportAddress boundAddress) { private void writePortsFile(String type, BoundTransportAddress boundAddress) {
Path tmpPortsFile = environment.logsFile().resolve(type + ".ports.tmp"); Path tmpPortsFile = environment.logsFile().resolve(type + ".ports.tmp");

View File

@ -20,26 +20,81 @@
package org.elasticsearch.bootstrap; package org.elasticsearch.bootstrap;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.BoundTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BootstrapCheckTests extends ESTestCase { public class BootstrapCheckTests extends ESTestCase {
public void testNonProductionMode() { public void testNonProductionMode() {
// nothing should happen since we are in non-production mode // nothing should happen since we are in non-production mode
BootstrapCheck.check(Settings.EMPTY); final List<TransportAddress> transportAddresses = new ArrayList<>();
for (int i = 0; i < randomIntBetween(1, 8); i++) {
TransportAddress localTransportAddress = mock(TransportAddress.class);
when(localTransportAddress.isLoopbackOrLinkLocalAddress()).thenReturn(true);
transportAddresses.add(localTransportAddress);
}
TransportAddress publishAddress = mock(TransportAddress.class);
when(publishAddress.isLoopbackOrLinkLocalAddress()).thenReturn(true);
BoundTransportAddress boundTransportAddress = mock(BoundTransportAddress.class);
when(boundTransportAddress.boundAddresses()).thenReturn(transportAddresses.toArray(new TransportAddress[0]));
when(boundTransportAddress.publishAddress()).thenReturn(publishAddress);
BootstrapCheck.check(Settings.EMPTY, boundTransportAddress);
}
public void testEnforceLimitsWhenBoundToNonLocalAddress() {
final List<TransportAddress> transportAddresses = new ArrayList<>();
final TransportAddress nonLocalTransportAddress = mock(TransportAddress.class);
when(nonLocalTransportAddress.isLoopbackOrLinkLocalAddress()).thenReturn(false);
transportAddresses.add(nonLocalTransportAddress);
for (int i = 0; i < randomIntBetween(0, 7); i++) {
final TransportAddress randomTransportAddress = mock(TransportAddress.class);
when(randomTransportAddress.isLoopbackOrLinkLocalAddress()).thenReturn(randomBoolean());
transportAddresses.add(randomTransportAddress);
}
final TransportAddress publishAddress = mock(TransportAddress.class);
when(publishAddress.isLoopbackOrLinkLocalAddress()).thenReturn(randomBoolean());
final BoundTransportAddress boundTransportAddress = mock(BoundTransportAddress.class);
Collections.shuffle(transportAddresses, random());
when(boundTransportAddress.boundAddresses()).thenReturn(transportAddresses.toArray(new TransportAddress[0]));
when(boundTransportAddress.publishAddress()).thenReturn(publishAddress);
assertTrue(BootstrapCheck.enforceLimits(boundTransportAddress));
}
public void testEnforceLimitsWhenPublishingToNonLocalAddress() {
final List<TransportAddress> transportAddresses = new ArrayList<>();
for (int i = 0; i < randomIntBetween(1, 8); i++) {
final TransportAddress randomTransportAddress = mock(TransportAddress.class);
when(randomTransportAddress.isLoopbackOrLinkLocalAddress()).thenReturn(false);
transportAddresses.add(randomTransportAddress);
}
final TransportAddress publishAddress = mock(TransportAddress.class);
when(publishAddress.isLoopbackOrLinkLocalAddress()).thenReturn(true);
final BoundTransportAddress boundTransportAddress = mock(BoundTransportAddress.class);
when(boundTransportAddress.boundAddresses()).thenReturn(transportAddresses.toArray(new TransportAddress[0]));
when(boundTransportAddress.publishAddress()).thenReturn(publishAddress);
assertTrue(BootstrapCheck.enforceLimits(boundTransportAddress));
} }
public void testFileDescriptorLimits() { public void testFileDescriptorLimits() {
@ -64,7 +119,7 @@ public class BootstrapCheckTests extends ESTestCase {
} }
try { try {
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testFileDescriptorLimits");
fail("should have failed due to max file descriptors too low"); fail("should have failed due to max file descriptors too low");
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
assertThat(e.getMessage(), containsString("max file descriptors")); assertThat(e.getMessage(), containsString("max file descriptors"));
@ -72,12 +127,12 @@ public class BootstrapCheckTests extends ESTestCase {
maxFileDescriptorCount.set(randomIntBetween(limit + 1, Integer.MAX_VALUE)); maxFileDescriptorCount.set(randomIntBetween(limit + 1, Integer.MAX_VALUE));
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testFileDescriptorLimits");
// nothing should happen if current file descriptor count is // nothing should happen if current file descriptor count is
// not available // not available
maxFileDescriptorCount.set(-1); maxFileDescriptorCount.set(-1);
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testFileDescriptorLimits");
} }
public void testFileDescriptorLimitsThrowsOnInvalidLimit() { public void testFileDescriptorLimitsThrowsOnInvalidLimit() {
@ -119,7 +174,7 @@ public class BootstrapCheckTests extends ESTestCase {
if (testCase.shouldFail) { if (testCase.shouldFail) {
try { try {
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testFileDescriptorLimitsThrowsOnInvalidLimit");
fail("should have failed due to memory not being locked"); fail("should have failed due to memory not being locked");
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
assertThat( assertThat(
@ -128,7 +183,7 @@ public class BootstrapCheckTests extends ESTestCase {
} }
} else { } else {
// nothing should happen // nothing should happen
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testFileDescriptorLimitsThrowsOnInvalidLimit");
} }
} }
} }
@ -144,7 +199,7 @@ public class BootstrapCheckTests extends ESTestCase {
}; };
try { try {
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testMaxNumberOfThreadsCheck");
fail("should have failed due to max number of threads too low"); fail("should have failed due to max number of threads too low");
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
assertThat(e.getMessage(), containsString("max number of threads")); assertThat(e.getMessage(), containsString("max number of threads"));
@ -152,12 +207,12 @@ public class BootstrapCheckTests extends ESTestCase {
maxNumberOfThreads.set(randomIntBetween(limit + 1, Integer.MAX_VALUE)); maxNumberOfThreads.set(randomIntBetween(limit + 1, Integer.MAX_VALUE));
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testMaxNumberOfThreadsCheck");
// nothing should happen if current max number of threads is // nothing should happen if current max number of threads is
// not available // not available
maxNumberOfThreads.set(-1); maxNumberOfThreads.set(-1);
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testMaxNumberOfThreadsCheck");
} }
public void testMaxSizeVirtualMemory() { public void testMaxSizeVirtualMemory() {
@ -176,7 +231,7 @@ public class BootstrapCheckTests extends ESTestCase {
}; };
try { try {
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testMaxSizeVirtualMemory");
fail("should have failed due to max size virtual memory too low"); fail("should have failed due to max size virtual memory too low");
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
assertThat(e.getMessage(), containsString("max size virtual memory")); assertThat(e.getMessage(), containsString("max size virtual memory"));
@ -184,19 +239,12 @@ public class BootstrapCheckTests extends ESTestCase {
maxSizeVirtualMemory.set(rlimInfinity); maxSizeVirtualMemory.set(rlimInfinity);
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testMaxSizeVirtualMemory");
// nothing should happen if max size virtual memory is not // nothing should happen if max size virtual memory is not
// available // available
maxSizeVirtualMemory.set(Long.MIN_VALUE); maxSizeVirtualMemory.set(Long.MIN_VALUE);
BootstrapCheck.check(true, Collections.singletonList(check)); BootstrapCheck.check(true, Collections.singletonList(check), "testMaxSizeVirtualMemory");
}
public void testEnforceLimits() {
final Set<Setting> enforceSettings = BootstrapCheck.enforceSettings();
final Setting setting = randomFrom(Arrays.asList(enforceSettings.toArray(new Setting[enforceSettings.size()])));
final Settings settings = Settings.builder().put(setting.getKey(), randomAsciiOfLength(8)).build();
assertTrue(BootstrapCheck.enforceLimits(settings));
} }
public void testMinMasterNodes() { public void testMinMasterNodes() {
@ -205,6 +253,7 @@ public class BootstrapCheckTests extends ESTestCase {
assertThat(check.check(), not(equalTo(isSet))); assertThat(check.check(), not(equalTo(isSet)));
List<BootstrapCheck.Check> defaultChecks = BootstrapCheck.checks(Settings.EMPTY); List<BootstrapCheck.Check> defaultChecks = BootstrapCheck.checks(Settings.EMPTY);
expectThrows(RuntimeException.class, () -> BootstrapCheck.check(true, defaultChecks)); expectThrows(RuntimeException.class, () -> BootstrapCheck.check(true, defaultChecks, "testMinMasterNodes"));
} }
} }