/*
* 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.cluster.ack;
import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteResponse;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse;
import org.elasticsearch.action.admin.indices.close.CloseIndexResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.AliasOrIndex;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData.State;
import org.elasticsearch.cluster.routing.RoutingNode;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.cluster.routing.ShardRoutingState;
import org.elasticsearch.cluster.routing.allocation.command.MoveAllocationCommand;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.discovery.DiscoverySettings;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
@ClusterScope(minNumDataNodes = 2)
public class AckIT extends ESIntegTestCase {
@Override
protected Settings nodeSettings(int nodeOrdinal) {
//to test that the acknowledgement mechanism is working we better disable the wait for publish
//otherwise the operation is most likely acknowledged even if it doesn't support ack
return Settings.builder().put(super.nodeSettings(nodeOrdinal))
.put(DiscoverySettings.COMMIT_TIMEOUT_SETTING.getKey(), "30s") // explicitly set so it won't default to publish timeout
.put(DiscoverySettings.PUBLISH_TIMEOUT_SETTING.getKey(), "0s") // don't wait post commit to check acking
.build();
}
public void testUpdateSettingsAcknowledgement() {
createIndex("test");
assertAcked(client().admin().indices().prepareUpdateSettings("test")
.setSettings(Settings.builder().put("refresh_interval", 9999, TimeUnit.MILLISECONDS)));
for (Client client : clients()) {
String refreshInterval = getLocalClusterState(client).metaData().index("test").getSettings().get("index.refresh_interval");
assertThat(refreshInterval, equalTo("9999ms"));
}
}
public void testUpdateSettingsNoAcknowledgement() {
createIndex("test");
UpdateSettingsResponse updateSettingsResponse = client().admin().indices().prepareUpdateSettings("test").setTimeout("0s")
.setSettings(Settings.builder().put("refresh_interval", 9999, TimeUnit.MILLISECONDS)).get();
assertThat(updateSettingsResponse.isAcknowledged(), equalTo(false));
}
public void testClusterRerouteAcknowledgement() throws InterruptedException {
assertAcked(prepareCreate("test").setSettings(Settings.builder()
.put(indexSettings())
.put(SETTING_NUMBER_OF_SHARDS, between(cluster().numDataNodes(), DEFAULT_MAX_NUM_SHARDS))
.put(SETTING_NUMBER_OF_REPLICAS, 0)
));
ensureGreen();
MoveAllocationCommand moveAllocationCommand = getAllocationCommand();
final Index index = client().admin().cluster().prepareState().get().getState().metaData().index("test").getIndex();
final ShardId commandShard = new ShardId(index, moveAllocationCommand.shardId());
assertAcked(client().admin().cluster().prepareReroute().add(moveAllocationCommand));
for (Client client : clients()) {
ClusterState clusterState = getLocalClusterState(client);
for (ShardRouting shardRouting : clusterState.getRoutingNodes().node(moveAllocationCommand.fromNode())) {
//if the shard that we wanted to move is still on the same node, it must be relocating
if (shardRouting.shardId().equals(commandShard)) {
assertThat(shardRouting.relocating(), equalTo(true));
}
}
boolean found = false;
for (ShardRouting shardRouting : clusterState.getRoutingNodes().node(moveAllocationCommand.toNode())) {
if (shardRouting.shardId().equals(commandShard)) {
assertThat(shardRouting.state(), anyOf(equalTo(ShardRoutingState.INITIALIZING), equalTo(ShardRoutingState.STARTED)));
found = true;
break;
}
}
assertThat(found, equalTo(true));
}
}
public void testClusterRerouteNoAcknowledgement() throws InterruptedException {
client().admin().indices().prepareCreate("test")
.setSettings(Settings.builder()
.put(SETTING_NUMBER_OF_SHARDS, between(cluster().numDataNodes(), DEFAULT_MAX_NUM_SHARDS))
.put(SETTING_NUMBER_OF_REPLICAS, 0)).get();
ensureGreen();
MoveAllocationCommand moveAllocationCommand = getAllocationCommand();
ClusterRerouteResponse clusterRerouteResponse = client().admin().cluster().prepareReroute().setTimeout("0s").add(moveAllocationCommand).get();
assertThat(clusterRerouteResponse.isAcknowledged(), equalTo(false));
}
public void testClusterRerouteAcknowledgementDryRun() throws InterruptedException {
client().admin().indices().prepareCreate("test")
.setSettings(Settings.builder()
.put(SETTING_NUMBER_OF_SHARDS, between(cluster().numDataNodes(), DEFAULT_MAX_NUM_SHARDS))
.put(SETTING_NUMBER_OF_REPLICAS, 0)).get();
ensureGreen();
MoveAllocationCommand moveAllocationCommand = getAllocationCommand();
final Index index = client().admin().cluster().prepareState().get().getState().metaData().index("test").getIndex();
final ShardId commandShard = new ShardId(index, moveAllocationCommand.shardId());
assertAcked(client().admin().cluster().prepareReroute().setDryRun(true).add(moveAllocationCommand));
//testing only on master with the latest cluster state as we didn't make any change thus we cannot guarantee that
//all nodes hold the same cluster state version. We only know there was no need to change anything, thus no need for ack on this update.
ClusterStateResponse clusterStateResponse = client().admin().cluster().prepareState().get();
boolean found = false;
for (ShardRouting shardRouting : clusterStateResponse.getState().getRoutingNodes().node(moveAllocationCommand.fromNode())) {
//the shard that we wanted to move is still on the same node, as we had dryRun flag
if (shardRouting.shardId().equals(commandShard)) {
assertThat(shardRouting.started(), equalTo(true));
found = true;
break;
}
}
assertThat(found, equalTo(true));
for (ShardRouting shardRouting : clusterStateResponse.getState().getRoutingNodes().node(moveAllocationCommand.toNode())) {
if (shardRouting.shardId().equals(commandShard)) {
fail("shard [" + shardRouting + "] shouldn't be on node [" + moveAllocationCommand.toString() + "]");
}
}
}
public void testClusterRerouteNoAcknowledgementDryRun() throws InterruptedException {
client().admin().indices().prepareCreate("test")
.setSettings(Settings.builder()
.put(SETTING_NUMBER_OF_SHARDS, between(cluster().numDataNodes(), DEFAULT_MAX_NUM_SHARDS))
.put(SETTING_NUMBER_OF_REPLICAS, 0)).get();
ensureGreen();
MoveAllocationCommand moveAllocationCommand = getAllocationCommand();
ClusterRerouteResponse clusterRerouteResponse = client().admin().cluster().prepareReroute().setTimeout("0s").setDryRun(true).add(moveAllocationCommand).get();
//acknowledged anyway as no changes were made
assertThat(clusterRerouteResponse.isAcknowledged(), equalTo(true));
}
private MoveAllocationCommand getAllocationCommand() {
String fromNodeId = null;
String toNodeId = null;
ShardRouting shardToBeMoved = null;
ClusterStateResponse clusterStateResponse = client().admin().cluster().prepareState().get();
for (RoutingNode routingNode : clusterStateResponse.getState().getRoutingNodes()) {
if (routingNode.node().isDataNode()) {
if (fromNodeId == null && routingNode.numberOfOwningShards() > 0) {
fromNodeId = routingNode.nodeId();
shardToBeMoved = routingNode.copyShards().get(randomInt(routingNode.size() - 1));
} else {
toNodeId = routingNode.nodeId();
}
if (toNodeId != null && fromNodeId != null) {
break;
}
}
}
assertNotNull(fromNodeId);
assertNotNull(toNodeId);
assertNotNull(shardToBeMoved);
logger.info("==> going to move shard [{}] from [{}] to [{}]", shardToBeMoved, fromNodeId, toNodeId);
return new MoveAllocationCommand(shardToBeMoved.getIndexName(), shardToBeMoved.id(), fromNodeId, toNodeId);
}
public void testIndicesAliasesAcknowledgement() {
createIndex("test");
//testing acknowledgement when trying to submit an existing alias too
//in that case it would not make any change, but we are sure about the cluster state
//as the previous operation was acknowledged
for (int i = 0; i < 2; i++) {
assertAcked(client().admin().indices().prepareAliases().addAlias("test", "alias"));
for (Client client : clients()) {
AliasMetaData aliasMetaData = ((AliasOrIndex.Alias) getLocalClusterState(client).metaData().getAliasAndIndexLookup().get("alias")).getFirstAliasMetaData();
assertThat(aliasMetaData.alias(), equalTo("alias"));
}
}
}
public void testIndicesAliasesNoAcknowledgement() {
createIndex("test");
IndicesAliasesResponse indicesAliasesResponse = client().admin().indices().prepareAliases().addAlias("test", "alias").setTimeout("0s").get();
assertThat(indicesAliasesResponse.isAcknowledged(), equalTo(false));
}
public void testCloseIndexAcknowledgement() {
createIndex("test");
ensureGreen();
assertAcked(client().admin().indices().prepareClose("test"));
for (Client client : clients()) {
IndexMetaData indexMetaData = getLocalClusterState(client).metaData().indices().get("test");
assertThat(indexMetaData.getState(), equalTo(State.CLOSE));
}
}
public void testCloseIndexNoAcknowledgement() {
createIndex("test");
ensureGreen();
CloseIndexResponse closeIndexResponse = client().admin().indices().prepareClose("test").setTimeout("0s").get();
assertThat(closeIndexResponse.isAcknowledged(), equalTo(false));
}
public void testOpenIndexAcknowledgement() {
createIndex("test");
ensureGreen();
assertAcked(client().admin().indices().prepareClose("test"));
assertAcked(client().admin().indices().prepareOpen("test"));
for (Client client : clients()) {
IndexMetaData indexMetaData = getLocalClusterState(client).metaData().indices().get("test");
assertThat(indexMetaData.getState(), equalTo(State.OPEN));
}
}
public void testPutMappingAcknowledgement() {
createIndex("test");
ensureGreen();
assertAcked(client().admin().indices().preparePutMapping("test").setType("test").setSource("field", "type=keyword"));
for (Client client : clients()) {
assertThat(getLocalClusterState(client).metaData().indices().get("test").mapping("test"), notNullValue());
}
}
public void testPutMappingNoAcknowledgement() {
createIndex("test");
ensureGreen();
PutMappingResponse putMappingResponse = client().admin().indices().preparePutMapping("test").setType("test").setSource("field", "type=keyword").setTimeout("0s").get();
assertThat(putMappingResponse.isAcknowledged(), equalTo(false));
}
public void testCreateIndexAcknowledgement() {
createIndex("test");
for (Client client : clients()) {
assertThat(getLocalClusterState(client).metaData().indices().containsKey("test"), equalTo(true));
}
//let's wait for green, otherwise there can be issues with after test checks (mock directory wrapper etc.)
//but we do want to check that the new index is on all nodes cluster state even before green
ensureGreen();
}
public void testCreateIndexNoAcknowledgement() {
CreateIndexResponse createIndexResponse = client().admin().indices().prepareCreate("test").setTimeout("0s").get();
assertThat(createIndexResponse.isAcknowledged(), equalTo(false));
//let's wait for green, otherwise there can be issues with after test checks (mock directory wrapper etc.)
ensureGreen();
}
private static ClusterState getLocalClusterState(Client client) {
return client.admin().cluster().prepareState().setLocal(true).get().getState();
}
}