diff --git a/buildSrc/src/main/resources/checkstyle_suppressions.xml b/buildSrc/src/main/resources/checkstyle_suppressions.xml index ab6dc1625d4..4bbd52affdd 100644 --- a/buildSrc/src/main/resources/checkstyle_suppressions.xml +++ b/buildSrc/src/main/resources/checkstyle_suppressions.xml @@ -1002,7 +1002,6 @@ - diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java index 131efba8a3f..2c87684b3d8 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java @@ -267,6 +267,11 @@ public abstract class ESIntegTestCase extends ESTestCase { */ public static final String TESTS_CLIENT_RATIO = "tests.client.ratio"; + /** + * Key used to eventually switch to using an external cluster and provide its transport addresses + */ + public static final String TESTS_CLUSTER = "tests.cluster"; + /** * Key used to retrieve the index random seed from the index settings on a running node. * The value of this seed can be used to initialize a random context for a specific index. @@ -1713,11 +1718,35 @@ public abstract class ESIntegTestCase extends ESTestCase { return Settings.EMPTY; } + private ExternalTestCluster buildExternalCluster(String clusterAddresses) throws IOException { + String[] stringAddresses = clusterAddresses.split(","); + TransportAddress[] transportAddresses = new TransportAddress[stringAddresses.length]; + int i = 0; + for (String stringAddress : stringAddresses) { + URL url = new URL("http://" + stringAddress); + InetAddress inetAddress = InetAddress.getByName(url.getHost()); + transportAddresses[i++] = new TransportAddress(new InetSocketAddress(inetAddress, url.getPort())); + } + return new ExternalTestCluster(createTempDir(), externalClusterClientSettings(), transportClientPlugins(), transportAddresses); + } + protected Settings externalClusterClientSettings() { return Settings.EMPTY; } + protected boolean ignoreExternalCluster() { + return false; + } + protected TestCluster buildTestCluster(Scope scope, long seed) throws IOException { + String clusterAddresses = System.getProperty(TESTS_CLUSTER); + if (Strings.hasLength(clusterAddresses) && ignoreExternalCluster() == false) { + if (scope == Scope.TEST) { + throw new IllegalArgumentException("Cannot run TEST scope test with " + TESTS_CLUSTER); + } + return buildExternalCluster(clusterAddresses); + } + final String nodePrefix; switch (scope) { case TEST: diff --git a/test/framework/src/main/java/org/elasticsearch/test/ExternalTestCluster.java b/test/framework/src/main/java/org/elasticsearch/test/ExternalTestCluster.java new file mode 100644 index 00000000000..47fbe3e20e9 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/test/ExternalTestCluster.java @@ -0,0 +1,185 @@ +/* + * 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.test; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; +import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.network.NetworkModule; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.env.Environment; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.transport.MockTcpTransportPlugin; +import org.elasticsearch.transport.MockTransportClient; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * External cluster to run the tests against. + * It is a pure immutable test cluster that allows to send requests to a pre-existing cluster + * and supports by nature all the needed test operations like wipeIndices etc. + */ +public final class ExternalTestCluster extends TestCluster { + + private static final Logger logger = Loggers.getLogger(ExternalTestCluster.class); + + private static final AtomicInteger counter = new AtomicInteger(); + public static final String EXTERNAL_CLUSTER_PREFIX = "external_"; + + private final Client client; + + private final InetSocketAddress[] httpAddresses; + + private final String clusterName; + + private final int numDataNodes; + private final int numMasterAndDataNodes; + + public ExternalTestCluster(Path tempDir, Settings additionalSettings, Collection> pluginClasses, + TransportAddress... transportAddresses) { + super(0); + Settings.Builder clientSettingsBuilder = Settings.builder() + .put(additionalSettings) + .put("node.name", InternalTestCluster.TRANSPORT_CLIENT_PREFIX + EXTERNAL_CLUSTER_PREFIX + counter.getAndIncrement()) + .put("client.transport.ignore_cluster_name", true) + .put(Environment.PATH_HOME_SETTING.getKey(), tempDir); + boolean addMockTcpTransport = additionalSettings.get(NetworkModule.TRANSPORT_TYPE_KEY) == null; + + if (addMockTcpTransport) { + clientSettingsBuilder.put(NetworkModule.TRANSPORT_TYPE_KEY, MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME); + if (pluginClasses.contains(MockTcpTransportPlugin.class) == false) { + pluginClasses = new ArrayList<>(pluginClasses); + pluginClasses.add(MockTcpTransportPlugin.class); + } + } + Settings clientSettings = clientSettingsBuilder.build(); + TransportClient client = new MockTransportClient(clientSettings, pluginClasses); + + try { + client.addTransportAddresses(transportAddresses); + NodesInfoResponse nodeInfos = client.admin().cluster().prepareNodesInfo().clear().setSettings(true).setHttp(true).get(); + httpAddresses = new InetSocketAddress[nodeInfos.getNodes().size()]; + this.clusterName = nodeInfos.getClusterName().value(); + int dataNodes = 0; + int masterAndDataNodes = 0; + for (int i = 0; i < nodeInfos.getNodes().size(); i++) { + NodeInfo nodeInfo = nodeInfos.getNodes().get(i); + httpAddresses[i] = nodeInfo.getHttp().address().publishAddress().address(); + if (DiscoveryNode.isDataNode(nodeInfo.getSettings())) { + dataNodes++; + masterAndDataNodes++; + } else if (DiscoveryNode.isMasterNode(nodeInfo.getSettings())) { + masterAndDataNodes++; + } + } + this.numDataNodes = dataNodes; + this.numMasterAndDataNodes = masterAndDataNodes; + this.client = client; + logger.info("Setup ExternalTestCluster [{}] made of [{}] nodes", nodeInfos.getClusterName().value(), size()); + } catch (Exception e) { + client.close(); + throw e; + } + } + + @Override + public void afterTest() { + + } + + @Override + public Client client() { + return client; + } + + @Override + public int size() { + return httpAddresses.length; + } + + @Override + public int numDataNodes() { + return numDataNodes; + } + + @Override + public int numDataAndMasterNodes() { + return numMasterAndDataNodes; + } + + @Override + public InetSocketAddress[] httpAddresses() { + return httpAddresses; + } + + @Override + public void close() throws IOException { + client.close(); + } + + @Override + public void ensureEstimatedStats() { + if (size() > 0) { + NodesStatsResponse nodeStats = client().admin().cluster().prepareNodesStats() + .clear().setBreaker(true).setIndices(true).execute().actionGet(); + for (NodeStats stats : nodeStats.getNodes()) { + assertThat("Fielddata breaker not reset to 0 on node: " + stats.getNode(), + stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated(), equalTo(0L)); + // ExternalTestCluster does not check the request breaker, + // because checking it requires a network request, which in + // turn increments the breaker, making it non-0 + + assertThat("Fielddata size must be 0 on node: " + + stats.getNode(), stats.getIndices().getFieldData().getMemorySizeInBytes(), equalTo(0L)); + assertThat("Query cache size must be 0 on node: " + + stats.getNode(), stats.getIndices().getQueryCache().getMemorySizeInBytes(), equalTo(0L)); + assertThat("FixedBitSet cache size must be 0 on node: " + + stats.getNode(), stats.getIndices().getSegments().getBitsetMemoryInBytes(), equalTo(0L)); + } + } + } + + @Override + public Iterable getClients() { + return Collections.singleton(client); + } + + @Override + public String getClusterName() { + return clusterName; + } +}