[Close Index API] Refactor MetaDataIndexStateService (#36354)
The commit changes how indices are closed in the MetaDataIndexStateService. It now uses a 3 steps process where writes are blocked on indices to be closed, then some verifications are done on shards using the TransportVerifyShardBeforeCloseAction added in #36249, and finally indices states are moved to CLOSE and their routing tables removed. The closing process also takes care of using the pre-7.0 way to close indices if the cluster contains mixed version of nodes and a node does not support the TransportVerifyShardBeforeCloseAction. It also closes unassigned indices. Related to #33888
This commit is contained in:
parent
40953d70ca
commit
8e5dd20efb
|
@ -20,12 +20,13 @@
|
||||||
package org.elasticsearch.test.rest;
|
package org.elasticsearch.test.rest;
|
||||||
|
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.apache.lucene.util.LuceneTestCase;
|
||||||
import org.elasticsearch.action.ActionFuture;
|
import org.elasticsearch.action.ActionFuture;
|
||||||
import org.elasticsearch.action.support.PlainActionFuture;
|
import org.elasticsearch.action.support.PlainActionFuture;
|
||||||
|
import org.elasticsearch.client.Request;
|
||||||
import org.elasticsearch.client.Response;
|
import org.elasticsearch.client.Response;
|
||||||
import org.elasticsearch.client.ResponseException;
|
import org.elasticsearch.client.ResponseException;
|
||||||
import org.elasticsearch.client.ResponseListener;
|
import org.elasticsearch.client.ResponseListener;
|
||||||
import org.elasticsearch.client.Request;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ import static org.hamcrest.Matchers.instanceOf;
|
||||||
/**
|
/**
|
||||||
* Tests that wait for refresh is fired if the index is closed.
|
* Tests that wait for refresh is fired if the index is closed.
|
||||||
*/
|
*/
|
||||||
|
@LuceneTestCase.AwaitsFix(bugUrl = "to be created")
|
||||||
public class WaitForRefreshAndCloseIT extends ESRestTestCase {
|
public class WaitForRefreshAndCloseIT extends ESRestTestCase {
|
||||||
@Before
|
@Before
|
||||||
public void setupIndex() throws IOException {
|
public void setupIndex() throws IOException {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.elasticsearch.cluster.ack.IndicesClusterStateUpdateRequest;
|
||||||
*/
|
*/
|
||||||
public class CloseIndexClusterStateUpdateRequest extends IndicesClusterStateUpdateRequest<CloseIndexClusterStateUpdateRequest> {
|
public class CloseIndexClusterStateUpdateRequest extends IndicesClusterStateUpdateRequest<CloseIndexClusterStateUpdateRequest> {
|
||||||
|
|
||||||
CloseIndexClusterStateUpdateRequest() {
|
public CloseIndexClusterStateUpdateRequest() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.elasticsearch.action.support.DestructiveOperations;
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
import org.elasticsearch.cluster.block.ClusterBlockException;
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
|
@ -105,19 +104,21 @@ public class TransportCloseIndexAction extends TransportMasterNodeAction<CloseIn
|
||||||
listener.onResponse(new AcknowledgedResponse(true));
|
listener.onResponse(new AcknowledgedResponse(true));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CloseIndexClusterStateUpdateRequest updateRequest = new CloseIndexClusterStateUpdateRequest()
|
|
||||||
.ackTimeout(request.timeout()).masterNodeTimeout(request.masterNodeTimeout())
|
|
||||||
.indices(concreteIndices);
|
|
||||||
|
|
||||||
indexStateService.closeIndices(updateRequest, new ActionListener<ClusterStateUpdateResponse>() {
|
final CloseIndexClusterStateUpdateRequest closeRequest = new CloseIndexClusterStateUpdateRequest()
|
||||||
|
.ackTimeout(request.timeout())
|
||||||
|
.masterNodeTimeout(request.masterNodeTimeout())
|
||||||
|
.indices(concreteIndices);
|
||||||
|
|
||||||
|
indexStateService.closeIndices(closeRequest, new ActionListener<AcknowledgedResponse>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(ClusterStateUpdateResponse response) {
|
public void onResponse(final AcknowledgedResponse response) {
|
||||||
listener.onResponse(new AcknowledgedResponse(response.isAcknowledged()));
|
listener.onResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Exception t) {
|
public void onFailure(final Exception t) {
|
||||||
logger.debug(() -> new ParameterizedMessage("failed to close indices [{}]", (Object) concreteIndices), t);
|
logger.debug(() -> new ParameterizedMessage("failed to close indices [{}]", (Object) concreteIndices), t);
|
||||||
listener.onFailure(t);
|
listener.onFailure(t);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,10 +40,10 @@ import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
|
||||||
public class TransportVerifyShardBeforeCloseAction extends TransportReplicationAction<
|
public class TransportVerifyShardBeforeCloseAction extends TransportReplicationAction<
|
||||||
TransportVerifyShardBeforeCloseAction.ShardCloseRequest, TransportVerifyShardBeforeCloseAction.ShardCloseRequest, ReplicationResponse> {
|
TransportVerifyShardBeforeCloseAction.ShardRequest, TransportVerifyShardBeforeCloseAction.ShardRequest, ReplicationResponse> {
|
||||||
|
|
||||||
public static final String NAME = CloseIndexAction.NAME + "[s]";
|
public static final String NAME = CloseIndexAction.NAME + "[s]";
|
||||||
private static final ClusterBlock EXPECTED_BLOCK = MetaDataIndexStateService.INDEX_CLOSED_BLOCK;
|
public static final ClusterBlock EXPECTED_BLOCK = MetaDataIndexStateService.INDEX_CLOSED_BLOCK;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TransportVerifyShardBeforeCloseAction(final Settings settings, final TransportService transportService,
|
public TransportVerifyShardBeforeCloseAction(final Settings settings, final TransportService transportService,
|
||||||
|
@ -51,7 +51,7 @@ public class TransportVerifyShardBeforeCloseAction extends TransportReplicationA
|
||||||
final ThreadPool threadPool, final ShardStateAction stateAction,
|
final ThreadPool threadPool, final ShardStateAction stateAction,
|
||||||
final ActionFilters actionFilters, final IndexNameExpressionResolver resolver) {
|
final ActionFilters actionFilters, final IndexNameExpressionResolver resolver) {
|
||||||
super(settings, NAME, transportService, clusterService, indicesService, threadPool, stateAction, actionFilters, resolver,
|
super(settings, NAME, transportService, clusterService, indicesService, threadPool, stateAction, actionFilters, resolver,
|
||||||
ShardCloseRequest::new, ShardCloseRequest::new, ThreadPool.Names.MANAGEMENT);
|
ShardRequest::new, ShardRequest::new, ThreadPool.Names.MANAGEMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -61,14 +61,14 @@ public class TransportVerifyShardBeforeCloseAction extends TransportReplicationA
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void acquirePrimaryOperationPermit(final IndexShard primary,
|
protected void acquirePrimaryOperationPermit(final IndexShard primary,
|
||||||
final ShardCloseRequest request,
|
final ShardRequest request,
|
||||||
final ActionListener<Releasable> onAcquired) {
|
final ActionListener<Releasable> onAcquired) {
|
||||||
primary.acquireAllPrimaryOperationsPermits(onAcquired, request.timeout());
|
primary.acquireAllPrimaryOperationsPermits(onAcquired, request.timeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void acquireReplicaOperationPermit(final IndexShard replica,
|
protected void acquireReplicaOperationPermit(final IndexShard replica,
|
||||||
final ShardCloseRequest request,
|
final ShardRequest request,
|
||||||
final ActionListener<Releasable> onAcquired,
|
final ActionListener<Releasable> onAcquired,
|
||||||
final long primaryTerm,
|
final long primaryTerm,
|
||||||
final long globalCheckpoint,
|
final long globalCheckpoint,
|
||||||
|
@ -77,14 +77,14 @@ public class TransportVerifyShardBeforeCloseAction extends TransportReplicationA
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PrimaryResult<ShardCloseRequest, ReplicationResponse> shardOperationOnPrimary(final ShardCloseRequest shardRequest,
|
protected PrimaryResult<ShardRequest, ReplicationResponse> shardOperationOnPrimary(final ShardRequest shardRequest,
|
||||||
final IndexShard primary) throws Exception {
|
final IndexShard primary) throws Exception {
|
||||||
executeShardOperation(primary);
|
executeShardOperation(primary);
|
||||||
return new PrimaryResult<>(shardRequest, new ReplicationResponse());
|
return new PrimaryResult<>(shardRequest, new ReplicationResponse());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ReplicaResult shardOperationOnReplica(final ShardCloseRequest shardRequest, final IndexShard replica) throws Exception {
|
protected ReplicaResult shardOperationOnReplica(final ShardRequest shardRequest, final IndexShard replica) throws Exception {
|
||||||
executeShardOperation(replica);
|
executeShardOperation(replica);
|
||||||
return new ReplicaResult();
|
return new ReplicaResult();
|
||||||
}
|
}
|
||||||
|
@ -109,18 +109,18 @@ public class TransportVerifyShardBeforeCloseAction extends TransportReplicationA
|
||||||
logger.debug("{} shard is ready for closing", shardId);
|
logger.debug("{} shard is ready for closing", shardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ShardCloseRequest extends ReplicationRequest<ShardCloseRequest> {
|
public static class ShardRequest extends ReplicationRequest<ShardRequest> {
|
||||||
|
|
||||||
ShardCloseRequest(){
|
ShardRequest(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShardCloseRequest(final ShardId shardId) {
|
public ShardRequest(final ShardId shardId) {
|
||||||
super(shardId);
|
super(shardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "close shard {" + shardId + "}";
|
return "verify shard before close {" + shardId + "}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,28 +19,46 @@
|
||||||
|
|
||||||
package org.elasticsearch.cluster.metadata;
|
package org.elasticsearch.cluster.metadata;
|
||||||
|
|
||||||
|
import com.carrotsearch.hppc.cursors.IntObjectCursor;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.action.NotifyOnceListener;
|
||||||
import org.elasticsearch.action.admin.indices.close.CloseIndexClusterStateUpdateRequest;
|
import org.elasticsearch.action.admin.indices.close.CloseIndexClusterStateUpdateRequest;
|
||||||
|
import org.elasticsearch.action.admin.indices.close.TransportVerifyShardBeforeCloseAction;
|
||||||
import org.elasticsearch.action.admin.indices.open.OpenIndexClusterStateUpdateRequest;
|
import org.elasticsearch.action.admin.indices.open.OpenIndexClusterStateUpdateRequest;
|
||||||
import org.elasticsearch.action.support.ActiveShardsObserver;
|
import org.elasticsearch.action.support.ActiveShardsObserver;
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
|
import org.elasticsearch.action.support.replication.ReplicationResponse;
|
||||||
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
|
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
|
import org.elasticsearch.cluster.ClusterStateUpdateTask;
|
||||||
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
||||||
import org.elasticsearch.cluster.ack.OpenIndexClusterStateUpdateResponse;
|
import org.elasticsearch.cluster.ack.OpenIndexClusterStateUpdateResponse;
|
||||||
import org.elasticsearch.cluster.block.ClusterBlock;
|
import org.elasticsearch.cluster.block.ClusterBlock;
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
||||||
import org.elasticsearch.cluster.block.ClusterBlocks;
|
import org.elasticsearch.cluster.block.ClusterBlocks;
|
||||||
|
import org.elasticsearch.cluster.routing.IndexRoutingTable;
|
||||||
|
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
|
||||||
import org.elasticsearch.cluster.routing.RoutingTable;
|
import org.elasticsearch.cluster.routing.RoutingTable;
|
||||||
import org.elasticsearch.cluster.routing.allocation.AllocationService;
|
import org.elasticsearch.cluster.routing.allocation.AllocationService;
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.Priority;
|
import org.elasticsearch.common.Priority;
|
||||||
import org.elasticsearch.common.ValidationException;
|
import org.elasticsearch.common.ValidationException;
|
||||||
|
import org.elasticsearch.common.collect.ImmutableOpenIntMap;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
|
||||||
|
import org.elasticsearch.common.util.concurrent.AtomicArray;
|
||||||
|
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
|
||||||
|
import org.elasticsearch.common.util.concurrent.CountDown;
|
||||||
import org.elasticsearch.index.Index;
|
import org.elasticsearch.index.Index;
|
||||||
|
import org.elasticsearch.index.IndexNotFoundException;
|
||||||
|
import org.elasticsearch.index.shard.ShardId;
|
||||||
import org.elasticsearch.indices.IndicesService;
|
import org.elasticsearch.indices.IndicesService;
|
||||||
import org.elasticsearch.rest.RestStatus;
|
import org.elasticsearch.rest.RestStatus;
|
||||||
import org.elasticsearch.snapshots.RestoreService;
|
import org.elasticsearch.snapshots.RestoreService;
|
||||||
|
@ -51,8 +69,13 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.util.Collections.unmodifiableMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service responsible for submitting open/close index requests
|
* Service responsible for submitting open/close index requests
|
||||||
|
@ -64,50 +87,118 @@ public class MetaDataIndexStateService {
|
||||||
false, false, RestStatus.FORBIDDEN, ClusterBlockLevel.READ_WRITE);
|
false, false, RestStatus.FORBIDDEN, ClusterBlockLevel.READ_WRITE);
|
||||||
|
|
||||||
private final ClusterService clusterService;
|
private final ClusterService clusterService;
|
||||||
|
|
||||||
private final AllocationService allocationService;
|
private final AllocationService allocationService;
|
||||||
|
|
||||||
private final MetaDataIndexUpgradeService metaDataIndexUpgradeService;
|
private final MetaDataIndexUpgradeService metaDataIndexUpgradeService;
|
||||||
private final IndicesService indicesService;
|
private final IndicesService indicesService;
|
||||||
|
private final ThreadPool threadPool;
|
||||||
|
private final TransportVerifyShardBeforeCloseAction transportVerifyShardBeforeCloseAction;
|
||||||
private final ActiveShardsObserver activeShardsObserver;
|
private final ActiveShardsObserver activeShardsObserver;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MetaDataIndexStateService(ClusterService clusterService, AllocationService allocationService,
|
public MetaDataIndexStateService(ClusterService clusterService, AllocationService allocationService,
|
||||||
MetaDataIndexUpgradeService metaDataIndexUpgradeService,
|
MetaDataIndexUpgradeService metaDataIndexUpgradeService,
|
||||||
IndicesService indicesService, ThreadPool threadPool) {
|
IndicesService indicesService, ThreadPool threadPool,
|
||||||
|
TransportVerifyShardBeforeCloseAction transportVerifyShardBeforeCloseAction) {
|
||||||
this.indicesService = indicesService;
|
this.indicesService = indicesService;
|
||||||
this.clusterService = clusterService;
|
this.clusterService = clusterService;
|
||||||
this.allocationService = allocationService;
|
this.allocationService = allocationService;
|
||||||
|
this.threadPool = threadPool;
|
||||||
|
this.transportVerifyShardBeforeCloseAction = transportVerifyShardBeforeCloseAction;
|
||||||
this.metaDataIndexUpgradeService = metaDataIndexUpgradeService;
|
this.metaDataIndexUpgradeService = metaDataIndexUpgradeService;
|
||||||
this.activeShardsObserver = new ActiveShardsObserver(clusterService, threadPool);
|
this.activeShardsObserver = new ActiveShardsObserver(clusterService, threadPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeIndices(final CloseIndexClusterStateUpdateRequest request, final ActionListener<ClusterStateUpdateResponse> listener) {
|
/**
|
||||||
if (request.indices() == null || request.indices().length == 0) {
|
* Closes one or more indices.
|
||||||
|
*
|
||||||
|
* Closing indices is a 3 steps process: it first adds a write block to every indices to close, then waits for the operations on shards
|
||||||
|
* to be terminated and finally closes the indices by moving their state to CLOSE.
|
||||||
|
*/
|
||||||
|
public void closeIndices(final CloseIndexClusterStateUpdateRequest request, final ActionListener<AcknowledgedResponse> listener) {
|
||||||
|
final Index[] concreteIndices = request.indices();
|
||||||
|
if (concreteIndices == null || concreteIndices.length == 0) {
|
||||||
throw new IllegalArgumentException("Index name is required");
|
throw new IllegalArgumentException("Index name is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
final String indicesAsString = Arrays.toString(request.indices());
|
final TimeValue timeout = request.ackTimeout();
|
||||||
clusterService.submitStateUpdateTask("close-indices " + indicesAsString,
|
final TimeValue masterTimeout = request.masterNodeTimeout();
|
||||||
new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(Priority.URGENT, request, listener) {
|
|
||||||
@Override
|
|
||||||
protected ClusterStateUpdateResponse newResponse(boolean acknowledged) {
|
|
||||||
return new ClusterStateUpdateResponse(acknowledged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
clusterService.submitStateUpdateTask("add-block-index-to-close " + Arrays.toString(concreteIndices),
|
||||||
public ClusterState execute(ClusterState currentState) {
|
new ClusterStateUpdateTask(Priority.URGENT) {
|
||||||
return closeIndices(currentState, request.indices(), indicesAsString);
|
|
||||||
|
private final Set<Index> blockedIndices = new HashSet<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClusterState execute(final ClusterState currentState) {
|
||||||
|
return addIndexClosedBlocks(concreteIndices, currentState, blockedIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clusterStateProcessed(final String source, final ClusterState oldState, final ClusterState newState) {
|
||||||
|
if (oldState == newState) {
|
||||||
|
assert blockedIndices.isEmpty() : "List of blocked indices is not empty but cluster state wasn't changed";
|
||||||
|
listener.onResponse(new AcknowledgedResponse(true));
|
||||||
|
} else {
|
||||||
|
assert blockedIndices.isEmpty() == false : "List of blocked indices is empty but cluster state was changed";
|
||||||
|
threadPool.executor(ThreadPool.Names.MANAGEMENT)
|
||||||
|
.execute(new WaitForClosedBlocksApplied(blockedIndices, timeout,
|
||||||
|
ActionListener.wrap(closedBlocksResults ->
|
||||||
|
clusterService.submitStateUpdateTask("close-indices", new ClusterStateUpdateTask(Priority.URGENT) {
|
||||||
|
@Override
|
||||||
|
public ClusterState execute(final ClusterState currentState) throws Exception {
|
||||||
|
final ClusterState updatedState = closeRoutingTable(currentState, closedBlocksResults);
|
||||||
|
return allocationService.reroute(updatedState, "indices closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final String source, final Exception e) {
|
||||||
|
listener.onFailure(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clusterStateProcessed(final String source,
|
||||||
|
final ClusterState oldState, final ClusterState newState) {
|
||||||
|
boolean acknowledged = closedBlocksResults.values().stream()
|
||||||
|
.allMatch(AcknowledgedResponse::isAcknowledged);
|
||||||
|
listener.onResponse(new AcknowledgedResponse(acknowledged));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
listener::onFailure)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final String source, final Exception e) {
|
||||||
|
listener.onFailure(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimeValue timeout() {
|
||||||
|
return masterTimeout;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClusterState closeIndices(ClusterState currentState, final Index[] indices, String indicesAsString) {
|
/**
|
||||||
Set<IndexMetaData> indicesToClose = new HashSet<>();
|
* Step 1 - Start closing indices by adding a write block
|
||||||
|
*
|
||||||
|
* This step builds the list of indices to close (the ones explicitly requested that are not in CLOSE state) and adds the index block
|
||||||
|
* {@link #INDEX_CLOSED_BLOCK} to every index to close in the cluster state. After the cluster state is published, the shards should
|
||||||
|
* start to reject writing operations and we can proceed with step 2.
|
||||||
|
*/
|
||||||
|
static ClusterState addIndexClosedBlocks(final Index[] indices, final ClusterState currentState, final Set<Index> blockedIndices) {
|
||||||
|
final MetaData.Builder metadata = MetaData.builder(currentState.metaData());
|
||||||
|
|
||||||
|
final Set<IndexMetaData> indicesToClose = new HashSet<>();
|
||||||
for (Index index : indices) {
|
for (Index index : indices) {
|
||||||
final IndexMetaData indexMetaData = currentState.metaData().getIndexSafe(index);
|
final IndexMetaData indexMetaData = metadata.getSafe(index);
|
||||||
if (indexMetaData.getState() != IndexMetaData.State.CLOSE) {
|
if (indexMetaData.getState() != IndexMetaData.State.CLOSE) {
|
||||||
indicesToClose.add(indexMetaData);
|
indicesToClose.add(indexMetaData);
|
||||||
|
} else {
|
||||||
|
logger.debug("index {} is already closed, ignoring", index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,28 +210,174 @@ public class MetaDataIndexStateService {
|
||||||
RestoreService.checkIndexClosing(currentState, indicesToClose);
|
RestoreService.checkIndexClosing(currentState, indicesToClose);
|
||||||
// Check if index closing conflicts with any running snapshots
|
// Check if index closing conflicts with any running snapshots
|
||||||
SnapshotsService.checkIndexClosing(currentState, indicesToClose);
|
SnapshotsService.checkIndexClosing(currentState, indicesToClose);
|
||||||
logger.info("closing indices [{}]", indicesAsString);
|
|
||||||
|
|
||||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
// If the cluster is in a mixed version that does not support the shard close action,
|
||||||
ClusterBlocks.Builder blocksBuilder = ClusterBlocks.builder()
|
// we use the previous way to close indices and directly close them without sanity checks
|
||||||
.blocks(currentState.blocks());
|
final boolean useDirectClose = currentState.nodes().getMinNodeVersion().before(Version.V_7_0_0);
|
||||||
for (IndexMetaData openIndexMetadata : indicesToClose) {
|
|
||||||
final String indexName = openIndexMetadata.getIndex().getName();
|
final ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
|
||||||
mdBuilder.put(IndexMetaData.builder(openIndexMetadata).state(IndexMetaData.State.CLOSE));
|
final RoutingTable.Builder routingTable = RoutingTable.builder(currentState.routingTable());
|
||||||
blocksBuilder.addIndexBlock(indexName, INDEX_CLOSED_BLOCK);
|
|
||||||
|
for (IndexMetaData indexToClose : indicesToClose) {
|
||||||
|
final Index index = indexToClose.getIndex();
|
||||||
|
if (currentState.blocks().hasIndexBlock(index.getName(), INDEX_CLOSED_BLOCK) == false) {
|
||||||
|
blocks.addIndexBlock(index.getName(), INDEX_CLOSED_BLOCK);
|
||||||
|
}
|
||||||
|
if (useDirectClose) {
|
||||||
|
logger.debug("closing index {} directly", index);
|
||||||
|
metadata.put(IndexMetaData.builder(indexToClose).state(IndexMetaData.State.CLOSE));
|
||||||
|
routingTable.remove(index.getName());
|
||||||
|
}
|
||||||
|
blockedIndices.add(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClusterState updatedState = ClusterState.builder(currentState).metaData(mdBuilder).blocks(blocksBuilder).build();
|
logger.info(() -> new ParameterizedMessage("closing indices {}",
|
||||||
|
blockedIndices.stream().map(Object::toString).collect(Collectors.joining(","))));
|
||||||
|
return ClusterState.builder(currentState).blocks(blocks).metaData(metadata).routingTable(routingTable.build()).build();
|
||||||
|
}
|
||||||
|
|
||||||
RoutingTable.Builder rtBuilder = RoutingTable.builder(currentState.routingTable());
|
/**
|
||||||
for (IndexMetaData index : indicesToClose) {
|
* Step 2 - Wait for indices to be ready for closing
|
||||||
rtBuilder.remove(index.getIndex().getName());
|
* <p>
|
||||||
|
* This step iterates over the indices previously blocked and sends a {@link TransportVerifyShardBeforeCloseAction} to each shard. If
|
||||||
|
* this action succeed then the shard is considered to be ready for closing. When all shards of a given index are ready for closing,
|
||||||
|
* the index is considered ready to be closed.
|
||||||
|
*/
|
||||||
|
class WaitForClosedBlocksApplied extends AbstractRunnable {
|
||||||
|
|
||||||
|
private final Set<Index> blockedIndices;
|
||||||
|
private final @Nullable TimeValue timeout;
|
||||||
|
private final ActionListener<Map<Index, AcknowledgedResponse>> listener;
|
||||||
|
|
||||||
|
private WaitForClosedBlocksApplied(final Set<Index> blockedIndices,
|
||||||
|
final @Nullable TimeValue timeout,
|
||||||
|
final ActionListener<Map<Index, AcknowledgedResponse>> listener) {
|
||||||
|
if (blockedIndices == null || blockedIndices.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Cannot wait for closed block to be applied to null or empty list of blocked indices");
|
||||||
|
}
|
||||||
|
this.blockedIndices = blockedIndices;
|
||||||
|
this.listener = listener;
|
||||||
|
this.timeout = timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
//no explicit wait for other nodes needed as we use AckedClusterStateUpdateTask
|
@Override
|
||||||
return allocationService.reroute(
|
public void onFailure(final Exception e) {
|
||||||
ClusterState.builder(updatedState).routingTable(rtBuilder.build()).build(),
|
listener.onFailure(e);
|
||||||
"indices closed [" + indicesAsString + "]");
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRun() throws Exception {
|
||||||
|
final Map<Index, AcknowledgedResponse> results = ConcurrentCollections.newConcurrentMap();
|
||||||
|
final CountDown countDown = new CountDown(blockedIndices.size());
|
||||||
|
final ClusterState state = clusterService.state();
|
||||||
|
for (Index blockedIndex : blockedIndices) {
|
||||||
|
waitForShardsReadyForClosing(blockedIndex, state, timeout, response -> {
|
||||||
|
results.put(blockedIndex, response);
|
||||||
|
if (countDown.countDown()) {
|
||||||
|
listener.onResponse(unmodifiableMap(results));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForShardsReadyForClosing(final Index index, final ClusterState state, @Nullable final TimeValue timeout,
|
||||||
|
final Consumer<AcknowledgedResponse> onResponse) {
|
||||||
|
final IndexMetaData indexMetaData = state.metaData().index(index);
|
||||||
|
if (indexMetaData == null) {
|
||||||
|
logger.debug("index {} has been blocked before closing and is now deleted, ignoring", index);
|
||||||
|
onResponse.accept(new AcknowledgedResponse(true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final IndexRoutingTable indexRoutingTable = state.routingTable().index(index);
|
||||||
|
if (indexRoutingTable == null || indexMetaData.getState() == IndexMetaData.State.CLOSE) {
|
||||||
|
logger.debug("index {} has been blocked before closing and is already closed, ignoring", index);
|
||||||
|
onResponse.accept(new AcknowledgedResponse(true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ImmutableOpenIntMap<IndexShardRoutingTable> shards = indexRoutingTable.getShards();
|
||||||
|
final AtomicArray<AcknowledgedResponse> results = new AtomicArray<>(shards.size());
|
||||||
|
final CountDown countDown = new CountDown(shards.size());
|
||||||
|
|
||||||
|
for (IntObjectCursor<IndexShardRoutingTable> shard : shards) {
|
||||||
|
final IndexShardRoutingTable shardRoutingTable = shard.value;
|
||||||
|
final ShardId shardId = shardRoutingTable.shardId();
|
||||||
|
sendVerifyShardBeforeCloseRequest(shardRoutingTable, timeout, new NotifyOnceListener<ReplicationResponse>() {
|
||||||
|
@Override
|
||||||
|
public void innerOnResponse(final ReplicationResponse replicationResponse) {
|
||||||
|
ReplicationResponse.ShardInfo shardInfo = replicationResponse.getShardInfo();
|
||||||
|
results.setOnce(shardId.id(), new AcknowledgedResponse(shardInfo.getFailed() == 0));
|
||||||
|
processIfFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void innerOnFailure(final Exception e) {
|
||||||
|
results.setOnce(shardId.id(), new AcknowledgedResponse(false));
|
||||||
|
processIfFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processIfFinished() {
|
||||||
|
if (countDown.countDown()) {
|
||||||
|
final boolean acknowledged = results.asList().stream().allMatch(AcknowledgedResponse::isAcknowledged);
|
||||||
|
onResponse.accept(new AcknowledgedResponse(acknowledged));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendVerifyShardBeforeCloseRequest(final IndexShardRoutingTable shardRoutingTable, @Nullable final TimeValue timeout,
|
||||||
|
final ActionListener<ReplicationResponse> listener) {
|
||||||
|
final ShardId shardId = shardRoutingTable.shardId();
|
||||||
|
if (shardRoutingTable.primaryShard().unassigned()) {
|
||||||
|
logger.debug("primary shard {} is unassigned, ignoring", shardId);
|
||||||
|
final ReplicationResponse response = new ReplicationResponse();
|
||||||
|
response.setShardInfo(new ReplicationResponse.ShardInfo(shardRoutingTable.size(), shardRoutingTable.size()));
|
||||||
|
listener.onResponse(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final TransportVerifyShardBeforeCloseAction.ShardRequest shardRequest =
|
||||||
|
new TransportVerifyShardBeforeCloseAction.ShardRequest(shardId);
|
||||||
|
if (timeout != null) {
|
||||||
|
shardRequest.timeout(timeout);
|
||||||
|
}
|
||||||
|
// TODO propagate a task id from the parent CloseIndexRequest to the ShardCloseRequests
|
||||||
|
transportVerifyShardBeforeCloseAction.execute(shardRequest, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3 - Move index states from OPEN to CLOSE in cluster state for indices that are ready for closing.
|
||||||
|
*/
|
||||||
|
static ClusterState closeRoutingTable(final ClusterState currentState, final Map<Index, AcknowledgedResponse> results) {
|
||||||
|
final MetaData.Builder metadata = MetaData.builder(currentState.metaData());
|
||||||
|
final ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
|
||||||
|
final RoutingTable.Builder routingTable = RoutingTable.builder(currentState.routingTable());
|
||||||
|
|
||||||
|
final Set<String> closedIndices = new HashSet<>();
|
||||||
|
for (Map.Entry<Index, AcknowledgedResponse> result : results.entrySet()) {
|
||||||
|
final Index index = result.getKey();
|
||||||
|
try {
|
||||||
|
final IndexMetaData indexMetaData = metadata.getSafe(index);
|
||||||
|
if (indexMetaData.getState() != IndexMetaData.State.CLOSE) {
|
||||||
|
if (result.getValue().isAcknowledged()) {
|
||||||
|
logger.debug("closing index {} succeed, removing index routing table", index);
|
||||||
|
metadata.put(IndexMetaData.builder(indexMetaData).state(IndexMetaData.State.CLOSE));
|
||||||
|
routingTable.remove(index.getName());
|
||||||
|
closedIndices.add(index.getName());
|
||||||
|
} else {
|
||||||
|
logger.debug("closing index {} failed, removing index block because: {}", index, result.getValue());
|
||||||
|
blocks.removeIndexBlock(index.getName(), INDEX_CLOSED_BLOCK);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("index {} has been closed since it was blocked before closing, ignoring", index);
|
||||||
|
}
|
||||||
|
} catch (final IndexNotFoundException e) {
|
||||||
|
logger.debug("index {} has been deleted since it was blocked before closing, ignoring", index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("completed closing of indices {}", closedIndices);
|
||||||
|
return ClusterState.builder(currentState).blocks(blocks).metaData(metadata).routingTable(routingTable.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openIndex(final OpenIndexClusterStateUpdateRequest request,
|
public void openIndex(final OpenIndexClusterStateUpdateRequest request,
|
||||||
|
@ -250,7 +487,6 @@ public class MetaDataIndexStateService {
|
||||||
ex.addValidationError(error.get());
|
ex.addValidationError(error.get());
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getTotalShardCount(ClusterState state, Index index) {
|
private static int getTotalShardCount(ClusterState state, Index index) {
|
||||||
|
|
|
@ -74,8 +74,8 @@ public class TransportVerifyShardBeforeCloseActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeOnPrimaryOrReplica() throws Exception {
|
private void executeOnPrimaryOrReplica() throws Exception {
|
||||||
final TransportVerifyShardBeforeCloseAction.ShardCloseRequest request =
|
final TransportVerifyShardBeforeCloseAction.ShardRequest request =
|
||||||
new TransportVerifyShardBeforeCloseAction.ShardCloseRequest(indexShard.shardId());
|
new TransportVerifyShardBeforeCloseAction.ShardRequest(indexShard.shardId());
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
assertNotNull(action.shardOperationOnPrimary(request, indexShard));
|
assertNotNull(action.shardOperationOnPrimary(request, indexShard));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,27 +20,186 @@
|
||||||
package org.elasticsearch.cluster.metadata;
|
package org.elasticsearch.cluster.metadata;
|
||||||
|
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
import org.elasticsearch.cluster.ClusterName;
|
import org.elasticsearch.cluster.ClusterName;
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
|
import org.elasticsearch.cluster.RestoreInProgress;
|
||||||
|
import org.elasticsearch.cluster.SnapshotsInProgress;
|
||||||
|
import org.elasticsearch.cluster.block.ClusterBlock;
|
||||||
|
import org.elasticsearch.cluster.block.ClusterBlocks;
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
||||||
|
import org.elasticsearch.cluster.routing.IndexRoutingTable;
|
||||||
|
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
|
||||||
|
import org.elasticsearch.cluster.routing.RoutingTable;
|
||||||
|
import org.elasticsearch.cluster.routing.ShardRouting;
|
||||||
|
import org.elasticsearch.cluster.routing.ShardRoutingState;
|
||||||
import org.elasticsearch.cluster.shards.ClusterShardLimitIT;
|
import org.elasticsearch.cluster.shards.ClusterShardLimitIT;
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.ValidationException;
|
import org.elasticsearch.common.ValidationException;
|
||||||
import org.elasticsearch.common.collect.ImmutableOpenMap;
|
import org.elasticsearch.common.collect.ImmutableOpenMap;
|
||||||
import org.elasticsearch.common.logging.DeprecationLogger;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.index.Index;
|
import org.elasticsearch.index.Index;
|
||||||
|
import org.elasticsearch.index.IndexNotFoundException;
|
||||||
|
import org.elasticsearch.index.shard.ShardId;
|
||||||
|
import org.elasticsearch.repositories.IndexId;
|
||||||
|
import org.elasticsearch.snapshots.Snapshot;
|
||||||
|
import org.elasticsearch.snapshots.SnapshotId;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
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.cluster.metadata.IndexMetaData.SETTING_VERSION_CREATED;
|
||||||
|
import static org.elasticsearch.cluster.routing.TestShardRouting.newShardRouting;
|
||||||
import static org.elasticsearch.cluster.shards.ClusterShardLimitIT.ShardCounts.forDataNodeCount;
|
import static org.elasticsearch.cluster.shards.ClusterShardLimitIT.ShardCounts.forDataNodeCount;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class MetaDataIndexStateServiceTests extends ESTestCase {
|
public class MetaDataIndexStateServiceTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testCloseRoutingTable() {
|
||||||
|
final Set<Index> nonBlockedIndices = new HashSet<>();
|
||||||
|
final Map<Index, AcknowledgedResponse> blockedIndices = new HashMap<>();
|
||||||
|
|
||||||
|
ClusterState state = ClusterState.builder(new ClusterName("testCloseRoutingTable")).build();
|
||||||
|
for (int i = 0; i < randomIntBetween(1, 25); i++) {
|
||||||
|
final String indexName = randomAlphaOfLengthBetween(5, 15);
|
||||||
|
|
||||||
|
if (randomBoolean()) {
|
||||||
|
state = addOpenedIndex(indexName, randomIntBetween(1, 5), randomIntBetween(0, 5), state);
|
||||||
|
nonBlockedIndices.add(state.metaData().index(indexName).getIndex());
|
||||||
|
} else {
|
||||||
|
state = addBlockedIndex(indexName, randomIntBetween(1, 5), randomIntBetween(0, 5), state);
|
||||||
|
blockedIndices.put(state.metaData().index(indexName).getIndex(), new AcknowledgedResponse(randomBoolean()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ClusterState updatedState = MetaDataIndexStateService.closeRoutingTable(state, blockedIndices);
|
||||||
|
assertThat(updatedState.metaData().indices().size(), equalTo(nonBlockedIndices.size() + blockedIndices.size()));
|
||||||
|
|
||||||
|
for (Index nonBlockedIndex : nonBlockedIndices) {
|
||||||
|
assertIsOpened(nonBlockedIndex.getName(), updatedState);
|
||||||
|
}
|
||||||
|
for (Map.Entry<Index, AcknowledgedResponse> blockedIndex : blockedIndices.entrySet()) {
|
||||||
|
if (blockedIndex.getValue().isAcknowledged()) {
|
||||||
|
assertIsClosed(blockedIndex.getKey().getName(), updatedState);
|
||||||
|
} else {
|
||||||
|
assertIsOpened(blockedIndex.getKey().getName(), updatedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAddIndexClosedBlocks() {
|
||||||
|
final ClusterState initialState = ClusterState.builder(new ClusterName("testAddIndexClosedBlocks")).build();
|
||||||
|
{
|
||||||
|
final Set<Index> blockedIndices = new HashSet<>();
|
||||||
|
expectThrows(IndexNotFoundException.class, () ->
|
||||||
|
MetaDataIndexStateService.addIndexClosedBlocks(new Index[]{new Index("_name", "_uid")}, initialState, blockedIndices));
|
||||||
|
assertTrue(blockedIndices.isEmpty());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Set<Index> blockedIndices = new HashSet<>();
|
||||||
|
Index[] indices = Index.EMPTY_ARRAY;
|
||||||
|
|
||||||
|
ClusterState updatedState = MetaDataIndexStateService.addIndexClosedBlocks(indices, initialState, blockedIndices);
|
||||||
|
assertSame(initialState, updatedState);
|
||||||
|
assertTrue(blockedIndices.isEmpty());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Set<Index> blockedIndices = new HashSet<>();
|
||||||
|
ClusterState state = addClosedIndex("closed", randomIntBetween(1, 3), randomIntBetween(0, 3), initialState);
|
||||||
|
Index[] indices = new Index[]{state.metaData().index("closed").getIndex()};
|
||||||
|
|
||||||
|
ClusterState updatedState = MetaDataIndexStateService.addIndexClosedBlocks(indices, state, blockedIndices);
|
||||||
|
assertSame(state, updatedState);
|
||||||
|
assertTrue(blockedIndices.isEmpty());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Set<Index> blockedIndices = new HashSet<>();
|
||||||
|
ClusterState state = addClosedIndex("closed", randomIntBetween(1, 3), randomIntBetween(0, 3), initialState);
|
||||||
|
state = addOpenedIndex("opened", randomIntBetween(1, 3), randomIntBetween(0, 3), state);
|
||||||
|
Index[] indices = new Index[]{state.metaData().index("opened").getIndex(), state.metaData().index("closed").getIndex()};
|
||||||
|
|
||||||
|
ClusterState updatedState = MetaDataIndexStateService.addIndexClosedBlocks(indices, state, blockedIndices);
|
||||||
|
assertNotSame(state, updatedState);
|
||||||
|
assertTrue(blockedIndices.contains(updatedState.metaData().index("opened").getIndex()));
|
||||||
|
assertFalse(blockedIndices.contains(updatedState.metaData().index("closed").getIndex()));
|
||||||
|
assertIsBlocked("opened", updatedState, true);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> {
|
||||||
|
ClusterState state = addRestoredIndex("restored", randomIntBetween(1, 3), randomIntBetween(0, 3), initialState);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
state = addOpenedIndex("opened", randomIntBetween(1, 3), randomIntBetween(0, 3), state);
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
state = addOpenedIndex("closed", randomIntBetween(1, 3), randomIntBetween(0, 3), state);
|
||||||
|
}
|
||||||
|
Index[] indices = new Index[]{state.metaData().index("restored").getIndex()};
|
||||||
|
MetaDataIndexStateService.addIndexClosedBlocks(indices, state, new HashSet<>());
|
||||||
|
});
|
||||||
|
assertThat(exception.getMessage(), containsString("Cannot close indices that are being restored: [[restored]]"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> {
|
||||||
|
ClusterState state = addSnapshotIndex("snapshotted", randomIntBetween(1, 3), randomIntBetween(0, 3), initialState);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
state = addOpenedIndex("opened", randomIntBetween(1, 3), randomIntBetween(0, 3), state);
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
state = addOpenedIndex("closed", randomIntBetween(1, 3), randomIntBetween(0, 3), state);
|
||||||
|
}
|
||||||
|
Index[] indices = new Index[]{state.metaData().index("snapshotted").getIndex()};
|
||||||
|
MetaDataIndexStateService.addIndexClosedBlocks(indices, state, new HashSet<>());
|
||||||
|
});
|
||||||
|
assertThat(exception.getMessage(), containsString("Cannot close indices that are being snapshotted: [[snapshotted]]"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Set<Index> blockedIndices = new HashSet<>();
|
||||||
|
ClusterState state = addOpenedIndex("index-1", randomIntBetween(1, 3), randomIntBetween(0, 3), initialState);
|
||||||
|
state = addOpenedIndex("index-2", randomIntBetween(1, 3), randomIntBetween(0, 3), state);
|
||||||
|
state = addOpenedIndex("index-3", randomIntBetween(1, 3), randomIntBetween(0, 3), state);
|
||||||
|
final boolean mixedVersions = randomBoolean();
|
||||||
|
if (mixedVersions) {
|
||||||
|
state = ClusterState.builder(state)
|
||||||
|
.nodes(DiscoveryNodes.builder(state.nodes())
|
||||||
|
.add(new DiscoveryNode("old_node", buildNewFakeTransportAddress(), Collections.emptyMap(),
|
||||||
|
new HashSet<>(Arrays.asList(DiscoveryNode.Role.values())), Version.V_6_0_0)))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
Index[] indices = new Index[]{state.metaData().index("index-1").getIndex(),
|
||||||
|
state.metaData().index("index-2").getIndex(), state.metaData().index("index-3").getIndex()};
|
||||||
|
|
||||||
|
ClusterState updatedState = MetaDataIndexStateService.addIndexClosedBlocks(indices, state, blockedIndices);
|
||||||
|
assertNotSame(state, updatedState);
|
||||||
|
assertTrue(blockedIndices.contains(updatedState.metaData().index("index-1").getIndex()));
|
||||||
|
assertTrue(blockedIndices.contains(updatedState.metaData().index("index-2").getIndex()));
|
||||||
|
assertTrue(blockedIndices.contains(updatedState.metaData().index("index-3").getIndex()));
|
||||||
|
if (mixedVersions) {
|
||||||
|
assertIsClosed("index-1", updatedState);
|
||||||
|
assertIsClosed("index-2", updatedState);
|
||||||
|
assertIsClosed("index-2", updatedState);
|
||||||
|
} else {
|
||||||
|
assertIsBlocked("index-1", updatedState, true);
|
||||||
|
assertIsBlocked("index-2", updatedState, true);
|
||||||
|
assertIsBlocked("index-3", updatedState, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testValidateShardLimit() {
|
public void testValidateShardLimit() {
|
||||||
int nodesInCluster = randomIntBetween(2,100);
|
int nodesInCluster = randomIntBetween(2,100);
|
||||||
ClusterShardLimitIT.ShardCounts counts = forDataNodeCount(nodesInCluster);
|
ClusterShardLimitIT.ShardCounts counts = forDataNodeCount(nodesInCluster);
|
||||||
|
@ -55,7 +214,6 @@ public class MetaDataIndexStateServiceTests extends ESTestCase {
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
.toArray(new Index[2]);
|
.toArray(new Index[2]);
|
||||||
|
|
||||||
DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
|
|
||||||
int totalShards = counts.getFailingIndexShards() * (1 + counts.getFailingIndexReplicas());
|
int totalShards = counts.getFailingIndexShards() * (1 + counts.getFailingIndexReplicas());
|
||||||
int currentShards = counts.getFirstIndexShards() * (1 + counts.getFirstIndexReplicas());
|
int currentShards = counts.getFirstIndexShards() * (1 + counts.getFirstIndexReplicas());
|
||||||
int maxShards = counts.getShardsPerNode() * nodesInCluster;
|
int maxShards = counts.getShardsPerNode() * nodesInCluster;
|
||||||
|
@ -69,32 +227,115 @@ public class MetaDataIndexStateServiceTests extends ESTestCase {
|
||||||
int closedIndexShards, int closedIndexReplicas, Settings clusterSettings) {
|
int closedIndexShards, int closedIndexReplicas, Settings clusterSettings) {
|
||||||
ImmutableOpenMap.Builder<String, DiscoveryNode> dataNodes = ImmutableOpenMap.builder();
|
ImmutableOpenMap.Builder<String, DiscoveryNode> dataNodes = ImmutableOpenMap.builder();
|
||||||
for (int i = 0; i < nodesInCluster; i++) {
|
for (int i = 0; i < nodesInCluster; i++) {
|
||||||
dataNodes.put(randomAlphaOfLengthBetween(5,15), mock(DiscoveryNode.class));
|
dataNodes.put(randomAlphaOfLengthBetween(5, 15), mock(DiscoveryNode.class));
|
||||||
}
|
}
|
||||||
DiscoveryNodes nodes = mock(DiscoveryNodes.class);
|
DiscoveryNodes nodes = mock(DiscoveryNodes.class);
|
||||||
when(nodes.getDataNodes()).thenReturn(dataNodes.build());
|
when(nodes.getDataNodes()).thenReturn(dataNodes.build());
|
||||||
|
|
||||||
IndexMetaData.Builder openIndexMetaData = IndexMetaData.builder(randomAlphaOfLengthBetween(5, 15))
|
ClusterState state = ClusterState.builder(ClusterName.DEFAULT).build();
|
||||||
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT))
|
state = addOpenedIndex(randomAlphaOfLengthBetween(5, 15), openIndexShards, openIndexReplicas, state);
|
||||||
.creationDate(randomLong())
|
state = addClosedIndex(randomAlphaOfLengthBetween(5, 15), closedIndexShards, closedIndexReplicas, state);
|
||||||
.numberOfShards(openIndexShards)
|
|
||||||
.numberOfReplicas(openIndexReplicas);
|
final MetaData.Builder metaData = MetaData.builder(state.metaData());
|
||||||
IndexMetaData.Builder closedIndexMetaData = IndexMetaData.builder(randomAlphaOfLengthBetween(5, 15))
|
|
||||||
.settings(Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT))
|
|
||||||
.creationDate(randomLong())
|
|
||||||
.state(IndexMetaData.State.CLOSE)
|
|
||||||
.numberOfShards(closedIndexShards)
|
|
||||||
.numberOfReplicas(closedIndexReplicas);
|
|
||||||
MetaData.Builder metaData = MetaData.builder().put(openIndexMetaData).put(closedIndexMetaData);
|
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
metaData.persistentSettings(clusterSettings);
|
metaData.persistentSettings(clusterSettings);
|
||||||
} else {
|
} else {
|
||||||
metaData.transientSettings(clusterSettings);
|
metaData.transientSettings(clusterSettings);
|
||||||
}
|
}
|
||||||
|
return ClusterState.builder(state).metaData(metaData).nodes(nodes).build();
|
||||||
|
}
|
||||||
|
|
||||||
return ClusterState.builder(ClusterName.DEFAULT)
|
private static ClusterState addOpenedIndex(final String index, final int numShards, final int numReplicas, final ClusterState state) {
|
||||||
.metaData(metaData)
|
return addIndex(state, index, numShards, numReplicas, IndexMetaData.State.OPEN, null);
|
||||||
.nodes(nodes)
|
}
|
||||||
|
|
||||||
|
private static ClusterState addClosedIndex(final String index, final int numShards, final int numReplicas, final ClusterState state) {
|
||||||
|
return addIndex(state, index, numShards, numReplicas, IndexMetaData.State.CLOSE, MetaDataIndexStateService.INDEX_CLOSED_BLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClusterState addBlockedIndex(final String index, final int numShards, final int numReplicas, final ClusterState state) {
|
||||||
|
return addIndex(state, index, numShards, numReplicas, IndexMetaData.State.OPEN, MetaDataIndexStateService.INDEX_CLOSED_BLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClusterState addRestoredIndex(final String index, final int numShards, final int numReplicas, final ClusterState state) {
|
||||||
|
ClusterState newState = addOpenedIndex(index, numShards, numReplicas, state);
|
||||||
|
|
||||||
|
final ImmutableOpenMap.Builder<ShardId, RestoreInProgress.ShardRestoreStatus> shardsBuilder = ImmutableOpenMap.builder();
|
||||||
|
for (ShardRouting shardRouting : newState.routingTable().index(index).randomAllActiveShardsIt()) {
|
||||||
|
shardsBuilder.put(shardRouting.shardId(), new RestoreInProgress.ShardRestoreStatus(shardRouting.currentNodeId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Snapshot snapshot = new Snapshot(randomAlphaOfLength(10), new SnapshotId(randomAlphaOfLength(5), randomAlphaOfLength(5)));
|
||||||
|
final RestoreInProgress.Entry entry =
|
||||||
|
new RestoreInProgress.Entry(snapshot, RestoreInProgress.State.INIT, Collections.singletonList(index), shardsBuilder.build());
|
||||||
|
return ClusterState.builder(newState).putCustom(RestoreInProgress.TYPE, new RestoreInProgress(entry)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClusterState addSnapshotIndex(final String index, final int numShards, final int numReplicas, final ClusterState state) {
|
||||||
|
ClusterState newState = addOpenedIndex(index, numShards, numReplicas, state);
|
||||||
|
|
||||||
|
final ImmutableOpenMap.Builder<ShardId, SnapshotsInProgress.ShardSnapshotStatus> shardsBuilder = ImmutableOpenMap.builder();
|
||||||
|
for (ShardRouting shardRouting : newState.routingTable().index(index).randomAllActiveShardsIt()) {
|
||||||
|
shardsBuilder.put(shardRouting.shardId(), new SnapshotsInProgress.ShardSnapshotStatus(shardRouting.currentNodeId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final Snapshot snapshot = new Snapshot(randomAlphaOfLength(10), new SnapshotId(randomAlphaOfLength(5), randomAlphaOfLength(5)));
|
||||||
|
final SnapshotsInProgress.Entry entry =
|
||||||
|
new SnapshotsInProgress.Entry(snapshot, randomBoolean(), false, SnapshotsInProgress.State.INIT,
|
||||||
|
Collections.singletonList(new IndexId(index, index)), randomNonNegativeLong(), randomLong(), shardsBuilder.build());
|
||||||
|
return ClusterState.builder(newState).putCustom(SnapshotsInProgress.TYPE, new SnapshotsInProgress(entry)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClusterState addIndex(final ClusterState currentState,
|
||||||
|
final String index,
|
||||||
|
final int numShards,
|
||||||
|
final int numReplicas,
|
||||||
|
final IndexMetaData.State state,
|
||||||
|
@Nullable final ClusterBlock block) {
|
||||||
|
final IndexMetaData indexMetaData = IndexMetaData.builder(index)
|
||||||
|
.state(state)
|
||||||
|
.creationDate(randomNonNegativeLong())
|
||||||
|
.settings(Settings.builder()
|
||||||
|
.put(SETTING_VERSION_CREATED, Version.CURRENT)
|
||||||
|
.put(SETTING_NUMBER_OF_SHARDS, numShards)
|
||||||
|
.put(SETTING_NUMBER_OF_REPLICAS, numReplicas))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
final ClusterState.Builder clusterStateBuilder = ClusterState.builder(currentState);
|
||||||
|
clusterStateBuilder.metaData(MetaData.builder(currentState.metaData()).put(indexMetaData, true));
|
||||||
|
|
||||||
|
if (state == IndexMetaData.State.OPEN) {
|
||||||
|
final IndexRoutingTable.Builder indexRoutingTable = IndexRoutingTable.builder(indexMetaData.getIndex());
|
||||||
|
for (int j = 0; j < indexMetaData.getNumberOfShards(); j++) {
|
||||||
|
ShardId shardId = new ShardId(indexMetaData.getIndex(), j);
|
||||||
|
IndexShardRoutingTable.Builder indexShardRoutingBuilder = new IndexShardRoutingTable.Builder(shardId);
|
||||||
|
indexShardRoutingBuilder.addShard(newShardRouting(shardId, randomAlphaOfLength(10), true, ShardRoutingState.STARTED));
|
||||||
|
for (int k = 0; k < indexMetaData.getNumberOfReplicas(); k++) {
|
||||||
|
indexShardRoutingBuilder.addShard(newShardRouting(shardId, randomAlphaOfLength(10), false, ShardRoutingState.STARTED));
|
||||||
|
}
|
||||||
|
indexRoutingTable.addIndexShard(indexShardRoutingBuilder.build());
|
||||||
|
}
|
||||||
|
clusterStateBuilder.routingTable(RoutingTable.builder(currentState.routingTable()).add(indexRoutingTable).build());
|
||||||
|
}
|
||||||
|
if (block != null) {
|
||||||
|
clusterStateBuilder.blocks(ClusterBlocks.builder().blocks(currentState.blocks()).addIndexBlock(index, block));
|
||||||
|
}
|
||||||
|
return clusterStateBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertIsOpened(final String indexName, final ClusterState clusterState) {
|
||||||
|
assertThat(clusterState.metaData().index(indexName).getState(), is(IndexMetaData.State.OPEN));
|
||||||
|
assertThat(clusterState.routingTable().index(indexName), notNullValue());
|
||||||
|
assertIsBlocked(indexName, clusterState, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertIsClosed(final String indexName, final ClusterState clusterState) {
|
||||||
|
assertThat(clusterState.metaData().index(indexName).getState(), is(IndexMetaData.State.CLOSE));
|
||||||
|
assertThat(clusterState.routingTable().index(indexName), nullValue());
|
||||||
|
assertIsBlocked(indexName, clusterState, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertIsBlocked(final String indexName, final ClusterState clusterState, final boolean blocked) {
|
||||||
|
assertThat(clusterState.blocks().hasIndexBlock(indexName, MetaDataIndexStateService.INDEX_CLOSED_BLOCK), is(blocked));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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.metadata;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
|
import org.elasticsearch.index.Index;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class MetaDataIndexStateServiceUtils {
|
||||||
|
|
||||||
|
private MetaDataIndexStateServiceUtils(){
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to call {@link MetaDataIndexStateService#addIndexClosedBlocks(Index[], ClusterState, Set)} which is a protected method.
|
||||||
|
*/
|
||||||
|
public static ClusterState addIndexClosedBlocks(final Index[] indices, final ClusterState state, final Set<Index> blockedIndices) {
|
||||||
|
return MetaDataIndexStateService.addIndexClosedBlocks(indices, state, blockedIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to call {@link MetaDataIndexStateService#closeRoutingTable(ClusterState, Map)} which is a protected method.
|
||||||
|
*/
|
||||||
|
public static ClusterState closeRoutingTable(final ClusterState state, final Map<Index, AcknowledgedResponse> results) {
|
||||||
|
return MetaDataIndexStateService.closeRoutingTable(state, results);
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteRequest;
|
||||||
import org.elasticsearch.action.admin.cluster.reroute.TransportClusterRerouteAction;
|
import org.elasticsearch.action.admin.cluster.reroute.TransportClusterRerouteAction;
|
||||||
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
|
import org.elasticsearch.action.admin.indices.close.CloseIndexRequest;
|
||||||
import org.elasticsearch.action.admin.indices.close.TransportCloseIndexAction;
|
import org.elasticsearch.action.admin.indices.close.TransportCloseIndexAction;
|
||||||
|
import org.elasticsearch.action.admin.indices.close.TransportVerifyShardBeforeCloseAction;
|
||||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||||
import org.elasticsearch.action.admin.indices.create.TransportCreateIndexAction;
|
import org.elasticsearch.action.admin.indices.create.TransportCreateIndexAction;
|
||||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||||
|
@ -39,6 +40,7 @@ import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
import org.elasticsearch.action.support.ActionFilters;
|
||||||
import org.elasticsearch.action.support.DestructiveOperations;
|
import org.elasticsearch.action.support.DestructiveOperations;
|
||||||
import org.elasticsearch.action.support.PlainActionFuture;
|
import org.elasticsearch.action.support.PlainActionFuture;
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
import org.elasticsearch.action.support.master.MasterNodeRequest;
|
import org.elasticsearch.action.support.master.MasterNodeRequest;
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeActionUtils;
|
import org.elasticsearch.action.support.master.TransportMasterNodeActionUtils;
|
||||||
|
@ -58,6 +60,7 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
import org.elasticsearch.cluster.metadata.MetaDataCreateIndexService;
|
import org.elasticsearch.cluster.metadata.MetaDataCreateIndexService;
|
||||||
import org.elasticsearch.cluster.metadata.MetaDataDeleteIndexService;
|
import org.elasticsearch.cluster.metadata.MetaDataDeleteIndexService;
|
||||||
import org.elasticsearch.cluster.metadata.MetaDataIndexStateService;
|
import org.elasticsearch.cluster.metadata.MetaDataIndexStateService;
|
||||||
|
import org.elasticsearch.cluster.metadata.MetaDataIndexStateServiceUtils;
|
||||||
import org.elasticsearch.cluster.metadata.MetaDataIndexUpgradeService;
|
import org.elasticsearch.cluster.metadata.MetaDataIndexUpgradeService;
|
||||||
import org.elasticsearch.cluster.metadata.MetaDataUpdateSettingsService;
|
import org.elasticsearch.cluster.metadata.MetaDataUpdateSettingsService;
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||||
|
@ -77,6 +80,7 @@ import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.env.TestEnvironment;
|
import org.elasticsearch.env.TestEnvironment;
|
||||||
|
import org.elasticsearch.index.Index;
|
||||||
import org.elasticsearch.index.IndexService;
|
import org.elasticsearch.index.IndexService;
|
||||||
import org.elasticsearch.index.mapper.MapperService;
|
import org.elasticsearch.index.mapper.MapperService;
|
||||||
import org.elasticsearch.index.shard.IndexEventListener;
|
import org.elasticsearch.index.shard.IndexEventListener;
|
||||||
|
@ -92,6 +96,8 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.getRandom;
|
import static com.carrotsearch.randomizedtesting.RandomizedTest.getRandom;
|
||||||
|
@ -179,8 +185,11 @@ public class ClusterStateChanges {
|
||||||
return indexMetaData;
|
return indexMetaData;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TransportVerifyShardBeforeCloseAction transportVerifyShardBeforeCloseAction = new TransportVerifyShardBeforeCloseAction(SETTINGS,
|
||||||
|
transportService, clusterService, indicesService, threadPool, null, actionFilters, indexNameExpressionResolver);
|
||||||
MetaDataIndexStateService indexStateService = new MetaDataIndexStateService(clusterService, allocationService,
|
MetaDataIndexStateService indexStateService = new MetaDataIndexStateService(clusterService, allocationService,
|
||||||
metaDataIndexUpgradeService, indicesService, threadPool);
|
metaDataIndexUpgradeService, indicesService, threadPool, transportVerifyShardBeforeCloseAction);
|
||||||
MetaDataDeleteIndexService deleteIndexService = new MetaDataDeleteIndexService(SETTINGS, clusterService, allocationService);
|
MetaDataDeleteIndexService deleteIndexService = new MetaDataDeleteIndexService(SETTINGS, clusterService, allocationService);
|
||||||
MetaDataUpdateSettingsService metaDataUpdateSettingsService = new MetaDataUpdateSettingsService(clusterService,
|
MetaDataUpdateSettingsService metaDataUpdateSettingsService = new MetaDataUpdateSettingsService(clusterService,
|
||||||
allocationService, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, indicesService, threadPool);
|
allocationService, IndexScopedSettings.DEFAULT_SCOPED_SETTINGS, indicesService, threadPool);
|
||||||
|
@ -210,7 +219,15 @@ public class ClusterStateChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClusterState closeIndices(ClusterState state, CloseIndexRequest request) {
|
public ClusterState closeIndices(ClusterState state, CloseIndexRequest request) {
|
||||||
return execute(transportCloseIndexAction, request, state);
|
final Index[] concreteIndices = Arrays.stream(request.indices())
|
||||||
|
.map(index -> state.metaData().index(index).getIndex()).toArray(Index[]::new);
|
||||||
|
|
||||||
|
final Set<Index> blockedIndices = new HashSet<>();
|
||||||
|
ClusterState newState = MetaDataIndexStateServiceUtils.addIndexClosedBlocks(concreteIndices, state, blockedIndices);
|
||||||
|
|
||||||
|
newState = MetaDataIndexStateServiceUtils.closeRoutingTable(newState, blockedIndices.stream()
|
||||||
|
.collect(Collectors.toMap(Function.identity(), index -> new AcknowledgedResponse(true))));
|
||||||
|
return allocationService.reroute(newState, "indices closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClusterState openIndices(ClusterState state, OpenIndexRequest request) {
|
public ClusterState openIndices(ClusterState state, OpenIndexRequest request) {
|
||||||
|
|
|
@ -0,0 +1,273 @@
|
||||||
|
/*
|
||||||
|
* 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.indices.state;
|
||||||
|
|
||||||
|
import org.elasticsearch.ExceptionsHelper;
|
||||||
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
|
import org.elasticsearch.action.support.ActiveShardCount;
|
||||||
|
import org.elasticsearch.action.support.IndicesOptions;
|
||||||
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
|
import org.elasticsearch.cluster.block.ClusterBlockException;
|
||||||
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||||
|
import org.elasticsearch.cluster.metadata.MetaDataIndexStateService;
|
||||||
|
import org.elasticsearch.cluster.routing.ShardRouting;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.index.IndexNotFoundException;
|
||||||
|
import org.elasticsearch.indices.IndexClosedException;
|
||||||
|
import org.elasticsearch.test.BackgroundIndexer;
|
||||||
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||||
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
|
public class CloseIndexIT extends ESIntegTestCase {
|
||||||
|
|
||||||
|
public void testCloseMissingIndex() {
|
||||||
|
IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().admin().indices().prepareClose("test").get());
|
||||||
|
assertThat(e.getMessage(), is("no such index [test]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseOneMissingIndex() {
|
||||||
|
createIndex("test1");
|
||||||
|
final IndexNotFoundException e = expectThrows(IndexNotFoundException.class,
|
||||||
|
() -> client().admin().indices().prepareClose("test1", "test2").get());
|
||||||
|
assertThat(e.getMessage(), is("no such index [test2]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseOneMissingIndexIgnoreMissing() {
|
||||||
|
createIndex("test1");
|
||||||
|
assertAcked(client().admin().indices().prepareClose("test1", "test2").setIndicesOptions(IndicesOptions.lenientExpandOpen()));
|
||||||
|
assertIndexIsClosed("test1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseNoIndex() {
|
||||||
|
final ActionRequestValidationException e = expectThrows(ActionRequestValidationException.class,
|
||||||
|
() -> client().admin().indices().prepareClose().get());
|
||||||
|
assertThat(e.getMessage(), containsString("index is missing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseNullIndex() {
|
||||||
|
final ActionRequestValidationException e = expectThrows(ActionRequestValidationException.class,
|
||||||
|
() -> client().admin().indices().prepareClose((String[])null).get());
|
||||||
|
assertThat(e.getMessage(), containsString("index is missing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseIndex() throws Exception {
|
||||||
|
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
|
||||||
|
createIndex(indexName);
|
||||||
|
|
||||||
|
final int nbDocs = randomIntBetween(0, 50);
|
||||||
|
indexRandom(randomBoolean(), false, randomBoolean(), IntStream.range(0, nbDocs)
|
||||||
|
.mapToObj(i -> client().prepareIndex(indexName, "_doc", String.valueOf(i)).setSource("num", i)).collect(toList()));
|
||||||
|
|
||||||
|
assertAcked(client().admin().indices().prepareClose(indexName));
|
||||||
|
assertIndexIsClosed(indexName);
|
||||||
|
|
||||||
|
assertAcked(client().admin().indices().prepareOpen(indexName));
|
||||||
|
assertHitCount(client().prepareSearch(indexName).setSize(0).get(), nbDocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseAlreadyClosedIndex() throws Exception {
|
||||||
|
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
|
||||||
|
createIndex(indexName);
|
||||||
|
|
||||||
|
if (randomBoolean()) {
|
||||||
|
indexRandom(randomBoolean(), false, randomBoolean(), IntStream.range(0, randomIntBetween(1, 10))
|
||||||
|
.mapToObj(i -> client().prepareIndex(indexName, "_doc", String.valueOf(i)).setSource("num", i)).collect(toList()));
|
||||||
|
}
|
||||||
|
// First close should be acked
|
||||||
|
assertAcked(client().admin().indices().prepareClose(indexName));
|
||||||
|
assertIndexIsClosed(indexName);
|
||||||
|
|
||||||
|
// Second close should be acked too
|
||||||
|
assertAcked(client().admin().indices().prepareClose(indexName));
|
||||||
|
assertIndexIsClosed(indexName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseUnassignedIndex() {
|
||||||
|
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
|
||||||
|
assertAcked(prepareCreate(indexName)
|
||||||
|
.setWaitForActiveShards(ActiveShardCount.NONE)
|
||||||
|
.setSettings(Settings.builder().put("index.routing.allocation.include._name", "nothing").build()));
|
||||||
|
|
||||||
|
final ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
|
||||||
|
assertThat(clusterState.metaData().indices().get(indexName).getState(), is(IndexMetaData.State.OPEN));
|
||||||
|
assertThat(clusterState.routingTable().allShards().stream().allMatch(ShardRouting::unassigned), is(true));
|
||||||
|
|
||||||
|
assertAcked(client().admin().indices().prepareClose(indexName));
|
||||||
|
assertIndexIsClosed(indexName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConcurrentClose() throws InterruptedException {
|
||||||
|
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
|
||||||
|
createIndex(indexName);
|
||||||
|
|
||||||
|
final int nbDocs = randomIntBetween(10, 50);
|
||||||
|
indexRandom(randomBoolean(), false, randomBoolean(), IntStream.range(0, nbDocs)
|
||||||
|
.mapToObj(i -> client().prepareIndex(indexName, "_doc", String.valueOf(i)).setSource("num", i)).collect(toList()));
|
||||||
|
|
||||||
|
final CountDownLatch startClosing = new CountDownLatch(1);
|
||||||
|
final Thread[] threads = new Thread[randomIntBetween(2, 5)];
|
||||||
|
|
||||||
|
for (int i = 0; i < threads.length; i++) {
|
||||||
|
threads[i] = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
startClosing.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
assertAcked(client().admin().indices().prepareClose(indexName));
|
||||||
|
});
|
||||||
|
threads[i].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
startClosing.countDown();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
assertIndexIsClosed(indexName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseWhileIndexingDocuments() throws Exception {
|
||||||
|
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
|
||||||
|
createIndex(indexName);
|
||||||
|
|
||||||
|
int nbDocs = 0;
|
||||||
|
try (BackgroundIndexer indexer = new BackgroundIndexer(indexName, "_doc", client())) {
|
||||||
|
indexer.setAssertNoFailuresOnStop(false);
|
||||||
|
|
||||||
|
waitForDocs(randomIntBetween(10, 50), indexer);
|
||||||
|
assertAcked(client().admin().indices().prepareClose(indexName));
|
||||||
|
indexer.stop();
|
||||||
|
nbDocs += indexer.totalIndexedDocs();
|
||||||
|
|
||||||
|
final Throwable[] failures = indexer.getFailures();
|
||||||
|
if (failures != null) {
|
||||||
|
for (Throwable failure : failures) {
|
||||||
|
assertException(failure, indexName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertIndexIsClosed(indexName);
|
||||||
|
assertAcked(client().admin().indices().prepareOpen(indexName));
|
||||||
|
assertHitCount(client().prepareSearch(indexName).setSize(0).get(), nbDocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseWhileDeletingIndices() throws Exception {
|
||||||
|
final String[] indices = new String[randomIntBetween(3, 10)];
|
||||||
|
for (int i = 0; i < indices.length; i++) {
|
||||||
|
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
|
||||||
|
createIndex(indexName);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
indexRandom(randomBoolean(), false, randomBoolean(), IntStream.range(0, 10)
|
||||||
|
.mapToObj(n -> client().prepareIndex(indexName, "_doc", String.valueOf(n)).setSource("num", n)).collect(toList()));
|
||||||
|
}
|
||||||
|
indices[i] = indexName;
|
||||||
|
}
|
||||||
|
assertThat(client().admin().cluster().prepareState().get().getState().metaData().indices().size(), equalTo(indices.length));
|
||||||
|
|
||||||
|
final List<Thread> threads = new ArrayList<>();
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
for (final String indexToDelete : indices) {
|
||||||
|
threads.add(new Thread(() -> {
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
assertAcked(client().admin().indices().prepareDelete(indexToDelete));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
assertException(e, indexToDelete);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
for (final String indexToClose : indices) {
|
||||||
|
threads.add(new Thread(() -> {
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
client().admin().indices().prepareClose(indexToClose).get();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
assertException(e, indexToClose);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertIndexIsClosed(final String indexName) {
|
||||||
|
final ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
|
||||||
|
assertThat(clusterState.metaData().indices().get(indexName).getState(), is(IndexMetaData.State.CLOSE));
|
||||||
|
assertThat(clusterState.routingTable().index(indexName), nullValue());
|
||||||
|
assertThat(clusterState.blocks().hasIndexBlock(indexName, MetaDataIndexStateService.INDEX_CLOSED_BLOCK), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertIndexIsOpened(final String indexName) {
|
||||||
|
final ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
|
||||||
|
assertThat(clusterState.metaData().indices().get(indexName).getState(), is(IndexMetaData.State.OPEN));
|
||||||
|
assertThat(clusterState.routingTable().index(indexName), notNullValue());
|
||||||
|
assertThat(clusterState.blocks().hasIndexBlock(indexName, MetaDataIndexStateService.INDEX_CLOSED_BLOCK), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertException(final Throwable throwable, final String indexName) {
|
||||||
|
final Throwable t = ExceptionsHelper.unwrapCause(throwable);
|
||||||
|
if (t instanceof ClusterBlockException) {
|
||||||
|
ClusterBlockException clusterBlockException = (ClusterBlockException) t;
|
||||||
|
assertThat(clusterBlockException.blocks(), hasSize(1));
|
||||||
|
assertThat(clusterBlockException.blocks(), hasItem(MetaDataIndexStateService.INDEX_CLOSED_BLOCK));
|
||||||
|
} else if (t instanceof IndexClosedException) {
|
||||||
|
IndexClosedException indexClosedException = (IndexClosedException) t;
|
||||||
|
assertThat(indexClosedException.getIndex(), notNullValue());
|
||||||
|
assertThat(indexClosedException.getIndex().getName(), equalTo(indexName));
|
||||||
|
} else if (t instanceof IndexNotFoundException) {
|
||||||
|
IndexNotFoundException indexNotFoundException = (IndexNotFoundException) t;
|
||||||
|
assertThat(indexNotFoundException.getIndex(), notNullValue());
|
||||||
|
assertThat(indexNotFoundException.getIndex().getName(), equalTo(indexName));
|
||||||
|
} else {
|
||||||
|
fail("Unexpected exception: " + t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* 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.indices.state;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.index.IndexResponse;
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
|
import org.elasticsearch.cluster.routing.IndexRoutingTable;
|
||||||
|
import org.elasticsearch.cluster.routing.ShardRouting;
|
||||||
|
import org.elasticsearch.cluster.routing.allocation.command.MoveAllocationCommand;
|
||||||
|
import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
|
||||||
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
import org.elasticsearch.test.BackgroundIndexer;
|
||||||
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import static org.elasticsearch.cluster.routing.allocation.decider.ConcurrentRebalanceAllocationDecider.CLUSTER_ROUTING_ALLOCATION_CLUSTER_CONCURRENT_REBALANCE_SETTING;
|
||||||
|
import static org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING;
|
||||||
|
import static org.elasticsearch.cluster.routing.allocation.decider.ThrottlingAllocationDecider.CLUSTER_ROUTING_ALLOCATION_NODE_CONCURRENT_RECOVERIES_SETTING;
|
||||||
|
import static org.elasticsearch.indices.state.CloseIndexIT.assertException;
|
||||||
|
import static org.elasticsearch.indices.state.CloseIndexIT.assertIndexIsClosed;
|
||||||
|
import static org.elasticsearch.indices.state.CloseIndexIT.assertIndexIsOpened;
|
||||||
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||||
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
|
|
||||||
|
@ESIntegTestCase.ClusterScope(minNumDataNodes = 2)
|
||||||
|
public class CloseWhileRelocatingShardsIT extends ESIntegTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Settings nodeSettings(int nodeOrdinal) {
|
||||||
|
return Settings.builder()
|
||||||
|
.put(super.nodeSettings(nodeOrdinal))
|
||||||
|
.put(CLUSTER_ROUTING_ALLOCATION_NODE_CONCURRENT_RECOVERIES_SETTING.getKey(), 10)
|
||||||
|
.put(CLUSTER_ROUTING_ALLOCATION_CLUSTER_CONCURRENT_REBALANCE_SETTING.getKey(), -1)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int numberOfReplicas() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCloseWhileRelocatingShards() throws Exception {
|
||||||
|
final String[] indices = new String[randomIntBetween(3, 10)];
|
||||||
|
final Map<String, Long> docsPerIndex = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < indices.length; i++) {
|
||||||
|
final String indexName = "index-" + i;
|
||||||
|
createIndex(indexName);
|
||||||
|
|
||||||
|
int nbDocs = 0;
|
||||||
|
if (randomBoolean()) {
|
||||||
|
nbDocs = randomIntBetween(1, 20);
|
||||||
|
for (int j = 0; j < nbDocs; j++) {
|
||||||
|
IndexResponse indexResponse = client().prepareIndex(indexName, "_doc").setSource("num", j).get();
|
||||||
|
assertEquals(RestStatus.CREATED, indexResponse.status());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
docsPerIndex.put(indexName, (long) nbDocs);
|
||||||
|
indices[i] = indexName;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureGreen(indices);
|
||||||
|
assertAcked(client().admin().cluster().prepareUpdateSettings()
|
||||||
|
.setTransientSettings(Settings.builder()
|
||||||
|
.put(CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING.getKey(), EnableAllocationDecider.Rebalance.NONE.toString())));
|
||||||
|
|
||||||
|
// start some concurrent indexing threads
|
||||||
|
final Map<String, BackgroundIndexer> indexers = new HashMap<>();
|
||||||
|
for (final String index : indices) {
|
||||||
|
if (randomBoolean()) {
|
||||||
|
final BackgroundIndexer indexer = new BackgroundIndexer(index, "_doc", client());
|
||||||
|
waitForDocs(1, indexer);
|
||||||
|
indexers.put(index, indexer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> acknowledgedCloses = ConcurrentCollections.newConcurrentSet();
|
||||||
|
final String newNode = internalCluster().startDataOnlyNode();
|
||||||
|
try {
|
||||||
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
final List<Thread> threads = new ArrayList<>();
|
||||||
|
|
||||||
|
// start shards relocating threads
|
||||||
|
final ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
|
||||||
|
for (final String indexToRelocate : indices) {
|
||||||
|
final IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(indexToRelocate);
|
||||||
|
for (int i = 0; i < getNumShards(indexToRelocate).numPrimaries; i++) {
|
||||||
|
final int shardId = i;
|
||||||
|
ShardRouting primary = indexRoutingTable.shard(shardId).primaryShard();
|
||||||
|
assertTrue(primary.started());
|
||||||
|
ShardRouting replica = indexRoutingTable.shard(shardId).replicaShards().iterator().next();
|
||||||
|
assertTrue(replica.started());
|
||||||
|
|
||||||
|
final String currentNodeId = randomBoolean() ? primary.currentNodeId() : replica.currentNodeId();
|
||||||
|
assertNotNull(currentNodeId);
|
||||||
|
|
||||||
|
final Thread thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
assertAcked(client().admin().cluster().prepareReroute()
|
||||||
|
.add(new MoveAllocationCommand(indexToRelocate, shardId, currentNodeId, newNode)));
|
||||||
|
});
|
||||||
|
threads.add(thread);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start index closing threads
|
||||||
|
for (final String indexToClose : indices) {
|
||||||
|
final Thread thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
AcknowledgedResponse closeResponse = client().admin().indices().prepareClose(indexToClose).get();
|
||||||
|
if (closeResponse.isAcknowledged()) {
|
||||||
|
assertTrue("Index closing should not be acknowledged twice", acknowledgedCloses.add(indexToClose));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
threads.add(thread);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
latch.countDown();
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, BackgroundIndexer> entry : indexers.entrySet()) {
|
||||||
|
final BackgroundIndexer indexer = entry.getValue();
|
||||||
|
indexer.setAssertNoFailuresOnStop(false);
|
||||||
|
indexer.stop();
|
||||||
|
|
||||||
|
final String indexName = entry.getKey();
|
||||||
|
docsPerIndex.computeIfPresent(indexName, (key, value) -> value + indexer.totalIndexedDocs());
|
||||||
|
|
||||||
|
final Throwable[] failures = indexer.getFailures();
|
||||||
|
if (failures != null) {
|
||||||
|
for (Throwable failure : failures) {
|
||||||
|
assertException(failure, indexName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
assertAcked(client().admin().cluster().prepareUpdateSettings()
|
||||||
|
.setTransientSettings(Settings.builder().putNull(CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING.getKey())));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String index : indices) {
|
||||||
|
if (acknowledgedCloses.contains(index)) {
|
||||||
|
assertIndexIsClosed(index);
|
||||||
|
} else {
|
||||||
|
assertIndexIsOpened(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat("Consider that the test failed if no indices were successfully closed", acknowledgedCloses.size(), greaterThan(0));
|
||||||
|
assertAcked(client().admin().indices().prepareOpen("index-*"));
|
||||||
|
ensureGreen(indices);
|
||||||
|
|
||||||
|
for (String index : acknowledgedCloses) {
|
||||||
|
long docsCount = client().prepareSearch(index).setSize(0).get().getHits().getTotalHits().value;
|
||||||
|
assertEquals("Expected " + docsPerIndex.get(index) + " docs in index " + index + " but got " + docsCount
|
||||||
|
+ " (close acknowledged=" + acknowledgedCloses.contains(index) + ")", (long) docsPerIndex.get(index), docsCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,13 +72,6 @@ public class OpenCloseIndexIT extends ESIntegTestCase {
|
||||||
assertIndexIsOpened("test1");
|
assertIndexIsOpened("test1");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSimpleCloseMissingIndex() {
|
|
||||||
Client client = client();
|
|
||||||
Exception e = expectThrows(IndexNotFoundException.class, () ->
|
|
||||||
client.admin().indices().prepareClose("test1").execute().actionGet());
|
|
||||||
assertThat(e.getMessage(), is("no such index [test1]"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSimpleOpenMissingIndex() {
|
public void testSimpleOpenMissingIndex() {
|
||||||
Client client = client();
|
Client client = client();
|
||||||
Exception e = expectThrows(IndexNotFoundException.class, () ->
|
Exception e = expectThrows(IndexNotFoundException.class, () ->
|
||||||
|
@ -86,27 +79,6 @@ public class OpenCloseIndexIT extends ESIntegTestCase {
|
||||||
assertThat(e.getMessage(), is("no such index [test1]"));
|
assertThat(e.getMessage(), is("no such index [test1]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCloseOneMissingIndex() {
|
|
||||||
Client client = client();
|
|
||||||
createIndex("test1");
|
|
||||||
ClusterHealthResponse healthResponse = client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
|
|
||||||
assertThat(healthResponse.isTimedOut(), equalTo(false));
|
|
||||||
Exception e = expectThrows(IndexNotFoundException.class, () ->
|
|
||||||
client.admin().indices().prepareClose("test1", "test2").execute().actionGet());
|
|
||||||
assertThat(e.getMessage(), is("no such index [test2]"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCloseOneMissingIndexIgnoreMissing() {
|
|
||||||
Client client = client();
|
|
||||||
createIndex("test1");
|
|
||||||
ClusterHealthResponse healthResponse = client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
|
|
||||||
assertThat(healthResponse.isTimedOut(), equalTo(false));
|
|
||||||
AcknowledgedResponse closeIndexResponse = client.admin().indices().prepareClose("test1", "test2")
|
|
||||||
.setIndicesOptions(IndicesOptions.lenientExpandOpen()).execute().actionGet();
|
|
||||||
assertThat(closeIndexResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertIndexIsClosed("test1");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testOpenOneMissingIndex() {
|
public void testOpenOneMissingIndex() {
|
||||||
Client client = client();
|
Client client = client();
|
||||||
createIndex("test1");
|
createIndex("test1");
|
||||||
|
@ -200,20 +172,6 @@ public class OpenCloseIndexIT extends ESIntegTestCase {
|
||||||
assertIndexIsOpened("test1", "test2", "test3");
|
assertIndexIsOpened("test1", "test2", "test3");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCloseNoIndex() {
|
|
||||||
Client client = client();
|
|
||||||
Exception e = expectThrows(ActionRequestValidationException.class, () ->
|
|
||||||
client.admin().indices().prepareClose().execute().actionGet());
|
|
||||||
assertThat(e.getMessage(), containsString("index is missing"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCloseNullIndex() {
|
|
||||||
Client client = client();
|
|
||||||
Exception e = expectThrows(ActionRequestValidationException.class, () ->
|
|
||||||
client.admin().indices().prepareClose((String[])null).execute().actionGet());
|
|
||||||
assertThat(e.getMessage(), containsString("index is missing"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testOpenNoIndex() {
|
public void testOpenNoIndex() {
|
||||||
Client client = client();
|
Client client = client();
|
||||||
Exception e = expectThrows(ActionRequestValidationException.class, () ->
|
Exception e = expectThrows(ActionRequestValidationException.class, () ->
|
||||||
|
@ -241,23 +199,6 @@ public class OpenCloseIndexIT extends ESIntegTestCase {
|
||||||
assertIndexIsOpened("test1");
|
assertIndexIsOpened("test1");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCloseAlreadyClosedIndex() {
|
|
||||||
Client client = client();
|
|
||||||
createIndex("test1");
|
|
||||||
ClusterHealthResponse healthResponse = client.admin().cluster().prepareHealth().setWaitForGreenStatus().execute().actionGet();
|
|
||||||
assertThat(healthResponse.isTimedOut(), equalTo(false));
|
|
||||||
|
|
||||||
//closing the index
|
|
||||||
AcknowledgedResponse closeIndexResponse = client.admin().indices().prepareClose("test1").execute().actionGet();
|
|
||||||
assertThat(closeIndexResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertIndexIsClosed("test1");
|
|
||||||
|
|
||||||
//no problem if we try to close an index that's already in close state
|
|
||||||
closeIndexResponse = client.admin().indices().prepareClose("test1").execute().actionGet();
|
|
||||||
assertThat(closeIndexResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertIndexIsClosed("test1");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSimpleCloseOpenAlias() {
|
public void testSimpleCloseOpenAlias() {
|
||||||
Client client = client();
|
Client client = client();
|
||||||
createIndex("test1");
|
createIndex("test1");
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
|
||||||
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
|
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
|
||||||
import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
|
import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
|
||||||
import org.elasticsearch.action.support.ActiveShardCount;
|
import org.elasticsearch.action.support.ActiveShardCount;
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
|
||||||
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
||||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||||
import org.elasticsearch.cluster.routing.ShardRoutingState;
|
import org.elasticsearch.cluster.routing.ShardRoutingState;
|
||||||
|
@ -35,6 +34,7 @@ import org.elasticsearch.index.IndexNotFoundException;
|
||||||
import org.elasticsearch.indices.IndexClosedException;
|
import org.elasticsearch.indices.IndexClosedException;
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
|
|
||||||
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
|
@ -61,8 +61,7 @@ public class SimpleIndexStateIT extends ESIntegTestCase {
|
||||||
client().prepareIndex("test", "type1", "1").setSource("field1", "value1").get();
|
client().prepareIndex("test", "type1", "1").setSource("field1", "value1").get();
|
||||||
|
|
||||||
logger.info("--> closing test index...");
|
logger.info("--> closing test index...");
|
||||||
AcknowledgedResponse closeIndexResponse = client().admin().indices().prepareClose("test").get();
|
assertAcked(client().admin().indices().prepareClose("test"));
|
||||||
assertThat(closeIndexResponse.isAcknowledged(), equalTo(true));
|
|
||||||
|
|
||||||
stateResponse = client().admin().cluster().prepareState().get();
|
stateResponse = client().admin().cluster().prepareState().get();
|
||||||
assertThat(stateResponse.getState().metaData().index("test").getState(), equalTo(IndexMetaData.State.CLOSE));
|
assertThat(stateResponse.getState().metaData().index("test").getState(), equalTo(IndexMetaData.State.CLOSE));
|
||||||
|
@ -103,7 +102,7 @@ public class SimpleIndexStateIT extends ESIntegTestCase {
|
||||||
assertThat(health.isTimedOut(), equalTo(false));
|
assertThat(health.isTimedOut(), equalTo(false));
|
||||||
assertThat(health.getStatus(), equalTo(ClusterHealthStatus.RED));
|
assertThat(health.getStatus(), equalTo(ClusterHealthStatus.RED));
|
||||||
|
|
||||||
client().admin().indices().prepareClose("test").get();
|
assertAcked(client().admin().indices().prepareClose("test"));
|
||||||
|
|
||||||
logger.info("--> updating test index settings to allow allocation");
|
logger.info("--> updating test index settings to allow allocation");
|
||||||
client().admin().indices().prepareUpdateSettings("test").setSettings(Settings.builder()
|
client().admin().indices().prepareUpdateSettings("test").setSettings(Settings.builder()
|
||||||
|
|
|
@ -594,7 +594,7 @@ public class DedicatedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTest
|
||||||
equalTo(SnapshotState.PARTIAL));
|
equalTo(SnapshotState.PARTIAL));
|
||||||
}
|
}
|
||||||
|
|
||||||
assertAcked(client().admin().indices().prepareClose("test-idx-some", "test-idx-all").execute().actionGet());
|
assertAcked(client().admin().indices().prepareClose("test-idx-some", "test-idx-all"));
|
||||||
|
|
||||||
logger.info("--> restore incomplete snapshot - should fail");
|
logger.info("--> restore incomplete snapshot - should fail");
|
||||||
assertThrows(client().admin().cluster().prepareRestoreSnapshot("test-repo", "test-snap-2").setRestoreGlobalState(false)
|
assertThrows(client().admin().cluster().prepareRestoreSnapshot("test-repo", "test-snap-2").setRestoreGlobalState(false)
|
||||||
|
|
|
@ -20,12 +20,10 @@ package org.elasticsearch.test;/*
|
||||||
import com.carrotsearch.randomizedtesting.RandomizedTest;
|
import com.carrotsearch.randomizedtesting.RandomizedTest;
|
||||||
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
|
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
|
||||||
import com.carrotsearch.randomizedtesting.generators.RandomStrings;
|
import com.carrotsearch.randomizedtesting.generators.RandomStrings;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||||
import org.apache.logging.log4j.util.Supplier;
|
import org.apache.logging.log4j.util.Supplier;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.action.bulk.BulkItemResponse;
|
import org.elasticsearch.action.bulk.BulkItemResponse;
|
||||||
import org.elasticsearch.action.bulk.BulkRequestBuilder;
|
import org.elasticsearch.action.bulk.BulkRequestBuilder;
|
||||||
import org.elasticsearch.action.bulk.BulkResponse;
|
import org.elasticsearch.action.bulk.BulkResponse;
|
||||||
|
@ -63,6 +61,7 @@ public class BackgroundIndexer implements AutoCloseable {
|
||||||
final Semaphore availableBudget = new Semaphore(0);
|
final Semaphore availableBudget = new Semaphore(0);
|
||||||
final boolean useAutoGeneratedIDs;
|
final boolean useAutoGeneratedIDs;
|
||||||
private final Set<String> ids = ConcurrentCollections.newConcurrentSet();
|
private final Set<String> ids = ConcurrentCollections.newConcurrentSet();
|
||||||
|
private boolean assertNoFailuresOnStop = true;
|
||||||
|
|
||||||
volatile int minFieldSize = 10;
|
volatile int minFieldSize = 10;
|
||||||
volatile int maxFieldSize = 140;
|
volatile int maxFieldSize = 140;
|
||||||
|
@ -163,13 +162,11 @@ public class BackgroundIndexer implements AutoCloseable {
|
||||||
}
|
}
|
||||||
BulkResponse bulkResponse = bulkRequest.get();
|
BulkResponse bulkResponse = bulkRequest.get();
|
||||||
for (BulkItemResponse bulkItemResponse : bulkResponse) {
|
for (BulkItemResponse bulkItemResponse : bulkResponse) {
|
||||||
if (!bulkItemResponse.isFailed()) {
|
if (bulkItemResponse.isFailed() == false) {
|
||||||
boolean add = ids.add(bulkItemResponse.getId());
|
boolean add = ids.add(bulkItemResponse.getId());
|
||||||
assert add : "ID: " + bulkItemResponse.getId() + " already used";
|
assert add : "ID: " + bulkItemResponse.getId() + " already used";
|
||||||
} else {
|
} else {
|
||||||
throw new ElasticsearchException("bulk request failure, id: ["
|
failures.add(bulkItemResponse.getFailure().getCause());
|
||||||
+ bulkItemResponse.getFailure().getId() + "] message: "
|
|
||||||
+ bulkItemResponse.getFailure().getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,7 +280,9 @@ public class BackgroundIndexer implements AutoCloseable {
|
||||||
}
|
}
|
||||||
stop.set(true);
|
stop.set(true);
|
||||||
Assert.assertThat("timeout while waiting for indexing threads to stop", stopLatch.await(6, TimeUnit.MINUTES), equalTo(true));
|
Assert.assertThat("timeout while waiting for indexing threads to stop", stopLatch.await(6, TimeUnit.MINUTES), equalTo(true));
|
||||||
assertNoFailures();
|
if (assertNoFailuresOnStop) {
|
||||||
|
assertNoFailures();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long totalIndexedDocs() {
|
public long totalIndexedDocs() {
|
||||||
|
@ -308,6 +307,10 @@ public class BackgroundIndexer implements AutoCloseable {
|
||||||
maxFieldSize = fieldSize;
|
maxFieldSize = fieldSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAssertNoFailuresOnStop(final boolean assertNoFailuresOnStop) {
|
||||||
|
this.assertNoFailuresOnStop = assertNoFailuresOnStop;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() throws Exception {
|
||||||
stop();
|
stop();
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.core.action;
|
package org.elasticsearch.xpack.core.action;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||||
import org.elasticsearch.ResourceNotFoundException;
|
import org.elasticsearch.ResourceNotFoundException;
|
||||||
import org.elasticsearch.action.Action;
|
import org.elasticsearch.action.Action;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
import org.elasticsearch.action.IndicesRequest;
|
import org.elasticsearch.action.IndicesRequest;
|
||||||
|
import org.elasticsearch.action.admin.indices.close.CloseIndexClusterStateUpdateRequest;
|
||||||
|
import org.elasticsearch.action.admin.indices.close.TransportCloseIndexAction;
|
||||||
import org.elasticsearch.action.admin.indices.open.OpenIndexClusterStateUpdateRequest;
|
import org.elasticsearch.action.admin.indices.open.OpenIndexClusterStateUpdateRequest;
|
||||||
import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
|
import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
import org.elasticsearch.action.support.ActionFilters;
|
||||||
|
@ -54,17 +57,20 @@ public final class TransportFreezeIndexAction extends
|
||||||
|
|
||||||
private final DestructiveOperations destructiveOperations;
|
private final DestructiveOperations destructiveOperations;
|
||||||
private final MetaDataIndexStateService indexStateService;
|
private final MetaDataIndexStateService indexStateService;
|
||||||
|
private final TransportCloseIndexAction transportCloseIndexAction;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TransportFreezeIndexAction(MetaDataIndexStateService indexStateService, TransportService transportService,
|
public TransportFreezeIndexAction(MetaDataIndexStateService indexStateService, TransportService transportService,
|
||||||
ClusterService clusterService,
|
ClusterService clusterService,
|
||||||
ThreadPool threadPool, ActionFilters actionFilters,
|
ThreadPool threadPool, ActionFilters actionFilters,
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver,
|
IndexNameExpressionResolver indexNameExpressionResolver,
|
||||||
DestructiveOperations destructiveOperations) {
|
DestructiveOperations destructiveOperations,
|
||||||
|
TransportCloseIndexAction transportCloseIndexAction) {
|
||||||
super(FreezeIndexAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver,
|
super(FreezeIndexAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver,
|
||||||
FreezeRequest::new);
|
FreezeRequest::new);
|
||||||
this.destructiveOperations = destructiveOperations;
|
this.destructiveOperations = destructiveOperations;
|
||||||
this.indexStateService = indexStateService;
|
this.indexStateService = indexStateService;
|
||||||
|
this.transportCloseIndexAction = transportCloseIndexAction;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected String executor() {
|
protected String executor() {
|
||||||
|
@ -108,6 +114,33 @@ public final class TransportFreezeIndexAction extends
|
||||||
listener.onResponse(new FreezeResponse(true, true));
|
listener.onResponse(new FreezeResponse(true, true));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final CloseIndexClusterStateUpdateRequest closeRequest = new CloseIndexClusterStateUpdateRequest()
|
||||||
|
.ackTimeout(request.timeout())
|
||||||
|
.masterNodeTimeout(request.masterNodeTimeout())
|
||||||
|
.indices(concreteIndices);
|
||||||
|
|
||||||
|
indexStateService.closeIndices(closeRequest, new ActionListener<AcknowledgedResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(final AcknowledgedResponse response) {
|
||||||
|
if (response.isAcknowledged()) {
|
||||||
|
toggleFrozenSettings(concreteIndices, request, listener);
|
||||||
|
} else {
|
||||||
|
// TODO improve FreezeResponse so that it also reports failures from the close index API
|
||||||
|
listener.onResponse(new FreezeResponse(false, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Exception t) {
|
||||||
|
logger.debug(() -> new ParameterizedMessage("failed to close indices [{}]", (Object) concreteIndices), t);
|
||||||
|
listener.onFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleFrozenSettings(final Index[] concreteIndices, final FreezeRequest request,
|
||||||
|
final ActionListener<FreezeResponse> listener) {
|
||||||
clusterService.submitStateUpdateTask("toggle-frozen-settings",
|
clusterService.submitStateUpdateTask("toggle-frozen-settings",
|
||||||
new AckedClusterStateUpdateTask<AcknowledgedResponse>(Priority.URGENT, request, new ActionListener<AcknowledgedResponse>() {
|
new AckedClusterStateUpdateTask<AcknowledgedResponse>(Priority.URGENT, request, new ActionListener<AcknowledgedResponse>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -136,14 +169,6 @@ public final class TransportFreezeIndexAction extends
|
||||||
}) {
|
}) {
|
||||||
@Override
|
@Override
|
||||||
public ClusterState execute(ClusterState currentState) {
|
public ClusterState execute(ClusterState currentState) {
|
||||||
List<Index> toClose = new ArrayList<>();
|
|
||||||
for (Index index : concreteIndices) {
|
|
||||||
IndexMetaData metaData = currentState.metaData().index(index);
|
|
||||||
if (metaData.getState() != IndexMetaData.State.CLOSE) {
|
|
||||||
toClose.add(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentState = indexStateService.closeIndices(currentState, toClose.toArray(new Index[0]), toClose.toString());
|
|
||||||
final MetaData.Builder builder = MetaData.builder(currentState.metaData());
|
final MetaData.Builder builder = MetaData.builder(currentState.metaData());
|
||||||
ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
|
ClusterBlocks.Builder blocks = ClusterBlocks.builder().blocks(currentState.blocks());
|
||||||
for (Index index : concreteIndices) {
|
for (Index index : concreteIndices) {
|
||||||
|
|
|
@ -59,7 +59,7 @@ public final class IndexPrivilege extends Privilege {
|
||||||
GetIndexAction.NAME, IndicesExistsAction.NAME, GetFieldMappingsAction.NAME + "*", GetMappingsAction.NAME,
|
GetIndexAction.NAME, IndicesExistsAction.NAME, GetFieldMappingsAction.NAME + "*", GetMappingsAction.NAME,
|
||||||
ClusterSearchShardsAction.NAME, TypesExistsAction.NAME, ValidateQueryAction.NAME + "*", GetSettingsAction.NAME);
|
ClusterSearchShardsAction.NAME, TypesExistsAction.NAME, ValidateQueryAction.NAME + "*", GetSettingsAction.NAME);
|
||||||
private static final Automaton MANAGE_FOLLOW_INDEX_AUTOMATON = patterns(PutFollowAction.NAME, UnfollowAction.NAME,
|
private static final Automaton MANAGE_FOLLOW_INDEX_AUTOMATON = patterns(PutFollowAction.NAME, UnfollowAction.NAME,
|
||||||
CloseIndexAction.NAME);
|
CloseIndexAction.NAME + "*");
|
||||||
|
|
||||||
public static final IndexPrivilege NONE = new IndexPrivilege("none", Automatons.EMPTY);
|
public static final IndexPrivilege NONE = new IndexPrivilege("none", Automatons.EMPTY);
|
||||||
public static final IndexPrivilege ALL = new IndexPrivilege("all", ALL_AUTOMATON);
|
public static final IndexPrivilege ALL = new IndexPrivilege("all", ALL_AUTOMATON);
|
||||||
|
|
Loading…
Reference in New Issue