/* * 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 static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThanOrEqualTo; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Objects; import java.util.concurrent.Semaphore; import javax.xml.bind.DatatypeConverter; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ElassandraDaemon; import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequestBuilder; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder; import org.elasticsearch.cache.recycler.PageCacheRecycler; import org.elasticsearch.client.Client; import org.elasticsearch.client.ClusterAdminClient; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.common.Priority; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexService; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.node.Node; import org.elasticsearch.node.internal.InternalSettingsPreparer; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.threadpool.ThreadPool; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; /** * A test that keep a singleton node started for all tests that can be used to get * references to Guice injectors in unit tests. * * Cassandra use many static initializer, so NODE is statically initialized once * for all tests and multinode cluster test is not possible. */ public abstract class ESSingleNodeTestCase extends ESTestCase { public interface ActionRequestBuilderHelper { public void addHeader(ActionRequestBuilder builder); } private static final Semaphore available = new Semaphore(1); protected static Node NODE; protected static Settings settings; public static ActionRequestBuilderHelper actionRequestHelper = null; static void initNode(Settings testSettings, Collection<Class<? extends Plugin>> classpathPlugins) { System.out.println("working.dir="+System.getProperty("user.dir")); System.out.println("cassandra.home="+System.getProperty("cassandra.home")); System.out.println("cassandra.config.loader="+System.getProperty("cassandra.config.loader")); System.out.println("cassandra.config="+System.getProperty("cassandra.config")); System.out.println("cassandra.config.dir="+System.getProperty("cassandra.config.dir")); System.out.println("cassandra-rackdc.properties="+System.getProperty("cassandra-rackdc.properties")); System.out.println("cassandra.storagedir="+System.getProperty("cassandra.storagedir")); System.out.println("logback.configurationFile="+System.getProperty("logback.configurationFile")); System.out.println("settings="+testSettings.getAsMap()); System.out.println("plugins="+classpathPlugins); DatabaseDescriptor.createAllDirectories(); settings = Settings.builder() .put(ClusterName.SETTING, DatabaseDescriptor.getClusterName()) .put("path.home", System.getProperty("cassandra.home")) .put("path.conf", System.getProperty("cassandra.config.dir")) .put("path.data", DatabaseDescriptor.getAllDataFileLocations()[0]) // TODO: use a consistent data path for custom paths // This needs to tie into the ESIntegTestCase#indexSettings() method .put("path.shared_data", DatabaseDescriptor.getAllDataFileLocations()[0]) .put("node.name", nodeName()) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put("script.inline", "on") .put("script.indexed", "on") //.put(EsExecutors.PROCESSORS, 1) // limit the number of threads created .put("http.enabled", true) .put(InternalSettingsPreparer.IGNORE_SYSTEM_PROPERTIES_SETTING, true) .put(testSettings) .build(); ElassandraDaemon.instance.activate(false, settings, new Environment(settings), classpathPlugins); try { Thread.sleep(1000*15); } catch (InterruptedException e) { } assertThat(DiscoveryNode.localNode(settings), is(false)); NODE = ElassandraDaemon.instance.node(); ClusterAdminClient clusterAdminClient = client().admin().cluster(); ClusterHealthRequestBuilder builder = clusterAdminClient.prepareHealth(); expand(builder); ClusterHealthResponse clusterHealthResponse = builder.setWaitForGreenStatus().get(); assertFalse(clusterHealthResponse.isTimedOut()); } public ESSingleNodeTestCase() { super(); } // override this to initialize the single node cluster. protected Settings nodeSettings(int nodeOrdinal) { return Settings.EMPTY; } static void reset() { } static void cleanup(boolean resetNode) { if (NODE != null) { DeleteIndexRequestBuilder builder = client().admin().indices().prepareDelete("*"); expand(builder); assertAcked(builder.get()); if (resetNode) { reset(); } } } public static String encodeBasicHeader(final String username, final String password) { return new String(DatatypeConverter.printBase64Binary((username + ":" + Objects.requireNonNull(password)).getBytes(StandardCharsets.UTF_8))); } @Before public void nodeSetup() throws Exception { try { available.acquire(); if (NODE == null) { initNode(nodeSettings(1), nodePlugins()); // register NodeEnvironment to remove node.lock closeAfterTest(NODE.nodeEnvironment()); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } logger.info("[{}#{}]: setup test {}", getTestClass().getSimpleName(), getTestName()); } @After public void nodeTearDown() throws Exception { logger.info("[{}#{}]: cleaning up after test {}", getTestClass().getSimpleName(), getTestName()); cleanup(resetNodeAfterTest()); super.tearDown(); available.release(); logger.info("[{}#{}]: semaphore released={}", getTestClass().getSimpleName(), getTestName(), available.getQueueLength()); } @BeforeClass public static synchronized void setUpClass() throws Exception { } @AfterClass public static void tearDownClass() { } public static void expand(ActionRequestBuilder builder) { if (actionRequestHelper != null) actionRequestHelper.addHeader(builder); } /** * This method returns <code>true</code> if the node that is used in the background should be reset * after each test. This is useful if the test changes the cluster state metadata etc. The default is * <code>false</code>. */ protected boolean resetNodeAfterTest() { return true; } /** * Returns a collection of plugins that should be loaded on each node. */ protected Collection<Class<? extends Plugin>> nodePlugins() { return Collections.emptyList(); } /** * Returns a collection of plugins that should be loaded when creating a transport client. */ protected Collection<Class<? extends Plugin>> transportClientPlugins() { return Collections.emptyList(); } /** Helper method to create list of plugins without specifying generic types. */ protected static Collection<Class<? extends Plugin>> pluginList(Class<? extends Plugin>... plugins) { return Arrays.asList(plugins); } /** * Returns a client to the single-node cluster. */ public static Client client() { return NODE.client(); } public ClusterService clusterService() { return ElassandraDaemon.instance.node().clusterService(); } public UntypedResultSet process(ConsistencyLevel cl, String query) throws RequestExecutionException, RequestValidationException, InvalidRequestException { return clusterService().process(cl, ClientState.forInternalCalls(), query); } public UntypedResultSet process(ConsistencyLevel cl, ClientState clientState, String query) throws RequestExecutionException, RequestValidationException, InvalidRequestException { return clusterService().process(cl, clientState, query); } public UntypedResultSet process(ConsistencyLevel cl, String query, Object... values) throws RequestExecutionException, RequestValidationException, InvalidRequestException { return clusterService().process(cl, ClientState.forInternalCalls(), query, values); } public UntypedResultSet process(ConsistencyLevel cl, ClientState clientState, String query, Object... values) throws RequestExecutionException, RequestValidationException, InvalidRequestException { return clusterService().process(cl, clientState, query, values); } /** * Returns the single test nodes name. */ public static String nodeName() { return "node_s_0"; } /** * Return a reference to the singleton node. */ protected static Node node() { return NODE; } /** * Get an instance for a particular class using the injector of the singleton node. */ protected static <T> T getInstanceFromNode(Class<T> clazz) { return NODE.injector().getInstance(clazz); } /** * Create a new index on the singleton node with empty index settings. */ protected static IndexService createIndex(String index) { return createIndex(index, Settings.EMPTY); } /** * Create a new index on the singleton node with the provided index settings. */ protected static IndexService createIndex(String index, Settings settings) { return createIndex(index, settings, null, (XContentBuilder) null); } /** * Create a new index on the singleton node with the provided index settings. */ protected static IndexService createIndex(String index, Settings settings, String type, XContentBuilder mappings) { CreateIndexRequestBuilder createIndexRequestBuilder = client().admin().indices().prepareCreate(index).setSettings(settings); if (type != null && mappings != null) { createIndexRequestBuilder.addMapping(type, mappings); } return createIndex(index, createIndexRequestBuilder); } /** * Create a new index on the singleton node with the provided index settings. */ protected static IndexService createIndex(String index, Settings settings, String type, Object... mappings) { CreateIndexRequestBuilder createIndexRequestBuilder = client().admin().indices().prepareCreate(index).setSettings(settings); if (type != null && mappings != null) { createIndexRequestBuilder.addMapping(type, mappings); } return createIndex(index, createIndexRequestBuilder); } protected static IndexService createIndex(String index, CreateIndexRequestBuilder createIndexRequestBuilder) { expand(createIndexRequestBuilder); assertAcked(createIndexRequestBuilder.get()); // Wait for the index to be allocated so that cluster state updates don't override // changes that would have been done locally ClusterHealthRequestBuilder builder = client().admin().cluster().prepareHealth(index); expand(builder); builder.setWaitForYellowStatus() .setWaitForEvents(Priority.LANGUID) .setWaitForRelocatingShards(0); ClusterHealthResponse health = builder.get(); assertThat(health.getStatus(), lessThanOrEqualTo(ClusterHealthStatus.YELLOW)); assertThat("Cluster must be a single node cluster", health.getNumberOfDataNodes(), equalTo(1)); IndicesService instanceFromNode = getInstanceFromNode(IndicesService.class); return instanceFromNode.indexServiceSafe(index); } protected static org.elasticsearch.index.engine.Engine engine(IndexService service) { return service.shard(0).engine(); } /** * Create a new search context. */ protected static SearchContext createSearchContext(IndexService indexService) { BigArrays bigArrays = indexService.injector().getInstance(BigArrays.class); ThreadPool threadPool = indexService.injector().getInstance(ThreadPool.class); PageCacheRecycler pageCacheRecycler = indexService.injector().getInstance(PageCacheRecycler.class); return new TestSearchContext(threadPool, pageCacheRecycler, bigArrays, indexService, null); } /** * Ensures the cluster has a green state via the cluster health API. This method will also wait for relocations. * It is useful to ensure that all action on the cluster have finished and all shards that were currently relocating * are now allocated and started. */ public ClusterHealthStatus ensureGreen(String... indices) { return ensureGreen(TimeValue.timeValueSeconds(30), indices); } /** * Ensures the cluster has a green state via the cluster health API. This method will also wait for relocations. * It is useful to ensure that all action on the cluster have finished and all shards that were currently relocating * are now allocated and started. * * @param timeout time out value to set on {@link org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest} */ public ClusterHealthStatus ensureGreen(TimeValue timeout, String... indices) { ClusterHealthRequestBuilder builder = client().admin().cluster().prepareHealth(indices); expand(builder); builder.setTimeout(timeout) .setWaitForGreenStatus() .setWaitForEvents(Priority.LANGUID) .setWaitForRelocatingShards(0); ClusterHealthResponse actionGet = builder.get(); if (actionGet.isTimedOut()) { logger.info("ensureGreen timed out, cluster state:\n{}\n{}", client().admin().cluster().prepareState().get().getState().prettyPrint(), client().admin().cluster().preparePendingClusterTasks().get().prettyPrint()); assertThat("timed out waiting for green state", actionGet.isTimedOut(), equalTo(false)); } assertThat(actionGet.getStatus(), equalTo(ClusterHealthStatus.GREEN)); logger.debug("indices {} are green", indices.length == 0 ? "[_all]" : indices); return actionGet.getStatus(); } }