org.elasticsearch.action.support.master.TransportMasterNodeAction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.action.support.master;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionListenerResponseHandler;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.ActionRunnable;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateObserver;
import org.elasticsearch.cluster.MasterNodeChangePredicate;
import org.elasticsearch.cluster.block.ClusterBlock;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.cluster.service.MasterService;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.discovery.MasterNotDiscoveredException;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.node.NodeClosedException;
import org.elasticsearch.tasks.CancellableTask;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.tasks.TaskCancelledException;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.ConnectTransportException;
import org.elasticsearch.transport.RemoteTransportException;
import org.elasticsearch.transport.TransportException;
import org.elasticsearch.transport.TransportService;
import java.util.function.Predicate;
import static org.elasticsearch.core.Strings.format;
/**
* A base class for operations that needs to be performed on the master node.
*/
public abstract class TransportMasterNodeAction, Response extends ActionResponse> extends
HandledTransportAction {
private static final Logger logger = LogManager.getLogger(TransportMasterNodeAction.class);
protected final ThreadPool threadPool;
protected final TransportService transportService;
protected final ClusterService clusterService;
protected final IndexNameExpressionResolver indexNameExpressionResolver;
private final Writeable.Reader responseReader;
protected final String executor;
protected TransportMasterNodeAction(
String actionName,
TransportService transportService,
ClusterService clusterService,
ThreadPool threadPool,
ActionFilters actionFilters,
Writeable.Reader request,
IndexNameExpressionResolver indexNameExpressionResolver,
Writeable.Reader response,
String executor
) {
this(
actionName,
true,
transportService,
clusterService,
threadPool,
actionFilters,
request,
indexNameExpressionResolver,
response,
executor
);
}
protected TransportMasterNodeAction(
String actionName,
boolean canTripCircuitBreaker,
TransportService transportService,
ClusterService clusterService,
ThreadPool threadPool,
ActionFilters actionFilters,
Writeable.Reader request,
IndexNameExpressionResolver indexNameExpressionResolver,
Writeable.Reader response,
String executor
) {
super(actionName, canTripCircuitBreaker, transportService, actionFilters, request);
this.transportService = transportService;
this.clusterService = clusterService;
this.threadPool = threadPool;
this.indexNameExpressionResolver = indexNameExpressionResolver;
this.executor = executor;
this.responseReader = response;
}
protected abstract void masterOperation(Task task, Request request, ClusterState state, ActionListener listener)
throws Exception;
private void executeMasterOperation(Task task, Request request, ClusterState state, ActionListener listener)
throws Exception {
if (task instanceof CancellableTask && ((CancellableTask) task).isCancelled()) {
throw new TaskCancelledException("Task was cancelled");
}
masterOperation(task, request, state, listener);
}
protected boolean localExecute(Request request) {
return false;
}
protected abstract ClusterBlockException checkBlock(Request request, ClusterState state);
private ClusterBlockException checkBlockIfStateRecovered(Request request, ClusterState state) {
try {
return checkBlock(request, state);
} catch (IndexNotFoundException e) {
if (state.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
// no index metadata is exposed yet, but checkBlock depends on an index, so keep trying until the cluster forms
assert GatewayService.STATE_NOT_RECOVERED_BLOCK.contains(ClusterBlockLevel.METADATA_READ);
assert state.blocks().global(ClusterBlockLevel.METADATA_READ).stream().allMatch(ClusterBlock::retryable);
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
} else {
throw e;
}
}
}
@Override
protected void doExecute(Task task, final Request request, ActionListener listener) {
ClusterState state = clusterService.state();
logger.trace("starting processing request [{}] with cluster state version [{}]", request, state.version());
if (task != null) {
request.setParentTask(clusterService.localNode().getId(), task.getId());
}
new AsyncSingleAction(task, request, listener).doStart(state);
}
class AsyncSingleAction {
private final ActionListener listener;
private final Request request;
private ClusterStateObserver observer;
private final long startTime;
private final Task task;
AsyncSingleAction(Task task, Request request, ActionListener listener) {
this.task = task;
this.request = request;
this.listener = listener;
this.startTime = threadPool.relativeTimeInMillis();
}
protected void doStart(ClusterState clusterState) {
if (isTaskCancelled()) {
listener.onFailure(new TaskCancelledException("Task was cancelled"));
return;
}
try {
final DiscoveryNodes nodes = clusterState.nodes();
if (nodes.isLocalNodeElectedMaster() || localExecute(request)) {
// check for block, if blocked, retry, else, execute locally
final ClusterBlockException blockException = checkBlockIfStateRecovered(request, clusterState);
if (blockException != null) {
if (blockException.retryable() == false) {
logger.trace("can't execute due to a non-retryable cluster block", blockException);
listener.onFailure(blockException);
} else {
logger.debug("can't execute due to a cluster block, retrying", blockException);
retry(clusterState, blockException, newState -> {
try {
ClusterBlockException newException = checkBlockIfStateRecovered(request, newState);
return (newException == null || newException.retryable() == false);
} catch (Exception e) {
// accept state as block will be rechecked by doStart() and listener.onFailure() then called
logger.debug("exception occurred during cluster block checking, accepting state", e);
return true;
}
});
}
} else {
ActionListener delegate = listener.delegateResponse((delegatedListener, t) -> {
if (MasterService.isPublishFailureException(t)) {
logger.debug(
() -> format(
"master could not publish cluster state or "
+ "stepped down before publishing action [%s], scheduling a retry",
actionName
),
t
);
retryOnMasterChange(clusterState, t);
} else {
logger.debug("unexpected exception during publication", t);
delegatedListener.onFailure(t);
}
});
threadPool.executor(executor)
.execute(ActionRunnable.wrap(delegate, l -> executeMasterOperation(task, request, clusterState, l)));
}
} else {
if (nodes.getMasterNode() == null) {
logger.debug("no known master node, scheduling a retry");
retryOnMasterChange(clusterState, null);
} else {
DiscoveryNode masterNode = nodes.getMasterNode();
logger.trace("forwarding request [{}] to master [{}]", actionName, masterNode);
transportService.sendRequest(
masterNode,
actionName,
request,
new ActionListenerResponseHandler(listener, responseReader) {
@Override
public void handleException(final TransportException exp) {
Throwable cause = exp.unwrapCause();
if (cause instanceof ConnectTransportException
|| (exp instanceof RemoteTransportException && cause instanceof NodeClosedException)) {
// we want to retry here a bit to see if a new master is elected
logger.debug(
"connection exception while trying to forward request with action name [{}] to "
+ "master node [{}], scheduling a retry. Error: [{}]",
actionName,
nodes.getMasterNode(),
exp.getDetailedMessage()
);
retryOnMasterChange(clusterState, cause);
} else {
logger.trace(
new ParameterizedMessage(
"failure when forwarding request [{}] to master [{}]",
actionName,
masterNode
),
exp
);
listener.onFailure(exp);
}
}
}
);
}
}
} catch (Exception e) {
logger.trace("top-level failure", e);
listener.onFailure(e);
}
}
private void retryOnMasterChange(ClusterState state, Throwable failure) {
retry(state, failure, MasterNodeChangePredicate.build(state));
}
private void retry(ClusterState state, final Throwable failure, final Predicate statePredicate) {
if (observer == null) {
final long remainingTimeoutMS = request.masterNodeTimeout().millis() - (threadPool.relativeTimeInMillis() - startTime);
if (remainingTimeoutMS <= 0) {
logger.debug(() -> "timed out before retrying [" + actionName + "] after failure", failure);
listener.onFailure(new MasterNotDiscoveredException(failure));
return;
}
this.observer = new ClusterStateObserver(
state,
clusterService,
TimeValue.timeValueMillis(remainingTimeoutMS),
logger,
threadPool.getThreadContext()
);
}
observer.waitForNextChange(new ClusterStateObserver.Listener() {
@Override
public void onNewClusterState(ClusterState state) {
logger.trace("retrying with cluster state version [{}]", state.version());
doStart(state);
}
@Override
public void onClusterServiceClose() {
listener.onFailure(new NodeClosedException(clusterService.localNode()));
}
@Override
public void onTimeout(TimeValue timeout) {
logger.debug(() -> format("timed out while retrying [%s] after failure (timeout [%s])", actionName, timeout), failure);
listener.onFailure(new MasterNotDiscoveredException(failure));
}
}, clusterState -> isTaskCancelled() || statePredicate.test(clusterState));
}
private boolean isTaskCancelled() {
return task instanceof CancellableTask && ((CancellableTask) task).isCancelled();
}
}
}