diff --git a/core/src/main/java/org/elasticsearch/bootstrap/BootstrapCheck.java b/core/src/main/java/org/elasticsearch/bootstrap/BootstrapCheck.java index 719a968faa2..85cc6497261 100644 --- a/core/src/main/java/org/elasticsearch/bootstrap/BootstrapCheck.java +++ b/core/src/main/java/org/elasticsearch/bootstrap/BootstrapCheck.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; /** * We enforce limits once any network host is configured. In this case we assume the node is running in production @@ -72,14 +73,22 @@ final class BootstrapCheck { static void check(final boolean enforceLimits, final List checks, final String nodeName) { final ESLogger logger = Loggers.getLogger(BootstrapCheck.class, nodeName); - for (final Check check : checks) { - final boolean fail = check.check(); - if (fail) { - if (enforceLimits) { - throw new RuntimeException(check.errorMessage()); - } else { - logger.warn(check.errorMessage()); - } + final List errors = + checks.stream() + .filter(BootstrapCheck.Check::check) + .map(BootstrapCheck.Check::errorMessage) + .collect(Collectors.toList()); + + if (!errors.isEmpty()) { + final List messages = new ArrayList<>(1 + errors.size()); + messages.add("bootstrap checks failed"); + messages.addAll(errors); + if (enforceLimits) { + final RuntimeException re = new RuntimeException(String.join("\n", messages)); + errors.stream().map(IllegalStateException::new).forEach(re::addSuppressed); + throw re; + } else { + messages.forEach(message -> logger.warn(message)); } } } diff --git a/core/src/test/java/org/elasticsearch/bootstrap/BootstrapCheckTests.java b/core/src/test/java/org/elasticsearch/bootstrap/BootstrapCheckTests.java index 28ff6b17d7e..4f2c6f67bf2 100644 --- a/core/src/test/java/org/elasticsearch/bootstrap/BootstrapCheckTests.java +++ b/core/src/test/java/org/elasticsearch/bootstrap/BootstrapCheckTests.java @@ -26,12 +26,16 @@ import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.test.ESTestCase; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.not; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -97,6 +101,42 @@ public class BootstrapCheckTests extends ESTestCase { assertTrue(BootstrapCheck.enforceLimits(boundTransportAddress)); } + public void testExceptionAggregation() { + final List checks = Arrays.asList( + new BootstrapCheck.Check() { + @Override + public boolean check() { + return true; + } + + @Override + public String errorMessage() { + return "first"; + } + }, + new BootstrapCheck.Check() { + @Override + public boolean check() { + return true; + } + + @Override + public String errorMessage() { + return "second"; + } + } + ); + final RuntimeException e = + expectThrows(RuntimeException.class, () -> BootstrapCheck.check(true, checks, "testExceptionAggregation")); + assertThat(e, hasToString(allOf(containsString("bootstrap checks failed"), containsString("first"), containsString("second")))); + final Throwable[] suppressed = e.getSuppressed(); + assertThat(suppressed.length, equalTo(2)); + assertThat(suppressed[0], instanceOf(IllegalStateException.class)); + assertThat(suppressed[0], hasToString(containsString("first"))); + assertThat(suppressed[1], instanceOf(IllegalStateException.class)); + assertThat(suppressed[1], hasToString(containsString("second"))); + } + public void testFileDescriptorLimits() { final boolean osX = randomBoolean(); // simulates OS X versus non-OS X final int limit = osX ? 10240 : 1 << 16;