/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.node; import org.apache.logging.log4j.Logger; import org.apache.lucene.util.LuceneTestCase; import org.elasticsearch.Version; import org.elasticsearch.bootstrap.BootstrapCheck; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.InternalTestCluster; import org.elasticsearch.transport.MockTcpTransportPlugin; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasToString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @LuceneTestCase.SuppressFileSystems(value = "ExtrasFS") public class NodeTests extends ESTestCase { public void testNodeName() throws IOException { final Path tempDir = createTempDir(); final String name = randomBoolean() ? randomAlphaOfLength(10) : null; Settings.Builder settings = Settings.builder() .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), InternalTestCluster.clusterName("single-node-cluster", randomLong())) .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) .put(NetworkModule.HTTP_ENABLED.getKey(), false) .put("transport.type", "mock-socket-network") .put(Node.NODE_DATA_SETTING.getKey(), true); if (name != null) { settings.put(Node.NODE_NAME_SETTING.getKey(), name); } try (Node node = new MockNode(settings.build(), Collections.singleton(MockTcpTransportPlugin.class))) { final Settings nodeSettings = randomBoolean() ? node.settings() : node.getEnvironment().settings(); if (name == null) { assertThat(Node.NODE_NAME_SETTING.get(nodeSettings), equalTo(node.getNodeEnvironment().nodeId().substring(0, 7))); } else { assertThat(Node.NODE_NAME_SETTING.get(nodeSettings), equalTo(name)); } } } public static class CheckPlugin extends Plugin { public static final BootstrapCheck CHECK = new BootstrapCheck() { @Override public boolean check() { return false; } @Override public String errorMessage() { return "boom"; } }; @Override public List<BootstrapCheck> getBootstrapChecks() { return Collections.singletonList(CHECK); } } public void testLoadPluginBootstrapChecks() throws IOException { final Path tempDir = createTempDir(); final String name = randomBoolean() ? randomAlphaOfLength(10) : null; Settings.Builder settings = Settings.builder() .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), InternalTestCluster.clusterName("single-node-cluster", randomLong())) .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) .put(NetworkModule.HTTP_ENABLED.getKey(), false) .put("transport.type", "mock-socket-network") .put(Node.NODE_DATA_SETTING.getKey(), true); if (name != null) { settings.put(Node.NODE_NAME_SETTING.getKey(), name); } AtomicBoolean executed = new AtomicBoolean(false); try (Node node = new MockNode(settings.build(), Arrays.asList(MockTcpTransportPlugin.class, CheckPlugin.class)) { @Override protected void validateNodeBeforeAcceptingRequests(Settings settings, BoundTransportAddress boundTransportAddress, List<BootstrapCheck> bootstrapChecks) throws NodeValidationException { assertEquals(1, bootstrapChecks.size()); assertSame(CheckPlugin.CHECK, bootstrapChecks.get(0)); executed.set(true); throw new NodeValidationException("boom"); } }) { expectThrows(NodeValidationException.class, () -> node.start()); assertTrue(executed.get()); } } public void testWarnIfPreRelease() { final Logger logger = mock(Logger.class); final int id = randomIntBetween(1, 9) * 1000000; final Version releaseVersion = Version.fromId(id + 99); final Version preReleaseVersion = Version.fromId(id + randomIntBetween(0, 98)); Node.warnIfPreRelease(releaseVersion, false, logger); verifyNoMoreInteractions(logger); reset(logger); Node.warnIfPreRelease(releaseVersion, true, logger); verify(logger).warn( "version [{}] is a pre-release version of Elasticsearch and is not suitable for production", releaseVersion + "-SNAPSHOT"); reset(logger); final boolean isSnapshot = randomBoolean(); Node.warnIfPreRelease(preReleaseVersion, isSnapshot, logger); verify(logger).warn( "version [{}] is a pre-release version of Elasticsearch and is not suitable for production", preReleaseVersion + (isSnapshot ? "-SNAPSHOT" : "")); } public void testNodeAttributes() throws IOException { String attr = randomAlphaOfLength(5); Settings.Builder settings = baseSettings().put(Node.NODE_ATTRIBUTES.getKey() + "test_attr", attr); try (Node node = new MockNode(settings.build(), Collections.singleton(MockTcpTransportPlugin.class))) { final Settings nodeSettings = randomBoolean() ? node.settings() : node.getEnvironment().settings(); assertEquals(attr, Node.NODE_ATTRIBUTES.get(nodeSettings).getAsMap().get("test_attr")); } // leading whitespace not allowed attr = " leading"; settings = baseSettings().put(Node.NODE_ATTRIBUTES.getKey() + "test_attr", attr); try (Node node = new MockNode(settings.build(), Collections.singleton(MockTcpTransportPlugin.class))) { fail("should not allow a node attribute with leading whitespace"); } catch (IllegalArgumentException e) { assertEquals("node.attr.test_attr cannot have leading or trailing whitespace [ leading]", e.getMessage()); } // trailing whitespace not allowed attr = "trailing "; settings = baseSettings().put(Node.NODE_ATTRIBUTES.getKey() + "test_attr", attr); try (Node node = new MockNode(settings.build(), Collections.singleton(MockTcpTransportPlugin.class))) { fail("should not allow a node attribute with trailing whitespace"); } catch (IllegalArgumentException e) { assertEquals("node.attr.test_attr cannot have leading or trailing whitespace [trailing ]", e.getMessage()); } } public void testDefaultPathDataSet() throws IOException { final Path zero = createTempDir().toAbsolutePath(); final Path one = createTempDir().toAbsolutePath(); final Path defaultPathData = createTempDir().toAbsolutePath(); final Settings settings = Settings.builder() .put("path.home", "/home") .put("path.data.0", zero) .put("path.data.1", one) .put("default.path.data", defaultPathData) .build(); try (NodeEnvironment nodeEnv = new NodeEnvironment(settings, new Environment(settings))) { final Path defaultPathDataWithNodesAndId = defaultPathData.resolve("nodes/0"); Files.createDirectories(defaultPathDataWithNodesAndId); final NodeEnvironment.NodePath defaultNodePath = new NodeEnvironment.NodePath(defaultPathDataWithNodesAndId); final boolean indexExists = randomBoolean(); final List<String> indices; if (indexExists) { indices = IntStream.range(0, randomIntBetween(1, 3)).mapToObj(i -> UUIDs.randomBase64UUID()).collect(Collectors.toList()); for (final String index : indices) { Files.createDirectories(defaultNodePath.indicesPath.resolve(index)); } } else { indices = Collections.emptyList(); } final Logger mock = mock(Logger.class); if (indexExists) { final IllegalStateException e = expectThrows( IllegalStateException.class, () -> Node.checkForIndexDataInDefaultPathData(settings, nodeEnv, mock)); final String message = String.format( Locale.ROOT, "detected index data in default.path.data [%s] where there should not be any; check the logs for details", defaultPathData); assertThat(e, hasToString(containsString(message))); verify(mock) .error("detected index data in default.path.data [{}] where there should not be any", defaultNodePath.indicesPath); for (final String index : indices) { verify(mock).info( "index folder [{}] in default.path.data [{}] must be moved to any of {}", index, defaultNodePath.indicesPath, Arrays.stream(nodeEnv.nodePaths()).map(np -> np.indicesPath).collect(Collectors.toList())); } verifyNoMoreInteractions(mock); } else { Node.checkForIndexDataInDefaultPathData(settings, nodeEnv, mock); verifyNoMoreInteractions(mock); } } } public void testDefaultPathDataNotSet() throws IOException { final Path zero = createTempDir().toAbsolutePath(); final Path one = createTempDir().toAbsolutePath(); final Settings settings = Settings.builder() .put("path.home", "/home") .put("path.data.0", zero) .put("path.data.1", one) .build(); try (NodeEnvironment nodeEnv = new NodeEnvironment(settings, new Environment(settings))) { final Logger mock = mock(Logger.class); Node.checkForIndexDataInDefaultPathData(settings, nodeEnv, mock); verifyNoMoreInteractions(mock); } } private static Settings.Builder baseSettings() { final Path tempDir = createTempDir(); return Settings.builder() .put(ClusterName.CLUSTER_NAME_SETTING.getKey(), InternalTestCluster.clusterName("single-node-cluster", randomLong())) .put(Environment.PATH_HOME_SETTING.getKey(), tempDir) .put(NetworkModule.HTTP_ENABLED.getKey(), false) .put("transport.type", "mock-socket-network") .put(Node.NODE_DATA_SETTING.getKey(), true); } }