/* * 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.action.admin.indices.create; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.UnavailableShardsException; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.RangeQueryBuilder; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.ESIntegTestCase.Scope; import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_WAIT_FOR_ACTIVE_SHARDS; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBlocked; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.core.IsNull.notNullValue; @ClusterScope(scope = Scope.TEST) public class CreateIndexIT extends ESIntegTestCase { public void testCreationDateGivenFails() { try { prepareCreate("test").setSettings(Settings.builder().put(IndexMetaData.SETTING_CREATION_DATE, 4L)).get(); fail(); } catch (IllegalArgumentException ex) { assertEquals("unknown setting [index.creation_date] please check that any required plugins are installed, or check the " + "breaking changes documentation for removed settings", ex.getMessage()); } } public void testCreationDateGenerated() { long timeBeforeRequest = System.currentTimeMillis(); prepareCreate("test").get(); long timeAfterRequest = System.currentTimeMillis(); ClusterStateResponse response = client().admin().cluster().prepareState().get(); ClusterState state = response.getState(); assertThat(state, notNullValue()); MetaData metadata = state.getMetaData(); assertThat(metadata, notNullValue()); ImmutableOpenMap<String, IndexMetaData> indices = metadata.getIndices(); assertThat(indices, notNullValue()); assertThat(indices.size(), equalTo(1)); IndexMetaData index = indices.get("test"); assertThat(index, notNullValue()); assertThat(index.getCreationDate(), allOf(lessThanOrEqualTo(timeAfterRequest), greaterThanOrEqualTo(timeBeforeRequest))); } public void testDoubleAddMapping() throws Exception { try { prepareCreate("test") .addMapping("type1", "date", "type=date") .addMapping("type1", "num", "type=integer"); fail("did not hit expected exception"); } catch (IllegalStateException ise) { // expected } try { prepareCreate("test") .addMapping("type1", new HashMap<String,Object>()) .addMapping("type1", new HashMap<String,Object>()); fail("did not hit expected exception"); } catch (IllegalStateException ise) { // expected } try { prepareCreate("test") .addMapping("type1", jsonBuilder()) .addMapping("type1", jsonBuilder()); fail("did not hit expected exception"); } catch (IllegalStateException ise) { // expected } } public void testInvalidShardCountSettings() throws Exception { int value = randomIntBetween(-10, 0); try { prepareCreate("test").setSettings(Settings.builder() .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, value) .build()) .get(); fail("should have thrown an exception about the primary shard count"); } catch (IllegalArgumentException e) { assertEquals("Failed to parse value [" + value + "] for setting [index.number_of_shards] must be >= 1", e.getMessage()); } value = randomIntBetween(-10, -1); try { prepareCreate("test").setSettings(Settings.builder() .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, value) .build()) .get(); fail("should have thrown an exception about the replica shard count"); } catch (IllegalArgumentException e) { assertEquals("Failed to parse value [" + value + "] for setting [index.number_of_replicas] must be >= 0", e.getMessage()); } } public void testCreateIndexWithBlocks() { try { setClusterReadOnly(true); assertBlocked(prepareCreate("test")); } finally { setClusterReadOnly(false); } } public void testCreateIndexWithMetadataBlocks() { assertAcked(prepareCreate("test").setSettings(Settings.builder().put(IndexMetaData.SETTING_BLOCKS_METADATA, true))); assertBlocked(client().admin().indices().prepareGetSettings("test"), IndexMetaData.INDEX_METADATA_BLOCK); disableIndexBlock("test", IndexMetaData.SETTING_BLOCKS_METADATA); } public void testUnknownSettingFails() { try { prepareCreate("test").setSettings(Settings.builder() .put("index.unknown.value", "this must fail") .build()) .get(); fail("should have thrown an exception about the shard count"); } catch (IllegalArgumentException e) { assertEquals("unknown setting [index.unknown.value] please check that any required plugins are installed, or check the" + " breaking changes documentation for removed settings", e.getMessage()); } } public void testInvalidShardCountSettingsWithoutPrefix() throws Exception { int value = randomIntBetween(-10, 0); try { prepareCreate("test").setSettings(Settings.builder() .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS.substring(IndexMetaData.INDEX_SETTING_PREFIX.length()), value) .build()) .get(); fail("should have thrown an exception about the shard count"); } catch (IllegalArgumentException e) { assertEquals("Failed to parse value [" + value + "] for setting [index.number_of_shards] must be >= 1", e.getMessage()); } value = randomIntBetween(-10, -1); try { prepareCreate("test").setSettings(Settings.builder() .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS.substring(IndexMetaData.INDEX_SETTING_PREFIX.length()), value) .build()) .get(); fail("should have thrown an exception about the shard count"); } catch (IllegalArgumentException e) { assertEquals("Failed to parse value [" + value + "] for setting [index.number_of_replicas] must be >= 0", e.getMessage()); } } public void testCreateAndDeleteIndexConcurrently() throws InterruptedException { createIndex("test"); final AtomicInteger indexVersion = new AtomicInteger(0); final Object indexVersionLock = new Object(); final CountDownLatch latch = new CountDownLatch(1); int numDocs = randomIntBetween(1, 10); for (int i = 0; i < numDocs; i++) { client().prepareIndex("test", "test").setSource("index_version", indexVersion.get()).get(); } synchronized (indexVersionLock) { // not necessarily needed here but for completeness we lock here too indexVersion.incrementAndGet(); } client().admin().indices().prepareDelete("test").execute(new ActionListener<DeleteIndexResponse>() { // this happens async!!! @Override public void onResponse(DeleteIndexResponse deleteIndexResponse) { Thread thread = new Thread() { @Override public void run() { try { // recreate that index client().prepareIndex("test", "test").setSource("index_version", indexVersion.get()).get(); synchronized (indexVersionLock) { // we sync here since we have to ensure that all indexing operations below for a given ID are done before // we increment the index version otherwise a doc that is in-flight could make it into an index that it // was supposed to be deleted for and our assertion fail... indexVersion.incrementAndGet(); } // from here on all docs with index_version == 0|1 must be gone!!!! only 2 are ok; assertAcked(client().admin().indices().prepareDelete("test").get()); } finally { latch.countDown(); } } }; thread.start(); } @Override public void onFailure(Exception e) { throw new RuntimeException(e); } } ); numDocs = randomIntBetween(100, 200); for (int i = 0; i < numDocs; i++) { try { synchronized (indexVersionLock) { client().prepareIndex("test", "test").setSource("index_version", indexVersion.get()) .setTimeout(TimeValue.timeValueSeconds(10)).get(); } } catch (IndexNotFoundException inf) { // fine } catch (UnavailableShardsException ex) { assertEquals(ex.getCause().getClass(), IndexNotFoundException.class); // fine we run into a delete index while retrying } } latch.await(); refresh(); // we only really assert that we never reuse segments of old indices or anything like this here and that nothing fails with // crazy exceptions SearchResponse expected = client().prepareSearch("test").setIndicesOptions(IndicesOptions.lenientExpandOpen()) .setQuery(new RangeQueryBuilder("index_version").from(indexVersion.get(), true)).get(); SearchResponse all = client().prepareSearch("test").setIndicesOptions(IndicesOptions.lenientExpandOpen()).get(); assertEquals(expected + " vs. " + all, expected.getHits().getTotalHits(), all.getHits().getTotalHits()); logger.info("total: {}", expected.getHits().getTotalHits()); } /** * Asserts that the root cause of mapping conflicts is readable. */ public void testMappingConflictRootCause() throws Exception { CreateIndexRequestBuilder b = prepareCreate("test"); b.addMapping("type1", jsonBuilder().startObject().startObject("properties") .startObject("text") .field("type", "text") .field("analyzer", "standard") .field("search_analyzer", "whitespace") .endObject().endObject().endObject()); b.addMapping("type2", jsonBuilder().humanReadable(true).startObject().startObject("properties") .startObject("text") .field("type", "text") .endObject().endObject().endObject()); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> b.get()); assertThat(e.getMessage(), containsString("mapper [text] is used by multiple types")); } public void testRestartIndexCreationAfterFullClusterRestart() throws Exception { client().admin().cluster().prepareUpdateSettings().setTransientSettings(Settings.builder().put("cluster.routing.allocation.enable", "none")).get(); client().admin().indices().prepareCreate("test").setWaitForActiveShards(ActiveShardCount.NONE).setSettings(indexSettings()).get(); internalCluster().fullRestart(); ensureGreen("test"); } /** * This test ensures that index creation adheres to the {@link IndexMetaData#SETTING_WAIT_FOR_ACTIVE_SHARDS}. */ public void testDefaultWaitForActiveShardsUsesIndexSetting() throws Exception { final int numReplicas = internalCluster().numDataNodes(); Settings settings = Settings.builder() .put(SETTING_WAIT_FOR_ACTIVE_SHARDS.getKey(), Integer.toString(numReplicas)) .put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1) .put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), numReplicas) .build(); assertAcked(client().admin().indices().prepareCreate("test-idx-1").setSettings(settings).get()); // all should fail settings = Settings.builder() .put(settings) .put(SETTING_WAIT_FOR_ACTIVE_SHARDS.getKey(), "all") .build(); assertFalse(client().admin().indices().prepareCreate("test-idx-2").setSettings(settings).setTimeout("100ms").get().isShardsAcked()); // the numeric equivalent of all should also fail settings = Settings.builder() .put(settings) .put(SETTING_WAIT_FOR_ACTIVE_SHARDS.getKey(), Integer.toString(numReplicas + 1)) .build(); assertFalse(client().admin().indices().prepareCreate("test-idx-3").setSettings(settings).setTimeout("100ms").get().isShardsAcked()); } public void testInvalidPartitionSize() { BiFunction<Integer, Integer, Boolean> createPartitionedIndex = (shards, partitionSize) -> { CreateIndexResponse response; try { response = prepareCreate("test_" + shards + "_" + partitionSize) .setSettings(Settings.builder() .put("index.number_of_shards", shards) .put("index.routing_partition_size", partitionSize)) .execute().actionGet(); } catch (IllegalStateException | IllegalArgumentException e) { return false; } return response.isAcknowledged(); }; assertFalse(createPartitionedIndex.apply(3, 6)); assertFalse(createPartitionedIndex.apply(3, 0)); assertFalse(createPartitionedIndex.apply(3, 3)); assertTrue(createPartitionedIndex.apply(3, 1)); assertTrue(createPartitionedIndex.apply(3, 2)); assertTrue(createPartitionedIndex.apply(1, 1)); } }